// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ /* RAM --- 0x00100000-0x01ffffff this is the physical address for the ram.its cached there 0x20100000-0x21ffffff uncached 0x30100000-0x31ffffff uncached & accelerated 0xa0000000-0xa1ffffff MIRROR might...??? 0x80000000-0x81ffffff MIRROR might... ???? scratch pad ---------- 0x70000000-0x70003fff scratch pad BIOS ---- 0x1FC00000 - 0x1FFFFFFF un-cached 0x9FC00000 - 0x9FFFFFFF cached 0xBFC00000 - 0xBFFFFFFF un-cached */ #include "DEV9/DEV9.h" #include "IopHw.h" #include "GS/Renderers/Common/GSFunctionMap.h" #include "GS.h" #include "Host.h" #include "MTVU.h" #include "SPU2/spu2.h" #include "SaveState.h" #include "VUmicro.h" #include "ps2/HwInternal.h" #include "ps2/BiosTools.h" #include "common/AlignedMalloc.h" #include "common/Error.h" #include #include #ifdef __linux__ #include #endif #ifdef ENABLECACHE #include "Cache.h" #endif #ifdef __APPLE__ #include "common/Darwin/DarwinMisc.h" #include #endif namespace Ps2MemSize { u32 ExposedRam = MainRam; u32 ExposedIopRam = IopRam; } // namespace Ps2MemSize namespace SysMemory { static bool AllocateMemoryMap(); static void DumpMemoryMap(); static void ReleaseMemoryMap(); static u8* s_data_memory; static void* s_data_memory_file_handle; static u8* s_code_memory; static std::unique_ptr s_memory_mapping_area; static std::unique_ptr s_code_mapping_area; } // namespace SysMemory static void memAllocate(); static void memReset(); static void memRelease(); int MemMode = 0; // 0 is Kernel Mode, 1 is Supervisor Mode, 2 is User Mode static u16 s_ba[0xff]; static u16 s_dve_regs[0xff]; static bool s_ba_command_executing = false; static bool s_ba_error_detected = false; static u16 s_ba_current_reg = 0; static bool s_extra_memory = false; namespace HostMemoryMap { // For debuggers extern "C" { #ifdef _WIN32 _declspec(dllexport) uptr EEmem, IOPmem, VUmem; #else __attribute__((visibility("default"), used)) uptr EEmem, IOPmem, VUmem; #endif } } // namespace HostMemoryMap bool SysMemory::AllocateMemoryMap() { s_data_memory_file_handle = HostSys::CreateSharedMemory(HostSys::GetFileMappingName("pcsx2").c_str(), HostMemoryMap::MainSize); if (!s_data_memory_file_handle) { Host::ReportErrorAsync("Error", "Failed to create shared memory file."); ReleaseMemoryMap(); return false; } // Constant-VA placement for the on-disk VU program cache: on arm64 the // data + code reservations must sit at the same VAs every run so cached // JIT code reloads without repatching its baked addresses. 4GB clears the // ASLR brk window (non-PIE image at 0x400000 + brk randomization < 2GB) and // sits far below the mmap_base / PIE-load regions, so the slot-0 candidate // succeeds deterministically; Create() walks 256MB-stride fallback slots and // finally kernel placement (program-cache misses, never corruption). Other // arches pass 0 and take kernel-chosen placement. The code area is hinted // directly after the data area, reproducing a contiguous arena when both land. #if defined(__aarch64__) || defined(_M_ARM64) constexpr uptr kArenaBase = 0x100000000ull; // 4GB #else constexpr uptr kArenaBase = 0; #endif Console.WriteLn("@@MAC_MEMMAP@@ data_area_begin size=%zu", static_cast(HostMemoryMap::MainSize)); if (!(s_memory_mapping_area = SharedMemoryMappingArea::Create(HostMemoryMap::MainSize, false, kArenaBase))) { Host::ReportErrorAsync("Error", "Failed to map main memory."); ReleaseMemoryMap(); return false; } if ((s_data_memory = s_memory_mapping_area->Map(s_data_memory_file_handle, 0, s_memory_mapping_area->BasePointer(), HostMemoryMap::MainSize, PageAccess_ReadWrite())) == nullptr) { Host::ReportErrorAsync("Error", "Failed to map data memory."); ReleaseMemoryMap(); return false; } Console.WriteLn("@@MAC_MEMMAP@@ code_area_begin size=%zu", static_cast(HostMemoryMap::CodeSize)); #if defined(__APPLE__) && TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR // [iOS] Code is allocated separately via DarwinMisc dual-mapping (W^X RW/RX aliases), // not through SharedMemoryMappingArea. iOS rejects PROT_NONE MAP_JIT and MAP_FIXED+MAP_JIT, // so the generic SharedMemoryMappingArea code path is unusable here. // // In forced-interpreter mode (JIT unavailable/expired), skip executable code // allocation entirely. The interpreter does not generate native code, and // intCpu::Reserve() never touches s_code_memory. if (!DarwinMisc::iPSX2_FORCE_EE_INTERP) { if ((s_code_memory = static_cast(DarwinMisc::MmapCodeDualMap(HostMemoryMap::CodeSize))) == nullptr) { std::fprintf(stderr, "@@BOOT_FAIL@@ reason=ios_code_alloc_failed stage=code_dualmap\n"); std::fflush(stderr); Host::ReportErrorAsync("Error", "Failed to allocate iOS executable code memory. " "Try Settings \u2192 Emulator \u2192 JIT Script \u2192 Legacy, or relaunch via StikDebug."); ReleaseMemoryMap(); return false; } Console.WriteLn("@@P43_OFFSET@@ g_code_rw_offset=%ld rw_base=%p size=%zu", (long)DarwinMisc::g_code_rw_offset, reinterpret_cast(DarwinMisc::g_code_rw_base), static_cast(DarwinMisc::g_code_rw_size)); } else { Console.WriteLn("[iOS] Skipping code-memory allocation \u2014 interpreter-only mode (iPSX2_FORCE_EE_INTERP=1)"); s_code_memory = nullptr; } #else #ifdef __APPLE__ // [jit-transplant] CI-only test hook: ARMSX2_FORCE_DUAL_MAP=1 routes macOS // through the iOS dual-map allocator (vm_remap RW alias, g_code_rw_offset // != 0) so the recompiler test suite exercises every RW-alias write path // without an iOS device. Production macOS takes the SharedMemoryMappingArea // MAP_JIT path below, unchanged. const char* const force_dual_map = std::getenv("ARMSX2_FORCE_DUAL_MAP"); if (force_dual_map && std::atoi(force_dual_map) == 1) { if ((s_code_memory = static_cast(DarwinMisc::MmapCodeDualMap(HostMemoryMap::CodeSize))) == nullptr) { Host::ReportErrorAsync("Error", "Failed to allocate forced dual-map code memory."); ReleaseMemoryMap(); return false; } } else #endif { if (!(s_code_mapping_area = SharedMemoryMappingArea::Create(HostMemoryMap::CodeSize, true, kArenaBase ? kArenaBase + HostMemoryMap::MainSize : 0))) { Host::ReportErrorAsync("Error", "Failed to map code memory."); ReleaseMemoryMap(); return false; } if ((s_code_memory = s_code_mapping_area->Map(nullptr, 0, s_code_mapping_area->BasePointer(), HostMemoryMap::CodeSize, PageAccess_Any())) == nullptr) { Host::ReportErrorAsync("Error", "Failed to allocate code memory."); ReleaseMemoryMap(); return false; } } #endif #ifdef __linux__ // FX-15 (design credit FEX-Emu): back the hot JIT code caches with // transparent hugepages to cut iTLB pressure. madvise is what the // Rocknix default THP mode ("madvise") honors, and the code half is a // private anonymous mapping, which is what THP backs. Scoped to the // EE+IOP and mVU0+mVU1 rec caches — each pair contiguous in the map — // leaving the VIF/SW-renderer tail alone. A/B off-arm: launch under // prctl(PR_SET_THP_DISABLE) (see tools/perf/fx15_thp_ab.sh) — it // survives execve, so no in-tree gate is needed. static_assert(HostMemoryMap::IOPrecOffset == HostMemoryMap::EErecOffset + HostMemoryMap::EErecSize); static_assert(HostMemoryMap::mVU1recOffset == HostMemoryMap::mVU0recOffset + HostMemoryMap::mVU0recSize); madvise(s_code_memory + HostMemoryMap::EErecOffset, HostMemoryMap::EErecSize + HostMemoryMap::IOPrecSize, MADV_HUGEPAGE); madvise(s_code_memory + HostMemoryMap::mVU0recOffset, HostMemoryMap::mVU0recSize + HostMemoryMap::mVU1recSize, MADV_HUGEPAGE); #endif HostMemoryMap::EEmem = (uptr)(s_data_memory + HostMemoryMap::EEmemOffset); HostMemoryMap::IOPmem = (uptr)(s_data_memory + HostMemoryMap::IOPmemOffset); HostMemoryMap::VUmem = (uptr)(s_data_memory + HostMemoryMap::VUmemOffset); #ifdef __APPLE__ DarwinMisc::SetJitRange(s_code_memory, HostMemoryMap::CodeSize); Console.WriteLn("@@P43_OFFSET@@ g_code_rw_offset=%ld", (long)DarwinMisc::g_code_rw_offset); #endif DumpMemoryMap(); return true; } void SysMemory::DumpMemoryMap() { #define DUMP_REGION(name, base, offset, size) \ DevCon.WriteLn(Color_Gray, " %-32s @ 0x%016" PRIXPTR " -> 0x%016" PRIXPTR " %s", name, \ (uptr)(base + offset), (uptr)(base + offset + size), fmt::format("[{}mb]", size / _1mb).c_str()); DUMP_REGION("EE Main Memory", s_data_memory, HostMemoryMap::EEmemOffset, HostMemoryMap::EEmemSize); DUMP_REGION("IOP Main Memory", s_data_memory, HostMemoryMap::IOPmemOffset, HostMemoryMap::IOPmemSize); DUMP_REGION("VU0/1 On-Chip Memory", s_data_memory, HostMemoryMap::VUmemOffset, HostMemoryMap::VUmemSize); DUMP_REGION("VTLB Virtual Map", s_data_memory, HostMemoryMap::VTLBVirtualMapOffset, HostMemoryMap::VTLBVirtualMapSize); DUMP_REGION("VTLB Address Map", s_data_memory, HostMemoryMap::VTLBAddressMapOffset, HostMemoryMap::VTLBAddressMapSize); DUMP_REGION("R5900 Recompiler Cache", s_code_memory, HostMemoryMap::EErecOffset, HostMemoryMap::EErecSize); DUMP_REGION("R3000A Recompiler Cache", s_code_memory, HostMemoryMap::IOPrecOffset, HostMemoryMap::IOPrecSize); DUMP_REGION("Micro VU0 Recompiler Cache", s_code_memory, HostMemoryMap::mVU0recOffset, HostMemoryMap::mVU0recSize); DUMP_REGION("Micro VU1 Recompiler Cache", s_code_memory, HostMemoryMap::mVU1recOffset, HostMemoryMap::mVU1recSize); DUMP_REGION("VIF0 Unpack Recompiler Cache", s_code_memory, HostMemoryMap::VIF0recOffset, HostMemoryMap::VIF0recSize); DUMP_REGION("VIF1 Unpack Recompiler Cache", s_code_memory, HostMemoryMap::VIF1recOffset, HostMemoryMap::VIF1recSize); DUMP_REGION("VIF Unpack Recompiler Cache", s_code_memory, HostMemoryMap::VIFUnpackRecOffset, HostMemoryMap::VIFUnpackRecSize); DUMP_REGION("GS Software Renderer", s_code_memory, HostMemoryMap::SWrecOffset, HostMemoryMap::SWrecSize); #undef DUMP_REGION } void SysMemory::ReleaseMemoryMap() { if (s_code_memory) { #if defined(__APPLE__) && TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR DarwinMisc::MunmapCodeDualMap(s_code_memory, HostMemoryMap::CodeSize); #else if (s_code_mapping_area) s_code_mapping_area->Unmap(s_code_memory, HostMemoryMap::CodeSize, false); #ifdef __APPLE__ else // macOS ARMSX2_FORCE_DUAL_MAP test hook allocated via MmapCodeDualMap. DarwinMisc::MunmapCodeDualMap(s_code_memory, HostMemoryMap::CodeSize); #endif #endif s_code_memory = nullptr; } s_code_mapping_area.reset(); if (s_data_memory) { if (s_memory_mapping_area) s_memory_mapping_area->Unmap(s_data_memory, HostMemoryMap::MainSize, true); s_data_memory = nullptr; } s_memory_mapping_area.reset(); if (s_data_memory_file_handle) { HostSys::DestroySharedMemory(s_data_memory_file_handle); s_data_memory_file_handle = nullptr; } } void SysMemory::ReserveMemory() { // Claim the host memory map (and the arm64 constant-VA arena) up front, so // the fixed-base placement isn't lost to an intervening heap/mmap. Idempotent. if (!s_data_memory_file_handle) AllocateMemoryMap(); } bool SysMemory::Allocate() { DevCon.WriteLn(Color_StrongBlue, "Allocating host memory for virtual systems..."); if (!s_data_memory_file_handle && !AllocateMemoryMap()) return false; memAllocate(); iopMemAlloc(); vuMemAllocate(); if (!vtlb_Core_Alloc()) { // Clean up already-allocated memory so the destructor doesn't assert // ("No mappings left") on a failed boot path. vuMemRelease(); iopMemRelease(); memRelease(); ReleaseMemoryMap(); return false; } return true; } void SysMemory::Reset() { DevCon.WriteLn(Color_StrongBlue, "Resetting host memory for virtual systems..."); memReset(); iopMemReset(); vuMemReset(); // Note: newVif is reset as part of other VIF structures. // Software is reset on the GS thread. } void SysMemory::Release() { Console.WriteLn(Color_Blue, "Releasing host memory for virtual systems..."); vtlb_Core_Free(); // Just to be sure... (calling order could result in it getting missed during Decommit). vuMemRelease(); iopMemRelease(); memRelease(); ReleaseMemoryMap(); } u8* SysMemory::GetDataPtr(size_t offset) { pxAssert(offset <= HostMemoryMap::MainSize); return s_data_memory + offset; } u8* SysMemory::GetCodePtr(size_t offset) { pxAssert(offset <= HostMemoryMap::CodeSize); return s_code_memory + offset; } void* SysMemory::GetDataFileHandle() { return s_data_memory_file_handle; } bool memGetExtraMemMode() { return s_extra_memory; } void memSetExtraMemMode(bool mode) { #ifdef ARCH_ARM64 // The ARM64 EE recompiler is MainRam-only: its LUT loop, recLutEntries, the // recRAM advance, the alias mask and the manual_page/manual_counter arrays are // all sized to Ps2MemSize::MainRam, where the x86 rec sizes the same things to // ExposedRam. Pages 0x0200-0x1FFF therefore keep the unmapped default, and // dispatching into one lands on UnmappedRecLUTPage -> recError. Converting all // of them together is real work and has to happen as one change (c4d0a8a47c // spells out why); until it does, refuse the setting at the seam rather than // let a user-selectable option fail as a recError deep inside a game. The // interpreter handles the 128MB map fine, so gate on the recompiler only. if (mode && EmuConfig.Cpu.Recompiler.EnableEE) { Console.Warning("Extended RAM (128MB) is not supported by the ARM64 EE recompiler; ignoring it. " "Disable the EE recompiler if you need it."); mode = false; } #endif s_extra_memory = mode; // update the amount of RAM exposed to the VM Ps2MemSize::ExposedRam = mode ? Ps2MemSize::TotalRam : Ps2MemSize::MainRam; Ps2MemSize::ExposedIopRam = mode ? Ps2MemSize::TotalIopRam: Ps2MemSize::IopRam; } void memSetKernelMode() { //Do something here MemMode = 0; } void memSetSupervisorMode() { } void memSetUserMode() { } // These regs are related to DEV9 and DVE stuff, we don't have to go crazy with this, but this sucks less than the original code void ba0W16(u32 mem, u16 value) { //MEM_LOG("ba000000 Memory write16 address %x value %x", mem, value); u32 masked_mem = (mem & 0xFF); if (masked_mem == 0x6) // Status Reg { s_ba[0x6] &= ~3; } else s_ba[masked_mem] = value; if (masked_mem == 0x00) // Command Execute Reg { if (s_ba[0x2] == 0x4F || s_ba[0x2] == 0x41) { DevCon.Warning("Error running DVE command, Control Reg value set to %x", value); s_ba_error_detected = true; } else if (s_ba[masked_mem] & 0x80) // Start executing { if (s_ba[0x2] == 0x43) // Write Mode { int size = (s_ba[masked_mem] & 0xF); s_ba_current_reg = s_ba[0x10]; size--; // 0x10->0x22 seems to be some sort of FIFO, with 0x10 generally being the register to read/write for (int i = 0; i < size; i++) { s_dve_regs[s_ba_current_reg] = s_ba[0x12 + i]; } s_ba_command_executing = true; s_ba_error_detected = false; } else if (s_ba[0x2] == 0x42) // Read Mode { int size = (s_ba[masked_mem] & 0xF); for (int i = 0; i < size; i++) s_ba[0x10 + i] = s_dve_regs[s_ba_current_reg]; // Probably not right but we don't access the real regs, will be enough for now. s_ba_command_executing = true; s_ba_error_detected = false; } } } else if (masked_mem == 0xA) // Power/Standby (?) Reg { if (value == 0) s_ba_error_detected = true; else s_ba_error_detected = false; DevCon.Warning("DVE powered %s", value == 0 ? "off" : "on"); } } u16 ba0R16(u32 mem) { //MEM_LOG("ba000000 Memory read16 address %x", mem); if (mem == 0x1a000006) { // 0xba00000A bit 0 is kind of an "on" switch. bit 0 of ba000006 seems to be the powered off/error bit. // bit 1 in ba000006 seems to be "ready". u16 return_val = (s_ba[0x6] & 2); if (s_ba_error_detected) return_val |= 1; if (s_ba[0x6] < 3 && s_ba_command_executing) s_ba[0x6]++; else s_ba_command_executing = false; return return_val; } return s_ba[mem & 0x1F]; } #define CHECK_MEM(mem) //MyMemCheck(mem) void MyMemCheck(u32 mem) { if ( mem == 0x1c02f2a0 ) Console.WriteLn("yo; (mem == 0x1c02f2a0) in MyMemCheck..."); } ///////////////////////////// // REGULAR MEM START ///////////////////////////// static vtlbHandler null_handler, tlb_fallback_0, tlb_fallback_2, tlb_fallback_3, tlb_fallback_4, tlb_fallback_5, tlb_fallback_6, tlb_fallback_7, tlb_fallback_8, vu0_micro_mem, vu1_micro_mem, vu1_data_mem, hw_by_page[0x10] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, gs_page_0, gs_page_1, iopHw_by_page_01, iopHw_by_page_03, iopHw_by_page_08, iop_memory; void memMapVUmicro() { // VU0/VU1 micro mem (instructions) // (Like IOP memory, these are generally only used by the EE Bios kernel during // boot-up. Applications/games are "supposed" to use the thread-safe VIF instead; // or must ensure all VIF/GIF transfers are finished and all VUmicro execution stopped // prior to accessing VU memory directly). // The VU0 mapping actually repeats 4 times across the mapped range, but we don't bother // to manually mirror it here because the indirect memory handler for it (see vuMicroRead* // functions below) automatically mask and wrap the address for us. vtlb_MapHandler(vu0_micro_mem,0x11000000,0x00004000); vtlb_MapHandler(vu1_micro_mem,0x11008000,0x00004000); // VU0/VU1 memory (data) // VU0 is 4k, mirrored 4 times across a 16k area. vtlb_MapBlock(VU0.Mem,0x11004000,0x00004000,0x1000); // Note: In order for the below conditional to work correctly // support needs to be coded to reset the memMappings when MTVU is // turned off/on. For now we just always use the vu data handlers... if (1||THREAD_VU1) vtlb_MapHandler(vu1_data_mem,0x1100c000,0x00004000); else vtlb_MapBlock (VU1.Mem, 0x1100c000,0x00004000); } void memMapPhy() { // Main memory vtlb_MapBlock(eeMem->Main, 0x00000000,Ps2MemSize::ExposedRam);//mirrored on first 256 mb ? // High memory, uninstalled on the configuration we emulate vtlb_MapHandler(null_handler, Ps2MemSize::ExposedRam, 0x10000000 - Ps2MemSize::ExposedRam); // Physical RAM mirrors used by BIOS InitRDRAM for RDRAM device configuration. // On real PS2 hardware: // 0x20000000-0x21FFFFFF = uncached mirror of main RAM // 0x30000000-0x31FFFFFF = uncached & accelerated mirror of main RAM // These mirrors must be present in the physical map; without them, BIOS writes // to RDRAM device registers hit UnmappedPhyHandler (bus error). // Requires VTLB_PMAP_SZ >= 1GB to cover these addresses. vtlb_MapBlock(eeMem->Main, 0x20000000, Ps2MemSize::ExposedRam); vtlb_MapBlock(eeMem->Main, 0x30000000, Ps2MemSize::ExposedRam); // Various ROMs (all read-only) vtlb_MapBlock(eeMem->ROM, 0x1fc00000, Ps2MemSize::Rom); vtlb_MapBlock(eeMem->ROM1, 0x1e000000, Ps2MemSize::Rom1); vtlb_MapBlock(eeMem->ROM2, 0x1e400000, Ps2MemSize::Rom2); // IOP memory // (used by the EE Bios Kernel during initial hardware initialization, Apps/Games // are "supposed" to use the thread-safe SIF instead.) vtlb_MapHandler(iop_memory,0x1c000000,0x00800000); // Generic Handlers; These fallback to mem* stuff... vtlb_MapHandler(tlb_fallback_7,0x14000000, _64kb); vtlb_MapHandler(tlb_fallback_4,0x18000000, _64kb); vtlb_MapHandler(tlb_fallback_5,0x1a000000, _64kb); vtlb_MapHandler(tlb_fallback_6,0x12000000, _64kb); vtlb_MapHandler(tlb_fallback_8,0x1f000000, _64kb); vtlb_MapHandler(tlb_fallback_3,0x1f400000, _64kb); vtlb_MapHandler(tlb_fallback_2,0x1f800000, _64kb); vtlb_MapHandler(tlb_fallback_8,0x1f900000, _64kb); // Hardware Register Handlers : specialized/optimized per-page handling of HW register accesses // (note that hw_by_page handles are assigned in memReset prior to calling this function) for( uint i=0; i<16; ++i) vtlb_MapHandler(hw_by_page[i], 0x10000000 + (0x01000 * i), 0x01000); vtlb_MapHandler(gs_page_0, 0x12000000, 0x01000); vtlb_MapHandler(gs_page_1, 0x12001000, 0x01000); // "Secret" IOP HW mappings - Used by EE Bios Kernel during boot and generally // left untouched after that, as per EE/IOP thread safety rules. vtlb_MapHandler(iopHw_by_page_01, 0x1f801000, 0x01000); vtlb_MapHandler(iopHw_by_page_03, 0x1f803000, 0x01000); vtlb_MapHandler(iopHw_by_page_08, 0x1f808000, 0x01000); } //Why is this required ? void memMapKernelMem() { //lower 512 mb: direct map //vtlb_VMap(0x00000000,0x00000000,0x20000000); //0x8* mirror vtlb_VMap(0x80000000, 0x00000000, _1mb*512); //0xa* mirror vtlb_VMap(0xA0000000, 0x00000000, _1mb*512); } //what do do with these ? void memMapSupervisorMem() { } void memMapUserMem() { } static mem8_t nullRead8(u32 mem) { MEM_LOG("Read uninstalled memory at address %08x", mem); return 0; } static mem16_t nullRead16(u32 mem) { MEM_LOG("Read uninstalled memory at address %08x", mem); return 0; } static mem32_t nullRead32(u32 mem) { MEM_LOG("Read uninstalled memory at address %08x", mem); return 0; } static mem64_t nullRead64(u32 mem) { MEM_LOG("Read uninstalled memory at address %08x", mem); return 0; } static RETURNS_R128 nullRead128(u32 mem) { MEM_LOG("Read uninstalled memory at address %08x", mem); return r128_zero(); } static void nullWrite8(u32 mem, mem8_t value) { MEM_LOG("Write uninstalled memory at address %08x", mem); } static void nullWrite16(u32 mem, mem16_t value) { MEM_LOG("Write uninstalled memory at address %08x", mem); } static void nullWrite32(u32 mem, mem32_t value) { MEM_LOG("Write uninstalled memory at address %08x", mem); } static void nullWrite64(u32 mem, mem64_t value) { MEM_LOG("Write uninstalled memory at address %08x", mem); } static void TAKES_R128 nullWrite128(u32 mem, r128 value) { MEM_LOG("Write uninstalled memory at address %08x", mem); } template static mem8_t _ext_memRead8 (u32 mem) { switch (p) { case 3: // psh4 return psxHw4Read8(mem); case 6: // gsm return gsRead8(mem); case 7: // dev9 { mem8_t retval = DEV9read8(mem & ~0xa4000000); Console.WriteLn("DEV9 read8 %8.8lx: %2.2lx", mem & ~0xa4000000, retval); return retval; } case 9: return iopMemRead8(mem & ~0x1c000000); default: break; } MEM_LOG("Unknown Memory Read8 from address %8.8x", mem); cpuTlbMissR(mem, cpuRegs.branch); return 0; } template static mem16_t _ext_memRead16(u32 mem) { switch (p) { case 4: // b80 MEM_LOG("b800000 Memory read16 address %x", mem); return 0; case 5: // ba0 MEM_LOG("ba000000 Memory read16 address %x", mem); return ba0R16(mem); case 6: // gsm return gsRead16(mem); case 7: // dev9 { mem16_t retval = DEV9read16(mem & ~0xa4000000); Console.WriteLn("DEV9 read16 %8.8lx: %4.4lx", mem & ~0xa4000000, retval); return retval; } case 8: // spu2 return SPU2read(mem); case 9: return iopMemRead16(mem & ~0x1c000000); default: break; } MEM_LOG("Unknown Memory read16 from address %8.8x", mem); cpuTlbMissR(mem, cpuRegs.branch); return 0; } template static mem32_t _ext_memRead32(u32 mem) { switch (p) { case 6: // gsm return gsRead32(mem); case 7: // dev9 { mem32_t retval = DEV9read32(mem & ~0xa4000000); Console.WriteLn("DEV9 read32 %8.8lx: %8.8lx", mem & ~0xa4000000, retval); return retval; } case 9: return iopMemRead32(mem & ~0x1c000000); default: break; } MEM_LOG("Unknown Memory read32 from address %8.8x (Status=%8.8x)", mem, cpuRegs.CP0.n.Status.val); cpuTlbMissR(mem, cpuRegs.branch); return 0; } template static u64 _ext_memRead64(u32 mem) { switch (p) { case 6: // gsm return gsRead64(mem); case 9: { u64 ret = 0; ret |= ((u64)(iopMemRead32((mem + 0) & ~0x1c000000)) << 0); ret |= ((u64)(iopMemRead32((mem + 4) & ~0x1c000000)) << 32); return ret; } default: break; } MEM_LOG("Unknown Memory read64 from address %8.8x", mem); cpuTlbMissR(mem, cpuRegs.branch); return 0; } template static RETURNS_R128 _ext_memRead128(u32 mem) { switch (p) { //case 1: // hwm // return hwRead128(mem & ~0xa0000000); case 6: // gsm return r128_load(PS2GS_BASE(mem)); case 9: { u128 ret = {}; ret._u32[0] = iopMemRead32((mem + 0) & ~0x1c000000); ret._u32[1] = iopMemRead32((mem + 4) & ~0x1c000000); ret._u32[2] = iopMemRead32((mem + 8) & ~0x1c000000); ret._u32[3] = iopMemRead32((mem + 12) & ~0x1c000000); return r128_from_u128(ret); } default: break; } MEM_LOG("Unknown Memory read128 from address %8.8x", mem); cpuTlbMissR(mem, cpuRegs.branch); return r128_zero(); } template static void _ext_memWrite8 (u32 mem, mem8_t value) { switch (p) { case 3: // psh4 psxHw4Write8(mem, value); return; case 6: // gsm gsWrite8(mem, value); return; case 7: // dev9 DEV9write8(mem & ~0xa4000000, value); Console.WriteLn("DEV9 write8 %8.8lx: %2.2lx", mem & ~0xa4000000, value); return; case 9: iopMemWrite8(mem & ~0x1c000000, value); return; default: break; } MEM_LOG("Unknown Memory write8 to address %x with data %2.2x", mem, value); cpuTlbMissW(mem, cpuRegs.branch); } template static void _ext_memWrite16(u32 mem, mem16_t value) { switch (p) { case 5: // ba0 MEM_LOG("ba000000 Memory write16 address %x value %x", mem, value); ba0W16(mem, value); return; case 6: // gsm gsWrite16(mem, value); return; case 7: // dev9 DEV9write16(mem & ~0xa4000000, value); Console.WriteLn("DEV9 write16 %8.8lx: %4.4lx", mem & ~0xa4000000, value); return; case 8: // spu2 SPU2write(mem, value); return; case 9: iopMemWrite16(mem & ~0x1c000000, value); return; default: break; } MEM_LOG("Unknown Memory write16 to address %x with data %4.4x", mem, value); cpuTlbMissW(mem, cpuRegs.branch); } template static void _ext_memWrite32(u32 mem, mem32_t value) { switch (p) { case 6: // gsm gsWrite32(mem, value); return; case 7: // dev9 DEV9write32(mem & ~0xa4000000, value); Console.WriteLn("DEV9 write32 %8.8lx: %8.8lx", mem & ~0xa4000000, value); return; case 9: iopMemWrite32(mem & ~0x1c000000, value); return; default: break; } MEM_LOG("Unknown Memory write32 to address %x with data %8.8x", mem, value); cpuTlbMissW(mem, cpuRegs.branch); } template static void _ext_memWrite64(u32 mem, mem64_t value) { switch (p) { case 9: iopMemWrite32((mem + 0) & ~0x1c000000, (value >> 0) & 0xffffffff); iopMemWrite32((mem + 4) & ~0x1c000000, (value >> 32) & 0xffffffff); return; default: break; } /*switch (p) { //case 1: // hwm // hwWrite64(mem & ~0xa0000000, *value); // return; //case 6: // gsm // gsWrite64(mem & ~0xa0000000, *value); return; }*/ MEM_LOG("Unknown Memory write64 to address %x with data %8.8x_%8.8x", mem, (u32)(value>>32), (u32)value); cpuTlbMissW(mem, cpuRegs.branch); } template static void TAKES_R128 _ext_memWrite128(u32 mem, r128 value) { switch (p) { case 9: { u128 val = r128_to_u128(value); iopMemWrite32((mem + 0) & ~0x1c000000, val._u32[0] & 0xffffffff); iopMemWrite32((mem + 4) & ~0x1c000000, val._u32[1] & 0xffffffff); iopMemWrite32((mem + 8) & ~0x1c000000, val._u32[2] & 0xffffffff); iopMemWrite32((mem + 12) & ~0x1c000000, val._u32[3] & 0xffffffff); return; } default: break; } /*switch (p) { //case 1: // hwm // hwWrite128(mem & ~0xa0000000, value); // return; //case 6: // gsm // mem &= ~0xa0000000; // gsWrite64(mem, value[0]); // gsWrite64(mem+8, value[1]); return; }*/ alignas(16) const u128 uvalue = r128_to_u128(value); MEM_LOG("Unknown Memory write128 to address %x with data %8.8x_%8.8x_%8.8x_%8.8x", mem, uvalue._u32[3], uvalue._u32[2], uvalue._u32[1], uvalue._u32[0]); cpuTlbMissW(mem, cpuRegs.branch); } #define vtlb_RegisterHandlerTempl1(nam,t) vtlb_RegisterHandler(nam##Read8,nam##Read16,nam##Read32,nam##Read64,nam##Read128, \ nam##Write8,nam##Write16,nam##Write32,nam##Write64,nam##Write128) typedef void ClearFunc_t( u32 addr, u32 qwc ); template static __fi void ClearVuFunc(u32 addr, u32 size) { if (vunum) CpuVU1->Clear(addr, size); else CpuVU0->Clear(addr, size); } // VU Micro Memory Reads... template static mem8_t vuMicroRead8(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return vu->Micro[addr]; } template static mem16_t vuMicroRead16(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return *(u16*)&vu->Micro[addr]; } template static mem32_t vuMicroRead32(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return *(u32*)&vu->Micro[addr]; } template static mem64_t vuMicroRead64(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return *(u64*)&vu->Micro[addr]; } template static RETURNS_R128 vuMicroRead128(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return r128_load(&vu->Micro[addr]); } // Profiled VU writes: Happen very infrequently, with exception of BIOS initialization (at most twice per // frame in-game, and usually none at all after BIOS), so cpu clears aren't much of a big deal. template static void vuMicroWrite8(u32 addr,mem8_t data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { vu1Thread.WriteMicroMem(addr, &data, sizeof(u8)); return; } if (vu->Micro[addr]!=data) { // Clear before writing new data ClearVuFunc(addr, 8); //(clearing 8 bytes because an instruction is 8 bytes) (cottonvibes) vu->Micro[addr] =data; } } template static void vuMicroWrite16(u32 addr, mem16_t data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { vu1Thread.WriteMicroMem(addr, &data, sizeof(u16)); return; } if (*(u16*)&vu->Micro[addr]!=data) { ClearVuFunc(addr, 8); *(u16*)&vu->Micro[addr] =data; } } template static void vuMicroWrite32(u32 addr, mem32_t data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { vu1Thread.WriteMicroMem(addr, &data, sizeof(u32)); return; } if (*(u32*)&vu->Micro[addr]!=data) { ClearVuFunc(addr, 8); *(u32*)&vu->Micro[addr] =data; } } template static void vuMicroWrite64(u32 addr, mem64_t data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { vu1Thread.WriteMicroMem(addr, &data, sizeof(u64)); return; } if (*(u64*)&vu->Micro[addr]!=data) { ClearVuFunc(addr, 8); *(u64*)&vu->Micro[addr] =data; } } template static void TAKES_R128 vuMicroWrite128(u32 addr, r128 data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; const u128 udata = r128_to_u128(data); if (vunum && THREAD_VU1) { vu1Thread.WriteMicroMem(addr, &udata, sizeof(u128)); return; } if ((u128&)vu->Micro[addr]!=udata) { ClearVuFunc(addr, 16); r128_store_unaligned(&vu->Micro[addr],data); } } // VU Data Memory Reads... template static mem8_t vuDataRead8(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return vu->Mem[addr]; } template static mem16_t vuDataRead16(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return *(u16*)&vu->Mem[addr]; } template static mem32_t vuDataRead32(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return *(u32*)&vu->Mem[addr]; } template static mem64_t vuDataRead64(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return *(u64*)&vu->Mem[addr]; } template static RETURNS_R128 vuDataRead128(u32 addr) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) vu1Thread.WaitVU(); return r128_load(&vu->Mem[addr]); } // VU Data Memory Writes... template static void vuDataWrite8(u32 addr, mem8_t data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { vu1Thread.WriteDataMem(addr, &data, sizeof(u8)); return; } vu->Mem[addr] = data; } template static void vuDataWrite16(u32 addr, mem16_t data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { vu1Thread.WriteDataMem(addr, &data, sizeof(u16)); return; } *(u16*)&vu->Mem[addr] = data; } template static void vuDataWrite32(u32 addr, mem32_t data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { vu1Thread.WriteDataMem(addr, &data, sizeof(u32)); return; } *(u32*)&vu->Mem[addr] = data; } template static void vuDataWrite64(u32 addr, mem64_t data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { vu1Thread.WriteDataMem(addr, &data, sizeof(u64)); return; } *(u64*)&vu->Mem[addr] = data; } template static void TAKES_R128 vuDataWrite128(u32 addr, r128 data) { VURegs* vu = vunum ? &VU1 : &VU0; addr &= vunum ? 0x3fff: 0xfff; if (vunum && THREAD_VU1) { alignas(16) const u128 udata = r128_to_u128(data); vu1Thread.WriteDataMem(addr, &udata, sizeof(u128)); return; } r128_store_unaligned(&vu->Mem[addr], data); } void memSetPageAddr(u32 vaddr, u32 paddr) { //Console.WriteLn("memSetPageAddr: %8.8x -> %8.8x", vaddr, paddr); vtlb_VMap(vaddr,paddr,0x1000); } void memClearPageAddr(u32 vaddr) { //Console.WriteLn("memClearPageAddr: %8.8x", vaddr); vtlb_VMapUnmap(vaddr,0x1000); // -> whut ? #ifdef FULLTLB // memLUTRK[vaddr >> 12] = 0; // memLUTWK[vaddr >> 12] = 0; #endif } /////////////////////////////////////////////////////////////////////////// // PS2 Memory Init / Reset / Shutdown EEVM_MemoryAllocMess* eeMem = nullptr; alignas(__pagealignsize) u8 eeHw[Ps2MemSize::Hardware]; void memBindConditionalHandlers() { if( hw_by_page[0xf] == 0xFFFFFFFF ) return; if (EmuConfig.Speedhacks.IntcStat) { vtlbMemR16FP* page0F16(hwRead16_page_0F_INTC_HACK); vtlbMemR32FP* page0F32(hwRead32_page_0F_INTC_HACK); //vtlbMemR64FP* page0F64(hwRead64_generic_INTC_HACK); vtlb_ReassignHandler( hw_by_page[0xf], hwRead8<0x0f>, page0F16, page0F32, hwRead64<0x0f>, hwRead128<0x0f>, hwWrite8<0x0f>, hwWrite16<0x0f>, hwWrite32<0x0f>, hwWrite64<0x0f>, hwWrite128<0x0f> ); } else { vtlbMemR16FP* page0F16(hwRead16<0x0f>); vtlbMemR32FP* page0F32(hwRead32<0x0f>); //vtlbMemR64FP* page0F64(hwRead64<0x0f>); vtlb_ReassignHandler( hw_by_page[0xf], hwRead8<0x0f>, page0F16, page0F32, hwRead64<0x0f>, hwRead128<0x0f>, hwWrite8<0x0f>, hwWrite16<0x0f>, hwWrite32<0x0f>, hwWrite64<0x0f>, hwWrite128<0x0f> ); } } // -------------------------------------------------------------------------------------- // eeMemoryReserve (implementations) // -------------------------------------------------------------------------------------- void memAllocate() { eeMem = reinterpret_cast(SysMemory::GetEEMem()); } void memReset() { // Note!! Ideally the vtlb should only be initialized once, and then subsequent // resets of the system hardware would only clear vtlb mappings, but since the // rest of the emu is not really set up to support a "soft" reset of that sort // we opt for the hard/safe version. pxAssume( eeMem ); #ifdef ENABLECACHE memset(pCache,0,sizeof(_cacheS)*64); #endif vtlb_Init(); null_handler = vtlb_RegisterHandler(nullRead8, nullRead16, nullRead32, nullRead64, nullRead128, nullWrite8, nullWrite16, nullWrite32, nullWrite64, nullWrite128); tlb_fallback_0 = vtlb_RegisterHandlerTempl1(_ext_mem,0); tlb_fallback_3 = vtlb_RegisterHandlerTempl1(_ext_mem,3); tlb_fallback_4 = vtlb_RegisterHandlerTempl1(_ext_mem,4); tlb_fallback_5 = vtlb_RegisterHandlerTempl1(_ext_mem,5); tlb_fallback_7 = vtlb_RegisterHandlerTempl1(_ext_mem,7); tlb_fallback_8 = vtlb_RegisterHandlerTempl1(_ext_mem,8); iop_memory = vtlb_RegisterHandlerTempl1(_ext_mem,9); // Dynarec versions of VUs vu0_micro_mem = vtlb_RegisterHandlerTempl1(vuMicro,0); vu1_micro_mem = vtlb_RegisterHandlerTempl1(vuMicro,1); vu1_data_mem = (1||THREAD_VU1) ? vtlb_RegisterHandlerTempl1(vuData,1) : 0; ////////////////////////////////////////////////////////////////////////////////////////// // IOP's "secret" Hardware Register mapping, accessible from the EE (and meant for use // by debugging or BIOS only). The IOP's hw regs are divided into three main pages in // the 0x1f80 segment, and then another oddball page for CDVD in the 0x1f40 segment. // using namespace IopMemory; tlb_fallback_2 = vtlb_RegisterHandler( iopHwRead8_generic, iopHwRead16_generic, iopHwRead32_generic, _ext_memRead64<2>, _ext_memRead128<2>, iopHwWrite8_generic, iopHwWrite16_generic, iopHwWrite32_generic, _ext_memWrite64<2>, _ext_memWrite128<2> ); iopHw_by_page_01 = vtlb_RegisterHandler( iopHwRead8_Page1, iopHwRead16_Page1, iopHwRead32_Page1, _ext_memRead64<2>, _ext_memRead128<2>, iopHwWrite8_Page1, iopHwWrite16_Page1, iopHwWrite32_Page1, _ext_memWrite64<2>, _ext_memWrite128<2> ); iopHw_by_page_03 = vtlb_RegisterHandler( iopHwRead8_Page3, iopHwRead16_Page3, iopHwRead32_Page3, _ext_memRead64<2>, _ext_memRead128<2>, iopHwWrite8_Page3, iopHwWrite16_Page3, iopHwWrite32_Page3, _ext_memWrite64<2>, _ext_memWrite128<2> ); iopHw_by_page_08 = vtlb_RegisterHandler( iopHwRead8_Page8, iopHwRead16_Page8, iopHwRead32_Page8, _ext_memRead64<2>, _ext_memRead128<2>, iopHwWrite8_Page8, iopHwWrite16_Page8, iopHwWrite32_Page8, _ext_memWrite64<2>, _ext_memWrite128<2> ); // psHw Optimized Mappings // The HW Registers have been split into pages to improve optimization. #define hwHandlerTmpl(page) \ hwRead8, hwRead16, hwRead32, hwRead64, hwRead128, \ hwWrite8, hwWrite16,hwWrite32,hwWrite64,hwWrite128 hw_by_page[0x0] = vtlb_RegisterHandler( hwHandlerTmpl(0x00) ); hw_by_page[0x1] = vtlb_RegisterHandler( hwHandlerTmpl(0x01) ); hw_by_page[0x2] = vtlb_RegisterHandler( hwHandlerTmpl(0x02) ); hw_by_page[0x3] = vtlb_RegisterHandler( hwHandlerTmpl(0x03) ); hw_by_page[0x4] = vtlb_RegisterHandler( hwHandlerTmpl(0x04) ); hw_by_page[0x5] = vtlb_RegisterHandler( hwHandlerTmpl(0x05) ); hw_by_page[0x6] = vtlb_RegisterHandler( hwHandlerTmpl(0x06) ); hw_by_page[0x7] = vtlb_RegisterHandler( hwHandlerTmpl(0x07) ); hw_by_page[0x8] = vtlb_RegisterHandler( hwHandlerTmpl(0x08) ); hw_by_page[0x9] = vtlb_RegisterHandler( hwHandlerTmpl(0x09) ); hw_by_page[0xa] = vtlb_RegisterHandler( hwHandlerTmpl(0x0a) ); hw_by_page[0xb] = vtlb_RegisterHandler( hwHandlerTmpl(0x0b) ); hw_by_page[0xc] = vtlb_RegisterHandler( hwHandlerTmpl(0x0c) ); hw_by_page[0xd] = vtlb_RegisterHandler( hwHandlerTmpl(0x0d) ); hw_by_page[0xe] = vtlb_RegisterHandler( hwHandlerTmpl(0x0e) ); hw_by_page[0xf] = vtlb_NewHandler(); // redefined later based on speedhacking prefs memBindConditionalHandlers(); ////////////////////////////////////////////////////////////////////// // GS Optimized Mappings tlb_fallback_6 = vtlb_RegisterHandler( _ext_memRead8<6>, _ext_memRead16<6>, _ext_memRead32<6>, _ext_memRead64<6>, _ext_memRead128<6>, _ext_memWrite8<6>, _ext_memWrite16<6>, _ext_memWrite32<6>, gsWrite64_generic, gsWrite128_generic ); gs_page_0 = vtlb_RegisterHandler( _ext_memRead8<6>, _ext_memRead16<6>, _ext_memRead32<6>, _ext_memRead64<6>, _ext_memRead128<6>, _ext_memWrite8<6>, _ext_memWrite16<6>, _ext_memWrite32<6>, gsWrite64_page_00, gsWrite128_page_00 ); gs_page_1 = vtlb_RegisterHandler( _ext_memRead8<6>, _ext_memRead16<6>, _ext_memRead32<6>, _ext_memRead64<6>, _ext_memRead128<6>, _ext_memWrite8<6>, _ext_memWrite16<6>, _ext_memWrite32<6>, gsWrite64_page_01, gsWrite128_page_01 ); //vtlb_Reset(); // reset memLUT (?) //vtlb_VMap(0x00000000,0x00000000,0x20000000); //vtlb_VMapUnmap(0x20000000,0x60000000); memMapPhy(); memMapVUmicro(); memMapKernelMem(); memMapSupervisorMem(); memMapUserMem(); memSetKernelMode(); vtlb_VMap(0x00000000,0x00000000,0x20000000); vtlb_VMapUnmap(0x20000000,0x60000000); std::memset(s_ba, 0, sizeof(s_ba)); s_ba[0xA] = 1; // Power on s_ba_command_executing = false; s_ba_error_detected = false; s_ba_current_reg = 0; std::memset(s_dve_regs, 0, sizeof(s_dve_regs)); s_dve_regs[0x7e] = 0x1C; // Status register. 0x1C seems to be the value it's expecting for everything being OK. // BIOS is included in eeMem, so it needs to be copied after zeroing. std::memset(eeMem, 0, sizeof(*eeMem)); CopyBIOSToMemory(); } void memRelease() { eeMem = nullptr; } bool SaveStateBase::memFreeze(Error* error) { Freeze(s_ba); Freeze(s_dve_regs); Freeze(s_ba_command_executing); Freeze(s_ba_error_detected); Freeze(s_ba_current_reg); bool extra_memory = s_extra_memory; Freeze(extra_memory); if (extra_memory != s_extra_memory) { Error::SetStringFmt(error, "Memory size mismatch, save state requires {}, but VM currently has {}.", extra_memory ? "128MB" : "32MB", s_extra_memory ? "128MB" : "32MB"); return false; } return IsOkay(); } u8 EEMemoryInterface::Read8(u32 address, bool* valid) { if (valid) *valid = true; return memRead8(address); } u16 EEMemoryInterface::Read16(u32 address, bool* valid) { if (valid) *valid = true; return memRead16(address); } u32 EEMemoryInterface::Read32(u32 address, bool* valid) { if (valid) *valid = true; return memRead32(address); } u64 EEMemoryInterface::Read64(u32 address, bool* valid) { if (valid) *valid = true; return memRead64(address); } u128 EEMemoryInterface::Read128(u32 address, bool* valid) { u128 value; memRead128(address, value); if (valid) *valid = true; return value; } bool EEMemoryInterface::ReadBytes(u32 address, void* dest, u32 size) { return vtlb_memSafeReadBytes(address, dest, size); } bool EEMemoryInterface::Write8(u32 address, u8 value) { memWrite8(address, value); return true; } bool EEMemoryInterface::Write16(u32 address, u16 value) { memWrite16(address, value); return true; } bool EEMemoryInterface::Write32(u32 address, u32 value) { memWrite32(address, value); return true; } bool EEMemoryInterface::Write64(u32 address, u64 value) { memWrite64(address, value); return true; } bool EEMemoryInterface::Write128(u32 address, u128 value) { memWrite128(address, value); return true; } bool EEMemoryInterface::WriteBytes(u32 address, const void* src, u32 size) { return vtlb_memSafeWriteBytes(address, src, size); } bool EEMemoryInterface::CompareBytes(u32 address, const void* src, u32 size) { return vtlb_memSafeCmpBytes(address, src, size) == 0; }