#include #include #include #include #include #include #include namespace n64 { Mem::Mem(Registers ®s, ParallelRDP ¶llel, JIT *jit) : mmio(*this, regs, parallel), flash(saveData), jit(jit) { rom.cart.resize(CART_SIZE); std::ranges::fill(rom.cart, 0); } void Mem::Reset() { std::ranges::fill(rom.cart, 0); flash.Reset(); if (saveData.is_mapped()) { std::error_code error; saveData.sync(error); if (error) { panic("Could not sync save data"); } saveData.unmap(); } mmio.Reset(); } void Mem::LoadSRAM(SaveType save_type, fs::path path) { if (save_type == SAVE_SRAM_256k) { std::error_code error; std::string savePath = Options::GetInstance().GetValue("general", "savePath"); if (!savePath.empty()) { path = savePath / path.filename(); } sramPath = path.replace_extension(".sram").string(); if (saveData.is_mapped()) { saveData.sync(error); if (error) { panic("Could not sync {}", sramPath); } saveData.unmap(); } auto sramVec = Util::ReadFileBinary(sramPath); if (sramVec.empty()) { Util::WriteFileBinary(std::array{}, sramPath); sramVec = Util::ReadFileBinary(sramPath); } if (sramVec.size() != SRAM_SIZE) { panic("Corrupt SRAM!"); } saveData = mio::make_mmap_sink(sramPath, 0, mio::map_entire_file, error); if (error) { panic("Could not mmap {}", sramPath); } } } FORCE_INLINE void SetROMCIC(u32 checksum, ROM &rom) { switch (checksum) { case 0xEC8B1325: rom.cicType = CIC_NUS_7102; break; // 7102 case 0x1DEB51A9: rom.cicType = CIC_NUS_6101; break; // 6101 case 0xC08E5BD6: rom.cicType = CIC_NUS_6102_7101; break; case 0x03B8376A: rom.cicType = CIC_NUS_6103_7103; break; case 0xCF7F41DC: rom.cicType = CIC_NUS_6105_7105; break; case 0xD1059C6A: rom.cicType = CIC_NUS_6106_7106; break; default: warn("Could not determine CIC TYPE! Checksum: 0x{:08X} is unknown!", checksum); rom.cicType = UNKNOWN_CIC_TYPE; break; } } std::vector Mem::OpenArchive(const std::string &path, size_t &sizeAdjusted) { const auto stream = ar_open_file(fs::path(path).string().c_str()); if (!stream) { panic("Could not open archive! Are you sure it's an archive?"); } ar_archive *archive = ar_open_zip_archive(stream, false); if (!archive) archive = ar_open_rar_archive(stream); if (!archive) archive = ar_open_7z_archive(stream); if (!archive) archive = ar_open_tar_archive(stream); if (!archive) { ar_close(stream); panic("Could not open archive! Are you sure it's a supported archive? (7z, zip, rar and tar are supported)"); } std::vector buf{}; std::vector rom_exts{".n64", ".z64", ".v64", ".N64", ".Z64", ".V64"}; while (ar_parse_entry(archive)) { auto filename = ar_entry_get_name(archive); auto extension = fs::path(filename).extension(); if (std::ranges::any_of(rom_exts, [&](const auto &x) { return extension == x; })) { const auto size = ar_entry_get_size(archive); sizeAdjusted = Util::NextPow2(size); buf.resize(sizeAdjusted); ar_entry_uncompress(archive, buf.data(), size); break; } ar_close_archive(archive); ar_close(stream); panic("Could not find any rom image in the archive!"); } ar_close_archive(archive); ar_close(stream); return buf; } std::vector Mem::OpenROM(const std::string &filename, size_t &sizeAdjusted) { auto buf = Util::ReadFileBinary(filename); sizeAdjusted = Util::NextPow2(buf.size()); return buf; } void Mem::LoadROM(const bool isArchive, const std::string &filename) { u32 endianness; { size_t sizeAdjusted; std::vector buf{}; if (isArchive) { buf = OpenArchive(filename, sizeAdjusted); } else { buf = OpenROM(filename, sizeAdjusted); } endianness = std::byteswap(Util::ReadAccess(buf, 0)); Util::SwapN64Rom(buf, endianness); std::ranges::copy(buf, rom.cart.begin()); rom.mask = sizeAdjusted - 1; memcpy(&rom.header, buf.data(), sizeof(ROMHeader)); } memcpy(rom.gameNameCart, rom.header.imageName, sizeof(rom.header.imageName)); rom.header.clockRate = std::byteswap(rom.header.clockRate); rom.header.programCounter = std::byteswap(rom.header.programCounter); rom.header.release = std::byteswap(rom.header.release); rom.header.crc1 = std::byteswap(rom.header.crc1); rom.header.crc2 = std::byteswap(rom.header.crc2); rom.header.unknown = std::byteswap(rom.header.unknown); rom.header.unknown2 = std::byteswap(rom.header.unknown2); rom.header.manufacturerId = std::byteswap(rom.header.manufacturerId); rom.header.cartridgeId = std::byteswap(rom.header.cartridgeId); rom.code[0] = rom.header.manufacturerId & 0xFF; rom.code[1] = (rom.header.cartridgeId >> 8) & 0xFF; rom.code[2] = rom.header.cartridgeId & 0xFF; rom.code[3] = '\0'; for (int i = sizeof(rom.header.imageName) - 1; rom.gameNameCart[i] == ' '; i--) { rom.gameNameCart[i] = '\0'; } const u32 checksum = Util::crc32(0, &rom.cart[0x40], 0x9c0); SetROMCIC(checksum, rom); endianness = std::byteswap(Util::ReadAccess(rom.cart, 0)); Util::SwapN64Rom(rom.cart, endianness); rom.pal = IsROMPAL(); } template <> u8 Mem::Read(Registers ®s, const u32 paddr) { const SI &si = mmio.si; if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM(paddr); if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; return src[BYTE_ADDRESS(paddr & 0xfff)]; } if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead(paddr); if(Util::IsInsideRange(paddr, AI_REGION_START, AI_REGION_END)) { const u32 w = mmio.ai.Read(paddr & ~3); const int offs = 3 - (paddr & 3); return w >> offs * 8 & 0xff; } if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) panic("MMIO Read!"); if(Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return si.pif.bootrom[BYTE_ADDRESS(paddr) - PIF_ROM_REGION_START]; if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return si.pif.ram[paddr - PIF_RAM_REGION_START]; if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; panic("Unimplemented 8-bit read at address {:08X} (PC = {:016X})", paddr, (u64)regs.pc); return 0; } template <> u16 Mem::Read(Registers ®s, const u32 paddr) { const SI &si = mmio.si; if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM(paddr); if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; return Util::ReadAccess(src, HALF_ADDRESS(paddr & 0xfff)); } if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead(paddr); if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr); if(Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return Util::ReadAccess(si.pif.bootrom, HALF_ADDRESS(paddr) - PIF_ROM_REGION_START); if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(Util::ReadAccess(si.pif.ram, paddr - PIF_RAM_REGION_START)); if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; panic("Unimplemented 16-bit read at address {:08X} (PC = {:016X})", paddr, (u64)regs.pc); return 0; } template <> u32 Mem::Read(Registers ®s, const u32 paddr) { const SI &si = mmio.si; if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM(paddr); if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; return Util::ReadAccess(src, paddr & 0xfff); } if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead(paddr); if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr); if(Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return Util::ReadAccess(si.pif.bootrom, paddr - PIF_ROM_REGION_START); if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(Util::ReadAccess(si.pif.ram, paddr - PIF_RAM_REGION_START)); if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; panic("Unimplemented 32-bit read at address {:08X} (PC = {:016X})", paddr, (u64)regs.pc); return 0; } template <> u64 Mem::Read(Registers ®s, const u32 paddr) { const SI &si = mmio.si; if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM(paddr); if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; return Util::ReadAccess(src, paddr & 0xfff); } if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead(paddr); if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr); if(Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return Util::ReadAccess(si.pif.bootrom, paddr - PIF_ROM_REGION_START); if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(Util::ReadAccess(si.pif.ram, paddr - PIF_RAM_REGION_START)); if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; panic("Unimplemented 64-bit read at address {:08X} (PC = {:016X})", paddr, (u64)regs.pc); return 0; } template <> void Mem::WriteInterpreter(Registers ®s, u32 paddr, u32 val) { SI &si = mmio.si; if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM(paddr, val); return; } if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { val = val << (8 * (3 - (paddr & 3))); auto &dest = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; paddr = (paddr & 0xFFF) & ~3; Util::WriteAccess(dest, paddr, val); return; } if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) { trace("BusWrite @ {:08X} = {:02X}", paddr, val); mmio.pi.BusWrite(paddr, val); return; } if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) panic("MMIO Write!"); if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) { val = val << (8 * (3 - (paddr & 3))); paddr = (paddr - PIF_RAM_REGION_START) & ~3; Util::WriteAccess(si.pif.ram, paddr, std::byteswap(val)); si.pif.ProcessCommands(*this); return; } if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) || Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return; panic("Unimplemented 8-bit write at address {:08X} with value {:02X} (PC = {:016X})", paddr, val, (u64)regs.pc); } #ifndef __aarch64__ template <> void Mem::WriteJIT(Registers ®s, const u32 paddr, const u32 val) { WriteInterpreter(regs, paddr, val); if (jit) jit->InvalidateBlock(paddr); } #endif template <> void Mem::Write(Registers ®s, const u32 paddr, const u32 val) { WriteInterpreter(regs, paddr, val); } template <> void Mem::WriteInterpreter(Registers ®s, u32 paddr, u32 val) { SI &si = mmio.si; if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM(paddr, val); return; } if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { val = val << (16 * !(paddr & 2)); auto &dest = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; paddr = (paddr & 0xFFF) & ~3; Util::WriteAccess(dest, paddr, val); return; } if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) { trace("BusWrite @ {:08X} = {:04X}", paddr, val); mmio.pi.BusWrite(paddr, val); return; } if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) panic("MMIO Write!"); if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) { val = val << (16 * !(paddr & 2)); paddr &= ~3; Util::WriteAccess(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val)); si.pif.ProcessCommands(*this); return; } if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) || Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return; panic("Unimplemented 16-bit write at address {:08X} with value {:04X} (PC = {:016X})", paddr, val, (u64)regs.pc); } #ifndef __aarch64__ template <> void Mem::WriteJIT(Registers ®s, const u32 paddr, const u32 val) { WriteInterpreter(regs, paddr, val); if (jit) jit->InvalidateBlock(paddr); } #endif template <> void Mem::Write(Registers ®s, const u32 paddr, const u32 val) { WriteInterpreter(regs, paddr, val); } template <> void Mem::WriteInterpreter(Registers ®s, const u32 paddr, const u32 val) { SI &si = mmio.si; if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM(paddr, val); return; } if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { auto &dest = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; Util::WriteAccess(dest, paddr & 0xfff, val); return; } if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) { trace("BusWrite @ {:08X} = {:08X}", paddr, val); mmio.pi.BusWrite(paddr, val); return; } if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) { mmio.Write(paddr, val); return; } if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) { Util::WriteAccess(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val)); si.pif.ProcessCommands(*this); return; } if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) || Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return; panic("Unimplemented 32-bit write at address {:08X} with value {:08X} (PC = {:016X})", paddr, val, (u64)regs.pc); } #ifndef __aarch64__ template <> void Mem::WriteJIT(Registers ®s, const u32 paddr, const u32 val) { WriteInterpreter(regs, paddr, val); if (jit) jit->InvalidateBlock(paddr); } #endif template <> void Mem::Write(Registers ®s, const u32 paddr, const u32 val) { WriteInterpreter(regs, paddr, val); } #ifndef __aarch64__ void Mem::WriteJIT(const Registers ®s, const u32 paddr, const u64 val) { WriteInterpreter(regs, paddr, val); if (jit) jit->InvalidateBlock(paddr); } #endif void Mem::Write(const Registers ®s, const u32 paddr, const u64 val) { WriteInterpreter(regs, paddr, val); } void Mem::WriteInterpreter(const Registers ®s, const u32 paddr, u64 val) { SI &si = mmio.si; if(Util::IsInsideRange(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM(paddr, val); return; } if(Util::IsInsideRange(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { auto &dest = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; val >>= 32; Util::WriteAccess(dest, paddr & 0xfff, val); return; } if(Util::IsInsideRange(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) { trace("BusWrite @ {:08X} = {:016X}", paddr, val); mmio.pi.BusWrite(paddr, val); return; } if(Util::IsInsideRange(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || Util::IsInsideRange(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) panic("MMIO Write!"); if(Util::IsInsideRange(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) { Util::WriteAccess(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val)); si.pif.ProcessCommands(*this); return; } if(Util::IsInsideRange(paddr, UNUSED_START_1, UNUSED_END_1) || // unused Util::IsInsideRange(paddr, UNUSED_START_2, UNUSED_END_2) || Util::IsInsideRange(paddr, UNUSED_START_3, UNUSED_END_3) || Util::IsInsideRange(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) || Util::IsInsideRange(paddr, UNUSED_START_4, UNUSED_END_4)) return; panic("Unimplemented 64-bit write at address {:08X} with value {:016X} (PC = {:016X})", paddr, val, (u64)regs.pc); } template <> u32 Mem::BackupRead(const u32 addr) { switch (saveType) { case SAVE_NONE: return 0; case SAVE_EEPROM_4k: case SAVE_EEPROM_16k: warn("Accessing cartridge backup type SAVE_EEPROM, returning 0 for word read"); return 0; case SAVE_FLASH_1m: return flash.Read(addr); case SAVE_SRAM_256k: return 0xFFFFFFFF; default: panic("Backup read word with unknown save type"); } } template <> u8 Mem::BackupRead(const u32 addr) { switch (saveType) { case SAVE_NONE: return 0; case SAVE_EEPROM_4k: case SAVE_EEPROM_16k: warn("Accessing cartridge backup type SAVE_EEPROM, returning 0 for word read"); return 0; case SAVE_FLASH_1m: return flash.Read(addr); case SAVE_SRAM_256k: if (saveData.is_mapped()) { assert(addr < saveData.size()); return saveData[addr]; } else { panic("Invalid backup Read if save data is not initialized"); } default: panic("Backup read word with unknown save type"); } } template <> void Mem::BackupWrite(const u32 addr, const u32 val) { switch (saveType) { case SAVE_NONE: warn("Accessing cartridge with save type SAVE_NONE in write word"); break; case SAVE_EEPROM_4k: case SAVE_EEPROM_16k: panic("Accessing cartridge with save type SAVE_EEPROM in write word"); case SAVE_FLASH_1m: flash.Write(addr, val); break; case SAVE_SRAM_256k: break; default: panic("Backup read word with unknown save type"); } } template <> void Mem::BackupWrite(const u32 addr, const u8 val) { switch (saveType) { case SAVE_NONE: warn("Accessing cartridge with save type SAVE_NONE in write word"); break; case SAVE_EEPROM_4k: case SAVE_EEPROM_16k: panic("Accessing cartridge with save type SAVE_EEPROM in write word"); case SAVE_FLASH_1m: flash.Write(addr, val); break; case SAVE_SRAM_256k: if (saveData.is_mapped()) { assert(addr < saveData.size()); saveData[addr] = val; } else { panic("Invalid backup Write if save data is not initialized"); } break; default: panic("Backup read word with unknown save type"); } } std::vector Mem::Serialize() { std::vector res{}; auto sMMIO = mmio.Serialize(); auto sFLASH = flash.Serialize(); mmioSize = sMMIO.size(); flashSize = sFLASH.size(); res.insert(res.begin(), sMMIO.begin(), sMMIO.end()); res.insert(res.end(), sFLASH.begin(), sFLASH.end()); res.insert(res.end(), saveData.begin(), saveData.end()); return res; } void Mem::Deserialize(const std::vector &data) { mmio.Deserialize(std::vector(data.begin(), data.begin() + mmioSize)); flash.Deserialize(std::vector(data.begin() + mmioSize, data.begin() + mmioSize + flashSize)); memcpy(saveData.data(), data.data() + mmioSize + flashSize, saveData.size()); } } // namespace n64