diff --git a/mods/builtin/packages/psx.enhancement.8mb-ram/1.0.0/manifest.toml b/mods/builtin/packages/psx.enhancement.8mb-ram/1.0.0/manifest.toml new file mode 100644 index 00000000..1fc40d3f --- /dev/null +++ b/mods/builtin/packages/psx.enhancement.8mb-ram/1.0.0/manifest.toml @@ -0,0 +1,17 @@ +id = "psx.enhancement.8mb-ram" +version = "1.0.0" +name = "8 MB Main RAM" +author = "psxrecomp" +description = "Enables an opt-in 8 MB main RAM map for patches that require expanded PS1 memory. Disabled by default." + +[[feature]] +id = "8mb-ram" +name = "8 MB Main RAM" +description = "Use expanded 8 MB main RAM instead of stock 2 MB mirroring. Both netplay peers must use the same setting." +category = "enhancement" +default_enabled = false +hidden = true + +[[plugin]] +feature = "8mb-ram" +id = "psx.8mb-ram" diff --git a/recompiler/src/full_function_emitter.cpp b/recompiler/src/full_function_emitter.cpp index b6bf9d26..2928ac05 100644 --- a/recompiler/src/full_function_emitter.cpp +++ b/recompiler/src/full_function_emitter.cpp @@ -2137,6 +2137,7 @@ void FullFunctionEmitter::emit_dispatch( out += "extern int dirty_ram_dispatch(CPUState* cpu, uint32_t addr, uint32_t stop_addr);\n"; out += "extern int dirty_ram_is_dirty(uint32_t phys);\n"; out += "extern int psx_kernel_bless_dispatchable(uint32_t phys);\n"; + out += "extern uint32_t psx_ram_canon_code_addr(uint32_t addr);\n"; out += "extern void fntrace_record(CPUState* cpu, uint32_t target);\n"; out += "extern uint64_t g_dispatch_static_hits;\n"; out += "\n"; @@ -2189,7 +2190,8 @@ void FullFunctionEmitter::emit_dispatch( out += " * physical 0x30000-0x5AFFF. If the target belongs to the\n"; out += " * active game text range, route it through the game/dirty-RAM\n"; out += " * path before normalizing it to shell ROM. */\n"; - out += " uint32_t game_phys = addr & 0x1FFFFFFFu;\n"; + out += " uint32_t game_addr = psx_ram_canon_code_addr(addr);\n"; + out += " uint32_t game_phys = game_addr & 0x1FFFFFFFu;\n"; out += " /* Class-A shell-window collision fix: post-game-start the BIOS shell\n"; out += " * copy at RAM 0x30000-0x5AFFF is DEAD (overwritten by the game EXE,\n"; out += " * its runtime-loaded overlays, or CD streaming), so normalize()->shell\n"; @@ -2233,13 +2235,13 @@ void FullFunctionEmitter::emit_dispatch( out += fmt::format( " game_phys >= 0x{:08X}u && game_phys <= 0x{:08X}u &&\n", addr_model().rom_keyed_ram_lo(), addr_model().rom_keyed_ram_hi_incl()); - out += " psx_game_address_in_text(addr) && dirty_ram_text_native_ok(game_phys);\n"; + out += " psx_game_address_in_text(game_addr) && dirty_ram_text_native_ok(game_phys);\n"; } else { out += " int game_text_in_shell_window = 0;\n"; } out += " if (!found && (game_shell_overlap || game_text_in_shell_window ||\n"; - out += " (psx_game_address_in_text(addr) && dirty_ram_is_dirty(game_phys)))) {\n"; - out += " found = dirty_ram_dispatch(cpu, addr, stop_addr);\n"; + out += " (psx_game_address_in_text(game_addr) && dirty_ram_is_dirty(game_phys)))) {\n"; + out += " found = dirty_ram_dispatch(cpu, game_addr, stop_addr);\n"; out += " }\n"; out += "#endif\n"; out += " uint32_t phys = normalize(addr);\n"; diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index 25e7f72c..11f0b835 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -1482,7 +1482,8 @@ int main(int argc, char** argv) { uint32_t game_text_start = exe->load_address() & 0x1FFFFFFFu; uint32_t game_text_end = game_text_start + exe->code_size(); ds << "int psx_game_address_in_text(uint32_t addr) {\n"; - ds << " uint32_t phys = addr & 0x1FFFFFFFu;\n"; + ds << " extern uint32_t psx_ram_canon_code_addr(uint32_t);\n"; + ds << " uint32_t phys = psx_ram_canon_code_addr(addr) & 0x1FFFFFFFu;\n"; ds << fmt::format(" return phys >= 0x{:08X}u && phys < 0x{:08X}u;\n", game_text_start, game_text_end); ds << "}\n\n"; @@ -1639,6 +1640,8 @@ int main(int argc, char** argv) { ds << " * code to the interpreter. Compare the 29-bit physical address instead;\n"; ds << " * the table is sorted by the same masked key. */\n"; ds << "static const PsxGameDispatchEntry* psx_game_find_entry(uint32_t addr) {\n"; + ds << " extern uint32_t psx_ram_canon_code_addr(uint32_t);\n"; + ds << " addr = psx_ram_canon_code_addr(addr);\n"; ds << " const uint32_t want = addr & 0x1FFFFFFFu;\n"; if (indexed_lookup) { ds << fmt::format(" const uint32_t offset = want - 0x{:08X}u;\n", lookup_lo); diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 08f00e64..32573949 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -146,6 +146,11 @@ if(BUILD_TESTING) COMMAND psx_memory_geometry_${_ram_geometry}_test) endforeach() + add_executable(psx_ram_runtime_map_test + tests/test_psx_ram_runtime_map.c) + target_include_directories(psx_ram_runtime_map_test PRIVATE include) + add_test(NAME psx_ram_runtime_map_test COMMAND psx_ram_runtime_map_test) + add_executable(host_keymap_test tests/test_host_keymap.c src/host_keymap.c) diff --git a/runtime/include/mod_memory.h b/runtime/include/mod_memory.h index 3eb54b66..2bfea7aa 100644 --- a/runtime/include/mod_memory.h +++ b/runtime/include/mod_memory.h @@ -2,6 +2,7 @@ #define PSXRECOMP_MOD_MEMORY_H #include +#include "psx_ram.h" #ifdef __cplusplus extern "C" { @@ -34,7 +35,7 @@ static inline int psx_mod_gpu_dma_aperture_offset_for( } /* - * Preserve the retail DMAC's 2 MiB folding unless the address is inside the + * Fold DMA addresses through live main RAM unless the address is inside the * portion of the enhancement aperture that has actually been allocated. */ static inline uint32_t psx_mod_gpu_dma_resolve_address_for( @@ -43,7 +44,7 @@ static inline uint32_t psx_mod_gpu_dma_resolve_address_for( if (psx_mod_gpu_dma_aperture_offset_for( canonical, 4u, used, (uint32_t *)0)) return canonical; - return canonical & 0x001FFFFCu; + return psx_ram_map_read(canonical) & ~3u; } uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t size, uint32_t alignment); diff --git a/runtime/include/mod_plugins.h b/runtime/include/mod_plugins.h index 9de40626..6ad3ffe7 100644 --- a/runtime/include/mod_plugins.h +++ b/runtime/include/mod_plugins.h @@ -84,6 +84,13 @@ uint32_t psx_mod_alloc_guest_memory(uint32_t size, uint32_t alignment); */ uint32_t psx_mod_alloc_gpu_dma_memory(uint32_t size, uint32_t alignment); +/* + * Opt into expanded 8 MiB main RAM for this launch. Default runtime behavior + * remains stock 2 MiB mirroring unless a trusted activation plugin requests + * this before memory_init(). + */ +int psx_mod_set_main_ram_8mb(int enabled); + /* Current per-side widescreen reveal in native game pixels (zero at 4:3). */ int32_t psx_mod_widescreen_x_margin(void); diff --git a/runtime/include/psx_cyc.h b/runtime/include/psx_cyc.h index 4239c123..3642d519 100644 --- a/runtime/include/psx_cyc.h +++ b/runtime/include/psx_cyc.h @@ -34,6 +34,7 @@ #endif #include "cpu_state.h" /* CPUState (guard-safe: cpu_state.h includes us last) */ #include "psx_cycles.h" /* inline psx_advance_cycles */ +#include "psx_ram.h" #ifdef __cplusplus extern "C" { @@ -250,7 +251,7 @@ static inline uint32_t psx_cyc_load_word(CPUState* cpu, uint32_t addr, cpu->ld_which_t = (uint8_t)rt; } uint32_t value; - memcpy(&value, g_psx_ram + (phys & 0x1FFFFFu), sizeof(value)); + memcpy(&value, g_psx_ram + psx_ram_map_read(phys), sizeof(value)); return value; } return psx_cyc_load_word_slow(cpu, addr, rt, reg_mask); @@ -280,7 +281,7 @@ static inline uint16_t psx_cyc_load_half(CPUState* cpu, uint32_t addr, cpu->ld_which_t = (uint8_t)rt; } uint16_t value; - memcpy(&value, g_psx_ram + (phys & 0x1FFFFFu), sizeof(value)); + memcpy(&value, g_psx_ram + psx_ram_map_read(phys), sizeof(value)); return value; } return psx_cyc_load_half_slow(cpu, addr, rt, reg_mask); diff --git a/runtime/include/psx_ram.h b/runtime/include/psx_ram.h new file mode 100644 index 00000000..c149dcd6 --- /dev/null +++ b/runtime/include/psx_ram.h @@ -0,0 +1,75 @@ +#ifndef PSX_RAM_H +#define PSX_RAM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Retail DRAM is 2 MiB, mirrored 4x across the 8 MiB decode window. The + * opt-in 8 MB RAM enhancement keeps an 8 MiB host backing and switches the + * live map to unique high pages only after a trusted package requests it. + */ +#define PSX_RAM_2MB 0x00200000u +#define PSX_RAM_8MB 0x00800000u +#define PSX_RAM_CAPACITY PSX_RAM_8MB +#define PSX_RAM_WINDOW 0x00800000u + +extern uint32_t g_psx_ram_size; +extern uint32_t g_psx_ram_mask; + +uint32_t memory_get_ram_bytes(void); +int psx_ram_8mb_active(void); +void psx_ram_reset_size_request(void); + +#define PSX_RAM_HIGH_PAGE0 (PSX_RAM_2MB >> 12) +#define PSX_RAM_HIGH_PAGES ((PSX_RAM_8MB - PSX_RAM_2MB) >> 12) +#define PSX_RAM_HIGH_BITWORDS ((PSX_RAM_HIGH_PAGES + 31u) / 32u) +extern uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; + +static inline int psx_ram_high_page_unique(uint32_t page) { + uint32_t i, bit; + if (page < PSX_RAM_HIGH_PAGE0 || page >= (PSX_RAM_8MB >> 12)) + return 1; + i = page - PSX_RAM_HIGH_PAGE0; + bit = 1u << (i & 31u); + return (g_psx_ram_high_unique[i >> 5] & bit) != 0; +} + +static inline uint32_t psx_ram_map_read(uint32_t phys) { + phys &= 0x1FFFFFFFu; + if (phys >= PSX_RAM_WINDOW) + return phys; + if (g_psx_ram_size <= PSX_RAM_2MB) + return phys & (PSX_RAM_2MB - 1u); + if (phys < PSX_RAM_2MB) + return phys; + if (psx_ram_high_page_unique(phys >> 12)) + return phys; + return phys & (PSX_RAM_2MB - 1u); +} + +static inline uint32_t psx_ram_map_write(uint32_t phys) { + return psx_ram_map_read(phys); +} + +static inline uint32_t psx_ram_canon_code_addr_inline(uint32_t addr) { + uint32_t seg = addr & 0xE0000000u; + uint32_t phys = addr & 0x1FFFFFFFu; + if (phys < PSX_RAM_WINDOW && phys >= PSX_RAM_2MB && + !psx_ram_high_page_unique(phys >> 12)) + phys &= (PSX_RAM_2MB - 1u); + return seg | phys; +} + +void psx_ram_register_unique(uint32_t addr, uint32_t len); +uint32_t psx_ram_canon_code_addr(uint32_t addr); +void psx_ram_resync_high_after_restore(void); + +#ifdef __cplusplus +} +#endif + +#endif /* PSX_RAM_H */ diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 109d5c66..11462f09 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -415,6 +415,7 @@ set(PSXRECOMP_RUNTIME_SOURCES ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_speed.c ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_pgxp.c ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_bezel.c + ${PSXRECOMP_ROOT}/runtime/src/mod_builtin_ram.c ${PSXRECOMP_ROOT}/runtime/src/mod_packages.cpp ${PSXRECOMP_ROOT}/runtime/src/mod_runtime.cpp ${PSXRECOMP_ROOT}/runtime/src/mod_texture_banks.c diff --git a/runtime/src/boot_state.c b/runtime/src/boot_state.c index 0a644f4d..0adf892b 100644 --- a/runtime/src/boot_state.c +++ b/runtime/src/boot_state.c @@ -9,6 +9,7 @@ #include "interrupts.h" #include "psx_cycles.h" #include "psx_icache.h" /* g_psx_icache_tv — fetch-cost tags in BS_SEC_ICACHE */ +#include "psx_ram.h" #include "pst_wire.h" #include #include @@ -38,7 +39,6 @@ static double boot_state_mono_ms(void) { /* Compress payloads at/above this size (RAM/VRAM/SPU/dirty dominate I/O). */ #define BOOT_STATE_ZLIB_MIN 256u -#define RAM_SIZE (2u * 1024u * 1024u) #define SPAD_SIZE (1024u) #define VRAM_W 1024 #define VRAM_H 512 @@ -46,6 +46,7 @@ static double boot_state_mono_ms(void) { /* ---- core accessors (existing runtime modules) ---- */ extern uint8_t* memory_get_ram_ptr(void); +extern uint32_t memory_get_ram_bytes(void); extern uint8_t* memory_get_scratchpad_ptr(void); extern uint32_t i_stat; extern uint32_t i_mask; @@ -377,7 +378,7 @@ static int boot_state_save_to(BsOut* o, const CPUState* cpu, ok = write_header_le(o, &h); if (ok) ok = write_cpu_section(o, cpu); - if (ok) ok = write_section(o, BS_SEC_RAM, memory_get_ram_ptr(), RAM_SIZE); + if (ok) ok = write_section(o, BS_SEC_RAM, memory_get_ram_ptr(), memory_get_ram_bytes()); if (ok) ok = write_section(o, BS_SEC_SPAD, memory_get_scratchpad_ptr(), SPAD_SIZE); if (ok && psx_mod_memory_snapshot_bytes()) ok = write_module_section(o, BS_SEC_MODMEM, psx_mod_memory_snapshot_bytes, @@ -490,8 +491,8 @@ static int boot_state_save_buffer_ex(const CPUState* cpu, uint32_t bios_checksum *out_len = 0; memset(&o, 0, sizeof o); o.no_zlib = no_zlib ? 1 : 0; - /* Compressed MotK ~1.3–1.5 MiB; raw ~3.5–4 MiB (RAM+VRAM+SPU). */ - o.cap = no_zlib ? (5u * 1024u * 1024u) : (2u * 1024u * 1024u); + /* Compressed MotK ~1.3-1.5 MiB; raw ~3.5-4 MiB (2 MB RAM) or ~9.5 MiB (8 MB). */ + o.cap = no_zlib ? (12u * 1024u * 1024u) : (2u * 1024u * 1024u); o.data = (uint8_t*)malloc(o.cap); if (!o.data) return 0; if (!boot_state_save_to(&o, cpu, bios_checksum, entry_pc)) { @@ -544,11 +545,12 @@ static int apply_section(uint32_t tag, const uint8_t* p, uint32_t len, return 1; } case BS_SEC_RAM: - if (len != RAM_SIZE) return 0; - memcpy(memory_get_ram_ptr(), p, RAM_SIZE); + if (len != memory_get_ram_bytes()) return 0; + memcpy(memory_get_ram_ptr(), p, memory_get_ram_bytes()); { extern void psx_kernel_bless_note_range(uint32_t phys, uint32_t l); - psx_kernel_bless_note_range(0, RAM_SIZE); + psx_kernel_bless_note_range(0, memory_get_ram_bytes()); + psx_ram_resync_high_after_restore(); } return 1; case BS_SEC_SPAD: diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index b6ba33fd..8b5af50c 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -30,6 +30,7 @@ #include "psx_cycles.h" #include "psx_icache.h" #include "psx_instr_cost.h" /* psx_instr_base_cycles — single-source cycle cost */ +#include "psx_ram.h" #include "gpu.h" /* psx_ws_is_backdrop_site / psx_ws_backdrop_x (interp hook) */ #include "ws_backdrop_detect.h" /* shared backdrop-window detector (auto_backdrop) */ #include "lockstep.h" @@ -2382,6 +2383,9 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_ int dirty_ram_dispatch(CPUState* cpu, uint32_t addr, uint32_t stop_addr) { extern int g_psx_dispatch_depth; extern void psx_fatal_halt(const char *reason); + addr = psx_ram_canon_code_addr(addr); + if (stop_addr != 0u) + stop_addr = psx_ram_canon_code_addr(stop_addr); #ifndef PSX_NO_DEBUG_TOOLS /* A0/B0/C0 kernel-vector stubs are runtime-written, so calls to them * land HERE, not in the static dispatcher — which meant the bioscall diff --git a/runtime/src/memory.c b/runtime/src/memory.c index dbb05188..03f6a704 100644 --- a/runtime/src/memory.c +++ b/runtime/src/memory.c @@ -25,13 +25,14 @@ #include "dirty_ram_interp.h" #include "guest_tty.h" #include "psx_cycles.h" +#include "psx_ram.h" #include "starvation_ring.h" #include #include #include #include -#define RAM_SIZE (2 * 1024 * 1024) +#define RAM_SIZE PSX_RAM_CAPACITY #define SCRATCHPAD_SIZE 1024 #define BIOS_ROM_SIZE (512 * 1024) #define MOD_MEMORY_BASE 0x1F000000u @@ -44,6 +45,66 @@ static uint8_t mod_memory[MOD_MEMORY_SIZE]; static uint32_t mod_memory_used; static uint8_t mod_gpu_dma_memory[PSX_MOD_GPU_DMA_APERTURE_SIZE]; static uint32_t mod_gpu_dma_memory_used; +static int s_ram_8mb_requested; + +uint32_t g_psx_ram_size = PSX_RAM_2MB; +uint32_t g_psx_ram_mask = PSX_RAM_2MB - 1u; +static uint32_t s_ram_high_registered[PSX_RAM_HIGH_BITWORDS]; +uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; + +static inline void psx_ram_high_page_mark(uint32_t page) { + uint32_t i, bit; + if (page < PSX_RAM_HIGH_PAGE0 || page >= (PSX_RAM_8MB >> 12)) + return; + i = page - PSX_RAM_HIGH_PAGE0; + bit = 1u << (i & 31u); + g_psx_ram_high_unique[i >> 5] |= bit; + s_ram_high_registered[i >> 5] |= bit; +} + +static void psx_ram_apply_registered_bitmap(void) { + memcpy(g_psx_ram_high_unique, s_ram_high_registered, + sizeof(g_psx_ram_high_unique)); +} + +void psx_ram_register_unique(uint32_t addr, uint32_t len) { + uint32_t phys, end, page, last; + if (len == 0u) + return; + phys = addr & 0x1FFFFFFFu; + if (phys >= PSX_RAM_WINDOW) + return; + end = phys + len; + if (end < phys || end > PSX_RAM_WINDOW) + end = PSX_RAM_WINDOW; + if (end <= PSX_RAM_2MB) + return; + if (phys < PSX_RAM_2MB) + phys = PSX_RAM_2MB; + page = phys >> 12; + last = (end - 1u) >> 12; + for (; page <= last; page++) + psx_ram_high_page_mark(page); +} + +uint32_t psx_ram_canon_code_addr(uint32_t addr) { + return psx_ram_canon_code_addr_inline(addr); +} + +void psx_ram_resync_high_after_restore(void) { + uint32_t page; + psx_ram_apply_registered_bitmap(); + if (g_psx_ram_size <= PSX_RAM_2MB) + return; + for (page = PSX_RAM_HIGH_PAGE0; page < (PSX_RAM_8MB >> 12); page++) { + uint32_t dst = page << 12; + uint32_t src = dst & (PSX_RAM_2MB - 1u); + if (psx_ram_high_page_unique(page)) + continue; + if (memcmp(ram + dst, ram + src, 4096u) != 0) + psx_ram_high_page_mark(page); + } +} uint32_t psx_mod_memory_snapshot_bytes(void) { return mod_memory_used || mod_gpu_dma_memory_used @@ -139,22 +200,63 @@ uint8_t *g_psx_ram = ram; /* PSX_LOAD_DELAY gate (default on). −1 = unread; 0/1 after first resolve. */ int g_psx_load_delay = -1; -/* Physical address translation for guest accesses. The 2 MB main RAM is - * mirrored 4x across the first 8 MB of each segment (mem-ctrl RAM_SIZE - * register, default 0x0B88) and games rely on it — Kula World's crt0 - * parks $sp in the 4th mirror (0x807FFFF8). Fold the mirrors here so the - * RAM bounds checks below see canonical offsets; without this, mirror - * writes fell into the open-bus no-op and mirror reads returned 0 (the - * guest's stack silently vanished and $ra came back as 0). */ +/* Physical address translation for guest accesses. Retail 2 MB DRAM is + * mirrored across the first 8 MB of each segment; the opt-in 8 MB mode maps + * registered high pages uniquely and keeps unregistered high pages aliased. */ static inline uint32_t psx_phys_addr(uint32_t addr) { uint32_t phys = addr & 0x1FFFFFFFu; - if (phys < 0x00800000u) phys &= (uint32_t)(RAM_SIZE - 1); + if (phys < PSX_RAM_WINDOW) phys = psx_ram_map_read(phys); + return phys; +} + +static inline uint32_t psx_phys_addr_store(uint32_t addr) { + uint32_t phys = addr & 0x1FFFFFFFu; + if (phys < PSX_RAM_WINDOW) phys = psx_ram_map_write(phys); return phys; } /* Expose RAM pointer for oracle comparison (find_first_divergence). */ uint8_t *memory_get_ram_ptr(void) { return ram; } uint8_t *memory_get_scratchpad_ptr(void) { return scratchpad; } +uint32_t memory_get_ram_bytes(void) { return g_psx_ram_size; } +int psx_ram_8mb_active(void) { return g_psx_ram_size > PSX_RAM_2MB; } + +void psx_ram_reset_size_request(void) { + s_ram_8mb_requested = 0; + memset(s_ram_high_registered, 0, sizeof(s_ram_high_registered)); + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); +} + +int psx_mod_set_main_ram_8mb(int enabled) { + s_ram_8mb_requested = enabled ? 1 : 0; + memset(s_ram_high_registered, 0, sizeof(s_ram_high_registered)); + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); + if (enabled) + psx_ram_register_unique(PSX_RAM_2MB, PSX_RAM_8MB - PSX_RAM_2MB); + return 1; +} + +static int psx_ram_any_high_registered(void) { + uint32_t i; + for (i = 0; i < PSX_RAM_HIGH_BITWORDS; i++) { + if (s_ram_high_registered[i] != 0u) + return 1; + } + return 0; +} + +static void psx_ram_apply_size_request(void) { + if (s_ram_8mb_requested) { + g_psx_ram_size = PSX_RAM_8MB; + g_psx_ram_mask = PSX_RAM_8MB - 1u; + if (!psx_ram_any_high_registered()) + psx_ram_register_unique(PSX_RAM_2MB, PSX_RAM_8MB - PSX_RAM_2MB); + } else { + g_psx_ram_size = PSX_RAM_2MB; + g_psx_ram_mask = PSX_RAM_2MB - 1u; + memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique)); + } +} void memory_clear_low_boot_scratch(void) { memset(ram, 0, 0x10u); @@ -771,6 +873,7 @@ uint32_t dirty_ram_get_bitmap_word_count(void) { } void dirty_ram_set_bitmap_words(const uint32_t* words, uint32_t count) { + memset(dirty_ram_bitmap, 0, sizeof(dirty_ram_bitmap)); if (count > DIRTY_RAM_BITMAP_WORDS) count = DIRTY_RAM_BITMAP_WORDS; for (uint32_t i = 0; i < count; i++) dirty_ram_bitmap[i] = words[i]; @@ -1103,6 +1206,7 @@ static uint32_t s_bios_checksum = 0; uint32_t memory_get_bios_checksum(void) { return s_bios_checksum; } void memory_init(const char* bios_path) { + psx_ram_apply_size_request(); memset(ram, 0, sizeof(ram)); memset(scratchpad, 0, sizeof(scratchpad)); /* Rematch re-enters without process exit — wipe sticky I/O regs that @@ -1712,7 +1816,7 @@ static void psx_write_word_raw(uint32_t addr, uint32_t val) { * We have no cache model, so silently discard RAM/scratchpad writes. */ if (sr_ptr && (*sr_ptr & 0x10000u)) return; - uint32_t phys = psx_phys_addr(addr); + uint32_t phys = psx_phys_addr_store(addr); /* The generated BIOS mirrors its exception trampoline to 0x80000000 during * boot. On hardware this mirror copy is not visible in RAM; only the real @@ -1906,7 +2010,7 @@ static void psx_write_half_raw(uint32_t addr, uint16_t val) { /* KSEG2 guard — see psx_read_word_raw. */ if (addr >= 0xC0000000u) { g_kseg2_ignored_writes++; return; } - uint32_t phys = psx_phys_addr(addr); + uint32_t phys = psx_phys_addr_store(addr); if (phys < RAM_SIZE) { debug_server_trace_write_check(phys, (uint32_t)read_ram_half(phys), (uint32_t)val, 2); @@ -2166,10 +2270,10 @@ static inline int psx_cyc_main_ram_fast_addr(uint32_t addr, uint32_t width, return 0; uint32_t phys = addr & 0x1FFFFFFFu; if (phys >= 0x00800000u) return 0; - phys &= (uint32_t)(RAM_SIZE - 1); + phys = psx_ram_map_read(phys); /* Aligned guest loads cannot cross this boundary, but fail closed for a * malformed/unaligned caller instead of introducing a host OOB read. */ - if (phys > (uint32_t)RAM_SIZE - width) return 0; + if (phys > g_psx_ram_size - width) return 0; *phys_out = phys; return 1; } @@ -2241,7 +2345,7 @@ static void psx_write_byte_raw(uint32_t addr, uint8_t val) { /* KSEG2 guard — see psx_read_word_raw. */ if (addr >= 0xC0000000u) { g_kseg2_ignored_writes++; return; } - uint32_t phys = psx_phys_addr(addr); + uint32_t phys = psx_phys_addr_store(addr); if (phys < RAM_SIZE) { debug_server_trace_write_check(phys, (uint32_t)ram[phys], (uint32_t)val, 1); diff --git a/runtime/src/mod_builtin_ram.c b/runtime/src/mod_builtin_ram.c new file mode 100644 index 00000000..2e51c8d9 --- /dev/null +++ b/runtime/src/mod_builtin_ram.c @@ -0,0 +1,16 @@ +/* + * Framework-owned opt-in 8 MB main RAM. + * + * Retail PS1 DRAM is 2 MiB mirrored four times across 0x00000000-0x007FFFFF. + * Some enhancement patches use the upper part of that window as real RAM. + */ +#include "mod_plugins.h" + +static void builtin_8mb_ram_activate(void) { + (void)psx_mod_set_main_ram_8mb(1); +} + +PSX_MOD_CONSTRUCTOR(psx_register_builtin_ram_plugins) { + (void)psx_mod_register_activation_plugin( + "psx.8mb-ram", builtin_8mb_ram_activate); +} diff --git a/runtime/src/mod_runtime.cpp b/runtime/src/mod_runtime.cpp index 92e4ee97..63226032 100644 --- a/runtime/src/mod_runtime.cpp +++ b/runtime/src/mod_runtime.cpp @@ -5,6 +5,7 @@ #include "mod_packages.h" #include "mod_plugins.h" #include "gpu.h" +#include "psx_ram.h" #include "psx_sha256.h" #if defined(RECOMP_LAUNCHER) @@ -1337,6 +1338,7 @@ extern "C" int psx_mod_read_disc_file(const char* path, void* buffer, extern "C" void mod_runtime_activate_plugins(void) { using namespace PSXRecompV4; RuntimeMods& s = state(); + psx_ram_reset_size_request(); if (!s.initialized || !s.plan.ok) return; for (const ModResolution::Plugin& plugin : s.plan.plugins) { s.current_plugin = &plugin; diff --git a/runtime/src/netplay_state_digest.c b/runtime/src/netplay_state_digest.c index cde6ccce..7a60e1d5 100644 --- a/runtime/src/netplay_state_digest.c +++ b/runtime/src/netplay_state_digest.c @@ -12,6 +12,7 @@ #include extern uint8_t* memory_get_ram_ptr(void); +extern uint32_t memory_get_ram_bytes(void); extern uint32_t i_stat; extern uint32_t i_mask; extern void timers_get_snapshot(uint16_t counter[3], uint32_t mode[3], @@ -55,8 +56,6 @@ static uint32_t digest_module(uint32_t (*bytes_fn)(void), void (*write_fn)(uint8 return crc32_compute(buf, n); } -#define NP_RAM_SIZE (2u * 1024u * 1024u) - uint32_t netplay_cdrom_digest(void) { static uint8_t *buf; @@ -159,7 +158,7 @@ void netplay_core_digest_parts(const CPUState* cpu, NetplayCoreParts* out) ram = memory_get_ram_ptr(); if (ram) - crc_ram = crc32_update(crc_ram, ram, NP_RAM_SIZE); + crc_ram = crc32_update(crc_ram, ram, memory_get_ram_bytes()); wc = dirty_ram_get_bitmap_word_count(); for (i = 0; i < wc; i++) { diff --git a/runtime/tests/test_mod_runtime.cpp b/runtime/tests/test_mod_runtime.cpp index 9b66052f..42f93cf1 100644 --- a/runtime/tests/test_mod_runtime.cpp +++ b/runtime/tests/test_mod_runtime.cpp @@ -60,6 +60,7 @@ extern "C" uint32_t psx_mod_memory_alloc(uint32_t, uint32_t) { return 0; } extern "C" uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t, uint32_t) { return 0; } +extern "C" void psx_ram_reset_size_request(void) {} extern "C" int psx_ws_x_margin(void) { return 0; } /* Stand-in for the GPU's display geometry. psx_mod_display_width/height must diff --git a/runtime/tests/test_psx_ram_runtime_map.c b/runtime/tests/test_psx_ram_runtime_map.c new file mode 100644 index 00000000..1e470cee --- /dev/null +++ b/runtime/tests/test_psx_ram_runtime_map.c @@ -0,0 +1,41 @@ +#include "psx_ram.h" + +#include +#include + +uint32_t g_psx_ram_size = PSX_RAM_2MB; +uint32_t g_psx_ram_mask = PSX_RAM_2MB - 1u; +uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS]; + +static int check(int cond, const char *label) { + if (!cond) { + fprintf(stderr, "FAIL: %s\n", label); + return 0; + } + return 1; +} + +int main(void) { + const uint32_t high = 0x00201000u; + const uint32_t high_page = high >> 12; + uint32_t i = high_page - PSX_RAM_HIGH_PAGE0; + int ok = 1; + + ok &= check(psx_ram_map_read(high) == 0x00001000u, + "retail maps high mirror to low RAM"); + ok &= check(psx_ram_canon_code_addr_inline(0x80201000u) == 0x80001000u, + "retail folds high code PC to low mirror"); + + g_psx_ram_size = PSX_RAM_8MB; + g_psx_ram_mask = PSX_RAM_8MB - 1u; + ok &= check(psx_ram_map_read(high) == 0x00001000u, + "unregistered 8 MB high page still mirrors low RAM"); + + g_psx_ram_high_unique[i >> 5] |= 1u << (i & 31u); + ok &= check(psx_ram_map_read(high) == high, + "registered 8 MB high page maps uniquely"); + ok &= check(psx_ram_canon_code_addr_inline(0x80201000u) == 0x80201000u, + "registered high code PC remains unique"); + + return ok ? 0 : 1; +}