#include #include #include #include #include #include #include namespace n64 { Mem::Mem() : flash(saveData) { rom.cart.resize(CART_SIZE); std::ranges::fill(rom.cart, 0); isviewer_sink = std::ofstream("isviewer.log"); } void Mem::Reset() { std::ranges::fill(isviewer, 0); flash.Reset(); if (saveData.is_mapped()) { std::error_code err; saveData.sync(err); if (err) { ircolib::error("[Mem]: Could not sync save data!"); return; } saveData.unmap(); } mmio.Reset(); } void Mem::LoadSRAM(SaveType save_type, fs::path path) { if (save_type == SAVE_SRAM_256k) { std::error_code err; std::string savePath = Options::GetSavesPath(); if (!savePath.empty()) { path = savePath / path.filename(); } sramPath = path.replace_extension(".sram").string(); if (saveData.is_mapped()) { saveData.sync(err); if (err) { ircolib::error(R"([Mem]: Could not sync save data stored @ "{}")", sramPath); return; } saveData.unmap(); } auto sramVec = ircolib::read_file_binary(sramPath); if (sramVec.empty()) { ircolib::write_file_binary(std::array{}, sramPath); sramVec = ircolib::read_file_binary(sramPath); } if (sramVec.size() != SRAM_SIZE) { ircolib::error("[Mem]: Save data is corrupt or has unexpected size! (it's {} KiB)", sramVec.size() / 1024); return; } saveData = mio::make_mmap_sink(sramPath, err); if (err) { ircolib::error(R"([Mem]: Could not create file sink for save data @ "{}")", 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: ircolib::warn("Could not determine CIC TYPE! Checksum: 0x{:08X} is unknown!", checksum); rom.cicType = UNKNOWN_CIC_TYPE; break; } } ROMHeader Mem::ReadROMHeader(bool isArchive, const std::string &filename) { ROMHeader res; u32 endianness; std::vector buf{}; if (isArchive) { buf = Util::ExtractROMHeaderFromArchive(filename); } else { buf = Util::OpenROMHeader(filename); } endianness = std::byteswap(ircolib::read_access(buf, 0)); Util::SwapN64Rom(buf, endianness); memcpy(&res, buf.data(), sizeof(ROMHeader)); res.clockRate = std::byteswap(res.clockRate); res.programCounter = std::byteswap(res.programCounter); res.release = std::byteswap(res.release); res.checkCode = std::byteswap(res.checkCode); res.reserved = std::byteswap(res.reserved); return res; } ROM Mem::LoadROM(const bool isArchive, const std::string &filename) { ROM res; res.cart.resize(CART_SIZE); std::ranges::fill(res.cart, 0); u32 endianness; { size_t sizeAdjusted; std::vector buf{}; if (isArchive) { buf = Util::ExtractROMFromArchive(filename, sizeAdjusted); } else { buf = Util::OpenROM(filename, sizeAdjusted); } endianness = std::byteswap(ircolib::read_access(buf, 0)); Util::SwapN64Rom(buf, endianness); std::ranges::copy(buf, res.cart.begin()); memcpy(&res.header, buf.data(), sizeof(ROMHeader)); } res.header.clockRate = std::byteswap(res.header.clockRate); res.header.programCounter = std::byteswap(res.header.programCounter); res.header.release = std::byteswap(res.header.release); res.header.checkCode = std::byteswap(res.header.checkCode); res.header.reserved = std::byteswap(res.header.reserved); auto [saveType, _, gameNameDB] = GameDB::match(res.header); res.saveType = saveType; if (gameNameDB.empty()) { gameNameDB = fs::path(filename).stem().string(); } res.gameNameDB = gameNameDB; const u32 checksum = SDL_crc32(0, &res.cart[0x40], 0x9C0); SetROMCIC(checksum, res); endianness = std::byteswap(ircolib::read_access(res.cart, 0)); Util::SwapN64Rom(res.cart, endianness); res.pal = IsROMPAL(res.header); return res; } template <> u8 Mem::Read(const u32 paddr) { n64::Registers ®s = n64::Core::GetRegs(); const SI &si = mmio.si; if (ircolib::is_inside_range(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM(paddr); if (ircolib::is_inside_range(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 (ircolib::is_inside_range(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead(paddr); if (ircolib::is_inside_range(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 (ircolib::is_inside_range(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || ircolib::is_inside_range(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) { ircolib::panic("8-bit read access from MMIO addr 0x{:08X} @ pc 0x{:016X}", paddr, (u64)regs.pc); return 0; } if (ircolib::is_inside_range(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return si.pif.bootrom[BYTE_ADDRESS(paddr) - PIF_ROM_REGION_START]; if (ircolib::is_inside_range(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return si.pif.ram[paddr - PIF_RAM_REGION_START]; if (ircolib::is_inside_range(paddr, UNUSED_START_1, UNUSED_END_1) || // unused ircolib::is_inside_range(paddr, UNUSED_START_2, UNUSED_END_2) || ircolib::is_inside_range(paddr, UNUSED_START_3, UNUSED_END_3) || ircolib::is_inside_range(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; ircolib::panic("8-bit read access in unhandled addr 0x{:08X} @ pc 0x{:016X}", paddr, (u64)regs.pc); return 0; } template <> u16 Mem::Read(const u32 paddr) { n64::Registers ®s = n64::Core::GetRegs(); const SI &si = mmio.si; if (ircolib::is_inside_range(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM(paddr); if (ircolib::is_inside_range(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; return ircolib::read_access(src, HALF_ADDRESS(paddr & 0xfff)); } if (ircolib::is_inside_range(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead(paddr); if (ircolib::is_inside_range(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || ircolib::is_inside_range(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr); if (ircolib::is_inside_range(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return ircolib::read_access(si.pif.bootrom, HALF_ADDRESS(paddr) - PIF_ROM_REGION_START); if (ircolib::is_inside_range(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(ircolib::read_access(si.pif.ram, paddr - PIF_RAM_REGION_START)); if (ircolib::is_inside_range(paddr, UNUSED_START_1, UNUSED_END_1) || // unused ircolib::is_inside_range(paddr, UNUSED_START_2, UNUSED_END_2) || ircolib::is_inside_range(paddr, UNUSED_START_3, UNUSED_END_3) || ircolib::is_inside_range(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; ircolib::panic("16-bit read access in unhandled addr 0x{:08X} @ pc 0x{:016X}", paddr, (u64)regs.pc); return 0; } template <> u32 Mem::Read(const u32 paddr) { n64::Registers ®s = n64::Core::GetRegs(); const SI &si = mmio.si; if (ircolib::is_inside_range(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM(paddr); if (ircolib::is_inside_range(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; return ircolib::read_access(src, paddr & 0xfff); } if (ircolib::is_inside_range(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead(paddr); if (ircolib::is_inside_range(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || ircolib::is_inside_range(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr); if (ircolib::is_inside_range(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return ircolib::read_access(si.pif.bootrom, paddr - PIF_ROM_REGION_START); if (ircolib::is_inside_range(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(ircolib::read_access(si.pif.ram, paddr - PIF_RAM_REGION_START)); if (ircolib::is_inside_range(paddr, UNUSED_START_1, UNUSED_END_1) || // unused ircolib::is_inside_range(paddr, UNUSED_START_2, UNUSED_END_2) || ircolib::is_inside_range(paddr, UNUSED_START_3, UNUSED_END_3) || ircolib::is_inside_range(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; ircolib::panic("32-bit read access in unhandled addr 0x{:08X} @ pc 0x{:016X}", paddr, (u64)regs.pc); return 0; } template <> u64 Mem::Read(const u32 paddr) { n64::Registers ®s = n64::Core::GetRegs(); const SI &si = mmio.si; if (ircolib::is_inside_range(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) return mmio.rdp.ReadRDRAM(paddr); if (ircolib::is_inside_range(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { const auto &src = paddr & 0x1000 ? mmio.rsp.imem : mmio.rsp.dmem; return ircolib::read_access(src, paddr & 0xfff); } if (ircolib::is_inside_range(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) return mmio.pi.BusRead(paddr); if (ircolib::is_inside_range(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || ircolib::is_inside_range(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) return mmio.Read(paddr); if (ircolib::is_inside_range(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END)) return ircolib::read_access(si.pif.bootrom, paddr - PIF_ROM_REGION_START); if (ircolib::is_inside_range(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) return std::byteswap(ircolib::read_access(si.pif.ram, paddr - PIF_RAM_REGION_START)); if (ircolib::is_inside_range(paddr, UNUSED_START_1, UNUSED_END_1) || // unused ircolib::is_inside_range(paddr, UNUSED_START_2, UNUSED_END_2) || ircolib::is_inside_range(paddr, UNUSED_START_3, UNUSED_END_3) || ircolib::is_inside_range(paddr, UNUSED_START_4, UNUSED_END_4)) return 0; ircolib::panic("64-bit read access in unhandled addr 0x{:08X} @ pc 0x{:016X}", paddr, (u64)regs.pc); return 0; } template <> void Mem::Write(u32 paddr, u32 val) { n64::Registers ®s = n64::Core::GetRegs(); SI &si = mmio.si; if (ircolib::is_inside_range(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM(paddr, val); return; } if (ircolib::is_inside_range(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { val = val << (8 * (3 - (paddr & 3))); bool is_imem = paddr & 0x1000; auto &dest = is_imem ? mmio.rsp.imem : mmio.rsp.dmem; paddr = (paddr & 0xFFF) & ~3; ircolib::write_access(dest, paddr, val); return; } if (ircolib::is_inside_range(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) { ircolib::trace("BusWrite @ {:08X} = {:02X}", paddr, val); mmio.pi.BusWrite(paddr, val); return; } if (ircolib::is_inside_range(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || ircolib::is_inside_range(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) ircolib::panic("MMIO Write!"); if (ircolib::is_inside_range(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) { val = val << (8 * (3 - (paddr & 3))); paddr = (paddr - PIF_RAM_REGION_START) & ~3; ircolib::write_access(si.pif.ram, paddr, std::byteswap(val)); si.pif.ProcessCommands(); return; } if (ircolib::is_inside_range(paddr, UNUSED_START_1, UNUSED_END_1) || // unused ircolib::is_inside_range(paddr, UNUSED_START_2, UNUSED_END_2) || ircolib::is_inside_range(paddr, UNUSED_START_3, UNUSED_END_3) || ircolib::is_inside_range(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) || ircolib::is_inside_range(paddr, UNUSED_START_4, UNUSED_END_4)) return; ircolib::panic("Unimplemented 8-bit write at address {:08X} with value {:02X} (PC = {:016X})", paddr, val, (u64)regs.pc); } template <> void Mem::Write(u32 paddr, u32 val) { n64::Registers ®s = n64::Core::GetRegs(); SI &si = mmio.si; if (ircolib::is_inside_range(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM(paddr, val); return; } if (ircolib::is_inside_range(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { val = val << (16 * !(paddr & 2)); bool is_imem = paddr & 0x1000; auto &dest = is_imem ? mmio.rsp.imem : mmio.rsp.dmem; paddr = (paddr & 0xFFF) & ~3; ircolib::write_access(dest, paddr, val); return; } if (ircolib::is_inside_range(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) { ircolib::trace("BusWrite @ {:08X} = {:04X}", paddr, val); mmio.pi.BusWrite(paddr, val); return; } if (ircolib::is_inside_range(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || ircolib::is_inside_range(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) ircolib::panic("MMIO Write!"); if (ircolib::is_inside_range(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) { val = val << (16 * !(paddr & 2)); paddr &= ~3; ircolib::write_access(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val)); si.pif.ProcessCommands(); return; } if (ircolib::is_inside_range(paddr, UNUSED_START_1, UNUSED_END_1) || // unused ircolib::is_inside_range(paddr, UNUSED_START_2, UNUSED_END_2) || ircolib::is_inside_range(paddr, UNUSED_START_3, UNUSED_END_3) || ircolib::is_inside_range(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) || ircolib::is_inside_range(paddr, UNUSED_START_4, UNUSED_END_4)) return; ircolib::panic("Unimplemented 16-bit write at address {:08X} with value {:04X} (PC = {:016X})", paddr, val, (u64)regs.pc); } template <> void Mem::Write(const u32 paddr, const u32 val) { n64::Registers ®s = n64::Core::GetRegs(); SI &si = mmio.si; if (ircolib::is_inside_range(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM(paddr, val); return; } if (ircolib::is_inside_range(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { bool is_imem = paddr & 0x1000; auto &dest = is_imem ? mmio.rsp.imem : mmio.rsp.dmem; ircolib::write_access(dest, paddr & 0xfff, val); return; } if (ircolib::is_inside_range(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) { ircolib::trace("BusWrite @ {:08X} = {:08X}", paddr, val); mmio.pi.BusWrite(paddr, val); return; } if (ircolib::is_inside_range(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || ircolib::is_inside_range(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) { mmio.Write(paddr, val); return; } if (ircolib::is_inside_range(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) { ircolib::write_access(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val)); si.pif.ProcessCommands(); return; } if (ircolib::is_inside_range(paddr, UNUSED_START_1, UNUSED_END_1) || // unused ircolib::is_inside_range(paddr, UNUSED_START_2, UNUSED_END_2) || ircolib::is_inside_range(paddr, UNUSED_START_3, UNUSED_END_3) || ircolib::is_inside_range(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) || ircolib::is_inside_range(paddr, UNUSED_START_4, UNUSED_END_4)) return; ircolib::panic("Unimplemented 32-bit write at address {:08X} with value {:08X} (PC = {:016X})", paddr, val, (u64)regs.pc); } void Mem::Write(const u32 paddr, u64 val) { n64::Registers ®s = n64::Core::GetRegs(); SI &si = mmio.si; if (ircolib::is_inside_range(paddr, RDRAM_REGION_START, RDRAM_REGION_END)) { mmio.rdp.WriteRDRAM(paddr, val); return; } if (ircolib::is_inside_range(paddr, DMEM_REGION_START, RSP_MEM_REGION_END)) { bool is_imem = paddr & 0x1000; auto &dest = is_imem ? mmio.rsp.imem : mmio.rsp.dmem; val >>= 32; ircolib::write_access(dest, paddr & 0xfff, val); return; } if (ircolib::is_inside_range(paddr, CART_REGION_START_2_1, CART_REGION_END_1_2)) { ircolib::trace("BusWrite @ {:08X} = {:016X}", paddr, val); mmio.pi.BusWrite(paddr, val); return; } if (ircolib::is_inside_range(paddr, MMIO_REGION_START_1, MMIO_REGION_END_1) || ircolib::is_inside_range(paddr, MMIO_REGION_START_2, MMIO_REGION_END_2)) ircolib::panic("MMIO Write!"); if (ircolib::is_inside_range(paddr, PIF_RAM_REGION_START, PIF_RAM_REGION_END)) { ircolib::write_access(si.pif.ram, paddr - PIF_RAM_REGION_START, std::byteswap(val)); si.pif.ProcessCommands(); return; } if (ircolib::is_inside_range(paddr, UNUSED_START_1, UNUSED_END_1) || // unused ircolib::is_inside_range(paddr, UNUSED_START_2, UNUSED_END_2) || ircolib::is_inside_range(paddr, UNUSED_START_3, UNUSED_END_3) || ircolib::is_inside_range(paddr, PIF_ROM_REGION_START, PIF_ROM_REGION_END) || ircolib::is_inside_range(paddr, UNUSED_START_4, UNUSED_END_4)) return; ircolib::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 (rom.saveType) { case SAVE_NONE: return 0; case SAVE_EEPROM_4k: case SAVE_EEPROM_16k: ircolib::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: ircolib::panic("Backup read word with unknown save type"); } } template <> u8 Mem::BackupRead(const u32 addr) { switch (rom.saveType) { case SAVE_NONE: return 0; case SAVE_EEPROM_4k: case SAVE_EEPROM_16k: ircolib::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 { ircolib::panic("Invalid backup Read if save data is not initialized"); } default: ircolib::panic("Backup read word with unknown save type"); } } template <> void Mem::BackupWrite(const u32 addr, const u32 val) { switch (rom.saveType) { case SAVE_NONE: ircolib::warn("Accessing cartridge with save type SAVE_NONE in write word"); break; case SAVE_EEPROM_4k: case SAVE_EEPROM_16k: ircolib::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: ircolib::panic("Backup read word with unknown save type"); } } template <> void Mem::BackupWrite(const u32 addr, const u8 val) { switch (rom.saveType) { case SAVE_NONE: ircolib::warn("Accessing cartridge with save type SAVE_NONE in write word"); break; case SAVE_EEPROM_4k: case SAVE_EEPROM_16k: ircolib::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 { ircolib::panic("Invalid backup Write if save data is not initialized"); } break; default: ircolib::panic("Backup read word with unknown save type"); } } } // namespace n64