mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
runtime: route every RAM consumer through one live 2/8 MiB geometry
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -266,10 +266,13 @@ uint32_t BiosAddressModel::rom_keyed_ram_hi_incl() const {
|
||||
|
||||
std::string BiosAddressModel::emit_normalize_c() const {
|
||||
std::string out;
|
||||
out += "extern uint32_t psx_ram_canon_code_addr(uint32_t addr);\n";
|
||||
out += "static uint32_t normalize(uint32_t addr) {\n";
|
||||
out += " uint32_t phys = addr & 0x1FFFFFFFu;\n";
|
||||
out += " /* PSX main RAM: 2 MiB mirrored through physical 0x007FFFFF. */\n";
|
||||
out += " if (phys < 0x00800000u) phys &= 0x001FFFFFu;\n";
|
||||
out += " /* PSX main RAM decode window [0, 0x00800000): retail 2 MiB mirrors\n";
|
||||
out += " * fold onto the low bank; the opt-in 8 MB map keeps registered high\n";
|
||||
out += " * pages unique. Only high-window keys need the live-geometry call. */\n";
|
||||
out += " if (phys - 0x00200000u < 0x00600000u) phys = psx_ram_canon_code_addr(phys);\n";
|
||||
for (const BiosAddrCopy& c : copies_) {
|
||||
if (c.key_is_ram) {
|
||||
out += fmt::format(" /* {}: ROM 0x{:X}+ -> RAM 0x{:X}+ */\n",
|
||||
|
||||
@@ -2190,7 +2190,9 @@ 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_addr = psx_ram_canon_code_addr(addr);\n";
|
||||
out += " /* Live-geometry code address: only 2nd-4th-MiB keys can fold. */\n";
|
||||
out += " uint32_t game_addr = ((addr & 0x1FFFFFFFu) - 0x00200000u < 0x00600000u)\n";
|
||||
out += " ? psx_ram_canon_code_addr(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";
|
||||
|
||||
@@ -1483,7 +1483,9 @@ int main(int argc, char** argv) {
|
||||
uint32_t game_text_end = game_text_start + exe->code_size();
|
||||
ds << "int psx_game_address_in_text(uint32_t addr) {\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 << " uint32_t phys = addr & 0x1FFFFFFFu;\n";
|
||||
ds << " if (phys - 0x00200000u < 0x00600000u)\n";
|
||||
ds << " 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";
|
||||
@@ -1641,7 +1643,8 @@ int main(int argc, char** argv) {
|
||||
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 << " if ((addr & 0x1FFFFFFFu) - 0x00200000u < 0x00600000u)\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);
|
||||
|
||||
@@ -141,6 +141,14 @@ static void psx_check_interrupts_dispatch_entry(CPUState* cpu, uint32_t a) {
|
||||
(void)cpu; (void)a; irqs++;
|
||||
}
|
||||
int psx_vsync_query_hle_try(CPUState* cpu, uint32_t a) { (void)cpu; (void)a; return 0; }
|
||||
/* Retail live geometry (runtime psx_ram_geometry.c with the 8 MB mod off):
|
||||
* 2nd-4th MiB of the decode window fold onto the low 2 MiB. */
|
||||
uint32_t psx_ram_canon_code_addr(uint32_t addr) {
|
||||
uint32_t phys = addr & 0x1FFFFFFFu;
|
||||
if (phys >= 0x00200000u && phys < 0x00800000u)
|
||||
return (addr & 0xE0000000u) | (phys & 0x001FFFFFu);
|
||||
return addr;
|
||||
}
|
||||
'''
|
||||
harness += "\n".join(f"#define {fn} dummy" for fn in funcs) + "\n" + fragment
|
||||
harness += r'''
|
||||
@@ -155,6 +163,9 @@ int main(void) {
|
||||
expected = &k_psx_game_dispatch[i];
|
||||
uint32_t addr = aliases[a] | phys;
|
||||
assert(psx_game_find_entry(addr) == expected);
|
||||
// Retail RAM mirrors resolve to the same compiled entry.
|
||||
for (uint32_t m = 1; m < 4; ++m)
|
||||
assert(psx_game_find_entry(addr + m * 0x00200000u) == expected);
|
||||
if (!expected) continue;
|
||||
// A previously resolved PC must NOT cache its live-byte verdict.
|
||||
allowed = 0; cpu.pc = 0xDEADBEEFu;
|
||||
|
||||
+11
-6
@@ -35,7 +35,8 @@ if(BUILD_TESTING)
|
||||
target_compile_options(ws_world_scene_test PRIVATE -O2 -flto -ffunction-sections -fdata-sections)
|
||||
target_link_options(ws_world_scene_test PRIVATE -flto -Wl,--gc-sections)
|
||||
add_test(NAME ws_world_scene_test COMMAND ws_world_scene_test)
|
||||
add_executable(mod_memory_snapshot_test tests/test_mod_memory_snapshot.c)
|
||||
add_executable(mod_memory_snapshot_test tests/test_mod_memory_snapshot.c
|
||||
src/psx_ram_geometry.c)
|
||||
target_include_directories(mod_memory_snapshot_test PRIVATE include src)
|
||||
target_compile_options(mod_memory_snapshot_test PRIVATE -O2 -flto -ffunction-sections -fdata-sections)
|
||||
target_link_options(mod_memory_snapshot_test PRIVATE -flto -Wl,--gc-sections)
|
||||
@@ -140,14 +141,14 @@ if(BUILD_TESTING)
|
||||
PRIVATE include)
|
||||
if(_ram_geometry STREQUAL "expanded")
|
||||
target_compile_definitions(psx_memory_geometry_${_ram_geometry}_test
|
||||
PRIVATE PSX_MAIN_RAM_BYTES=0x00800000u)
|
||||
PRIVATE PSX_MEMORY_GEOMETRY_TEST_EXPANDED=1)
|
||||
endif()
|
||||
add_test(NAME psx_memory_geometry_${_ram_geometry}_test
|
||||
COMMAND psx_memory_geometry_${_ram_geometry}_test)
|
||||
endforeach()
|
||||
|
||||
add_executable(psx_ram_runtime_map_test
|
||||
tests/test_psx_ram_runtime_map.c)
|
||||
tests/test_psx_ram_runtime_map.c src/psx_ram_geometry.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)
|
||||
|
||||
@@ -205,6 +206,7 @@ if(BUILD_TESTING)
|
||||
tests/test_gte_register_access.cpp
|
||||
src/gte.cpp
|
||||
src/pgxp.cpp
|
||||
src/psx_ram_geometry.c
|
||||
)
|
||||
target_include_directories(gte_register_access_test PRIVATE include)
|
||||
target_compile_definitions(gte_register_access_test PRIVATE
|
||||
@@ -219,7 +221,8 @@ if(BUILD_TESTING)
|
||||
# suppression bracket, invalidate-all, and GetPreciseVertex safeguards
|
||||
# (truncation agreement + tolerance clamp). White-box: includes src/pgxp.cpp
|
||||
# and stubs the gte.cpp fallback-cache extern.
|
||||
add_executable(pgxp_test tests/test_pgxp.cpp src/pgxp.cpp)
|
||||
add_executable(pgxp_test tests/test_pgxp.cpp src/pgxp.cpp
|
||||
src/psx_ram_geometry.c)
|
||||
target_include_directories(pgxp_test PRIVATE include)
|
||||
add_test(NAME pgxp_test COMMAND pgxp_test)
|
||||
|
||||
@@ -327,7 +330,8 @@ if(BUILD_TESTING)
|
||||
|
||||
add_executable(overlay_capture_retry_test
|
||||
tests/test_overlay_capture_retry.cpp
|
||||
src/overlay_capture.c)
|
||||
src/overlay_capture.c
|
||||
src/psx_ram_geometry.c)
|
||||
target_include_directories(overlay_capture_retry_test PRIVATE
|
||||
include ${PSX_SDL_INCLUDE_DIRS})
|
||||
target_compile_definitions(overlay_capture_retry_test PRIVATE
|
||||
@@ -427,7 +431,8 @@ if(BUILD_TESTING)
|
||||
|
||||
add_executable(text_xlate_fix_only_test
|
||||
tests/test_text_xlate_fix_only.cpp
|
||||
src/text_xlate.cpp)
|
||||
src/text_xlate.cpp
|
||||
src/psx_ram_geometry.c)
|
||||
target_include_directories(text_xlate_fix_only_test PRIVATE
|
||||
include
|
||||
../recompiler/lib/toml11)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define PSXRECOMP_DIRTY_RAM_INTERP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "psx_memory.h"
|
||||
#include "cpu_state.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -64,7 +65,7 @@ void dirty_ram_irq_ambient_resync_after_restore(void);
|
||||
* Kernel RAM [0x00000, 0x10000): the BIOS part-2 image relocated to RAM
|
||||
* plus install-at-runtime stubs (e.g. the SIO data-byte stub at 0xCF0).
|
||||
* Dirty-tracked per CPU store (dirty_ram_mark_kernel_write).
|
||||
* Overlay region [OVERLAY_REGION_FLOOR, RAM_SIZE): game overlays loaded by
|
||||
* Overlay region [OVERLAY_REGION_FLOOR, live RAM end): game overlays loaded by
|
||||
* CD DMA (dirty_ram_mark_executable_range).
|
||||
*
|
||||
* Main-EXE text [0x10000, OVERLAY_REGION_FLOOR): CLEAN pages are statically
|
||||
@@ -258,15 +259,17 @@ extern DirtyRamPcEntry g_dirty_ram_pc_table[DIRTY_RAM_PC_TABLE_SIZE];
|
||||
/* Every aligned main-RAM word is a possible instruction PC. Execution
|
||||
* coverage only needs presence, not a hit count, so record it in a direct
|
||||
* bitmap instead of probing a large hash table for every retired instruction.
|
||||
* This covers all 524,288 RAM words (the old 262K-entry hash could saturate)
|
||||
* and is also the execution-verified seed source used by overlay_capture. */
|
||||
#define DIRTY_RAM_EXEC_WORD_COUNT ((2u * 1024u * 1024u) / 4u)
|
||||
* This covers every word of the host RAM backing (the old 262K-entry hash
|
||||
* could saturate) -- retail 2 MiB and the opt-in 8 MiB map alike -- and is
|
||||
* also the execution-verified seed source used by overlay_capture. */
|
||||
#define DIRTY_RAM_EXEC_WORD_COUNT (PSX_MAIN_RAM_BACKING_BYTES / 4u)
|
||||
#define DIRTY_RAM_EXEC_BITMAP_WORDS ((DIRTY_RAM_EXEC_WORD_COUNT + 31u) / 32u)
|
||||
extern uint32_t g_dirty_ram_exec_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS];
|
||||
/* One bit per 4 KiB page. RAM writes use this as a one-test stale-evidence
|
||||
* guard; they clear that page's capture bits rather than serializing from the
|
||||
* universal store hot path. */
|
||||
#define DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS 16u
|
||||
#define DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS \
|
||||
(((PSX_MAIN_RAM_BACKING_BYTES / 4096u) + 31u) / 32u)
|
||||
extern uint32_t g_dirty_ram_exec_page_bitmap[DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS];
|
||||
/* Presence-only companion for interpreted block/dispatch entries. The richer
|
||||
* counter table remains for telemetry, while capture can snapshot/reset this
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define PSXRECOMP_MOD_MEMORY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "psx_ram.h"
|
||||
#include "psx_memory.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -44,7 +44,9 @@ 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 psx_ram_map_read(canonical) & ~3u;
|
||||
/* 24-bit tag bits above the 8 MiB decode window are ignored, then the
|
||||
* live geometry folds (retail: exactly the DMAC's 0x1FFFFC). */
|
||||
return psx_ram_canonical_offset(canonical) & ~3u;
|
||||
}
|
||||
|
||||
uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t size, uint32_t alignment);
|
||||
|
||||
@@ -34,7 +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"
|
||||
#include "psx_memory.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@@ -7,41 +7,111 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* One compile-time main-RAM geometry contract for every runtime subsystem.
|
||||
* A target selects retail 2 MiB mirroring or unique 8 MiB decoding. */
|
||||
/* One main-RAM geometry contract for every runtime subsystem.
|
||||
*
|
||||
* Retail DRAM is 2 MiB, mirrored four times across the 8 MiB decode window
|
||||
* (physical 0x00000000-0x007FFFFF of every segment). The opt-in 8 MB main RAM
|
||||
* enhancement (builtin mod psx.enhancement.8mb-ram) switches the LIVE geometry
|
||||
* to unique decoding of registered high pages before memory_init(); with the
|
||||
* mod off the live geometry is retail and every helper below folds exactly as
|
||||
* retail hardware does.
|
||||
*
|
||||
* Host storage indexed by a RAM offset is sized with PSX_MAIN_RAM_BACKING_BYTES
|
||||
* (the largest geometry); bounds that describe the guest-visible RAM use the
|
||||
* live size (psx_ram_live_bytes()). Serialized/digested RAM state covers the
|
||||
* live size only, so retail savestates and netplay digests are unchanged. */
|
||||
#define PSX_MAIN_RAM_RETAIL_BYTES 0x00200000u
|
||||
#define PSX_MAIN_RAM_EXPANDED_BYTES 0x00800000u
|
||||
#define PSX_MAIN_RAM_WINDOW_BYTES 0x00800000u
|
||||
#define PSX_MAIN_RAM_BACKING_BYTES PSX_MAIN_RAM_EXPANDED_BYTES
|
||||
|
||||
#ifndef PSX_MAIN_RAM_BYTES
|
||||
#define PSX_MAIN_RAM_BYTES PSX_MAIN_RAM_RETAIL_BYTES
|
||||
#endif
|
||||
/* Live geometry (memory.c). Retail until a trusted activation plugin requests
|
||||
* expanded RAM (psx_mod_set_main_ram_8mb) and memory_init() applies it. */
|
||||
extern uint32_t g_psx_ram_size;
|
||||
extern uint32_t g_psx_ram_mask;
|
||||
|
||||
#if PSX_MAIN_RAM_BYTES != PSX_MAIN_RAM_RETAIL_BYTES && \
|
||||
PSX_MAIN_RAM_BYTES != PSX_MAIN_RAM_EXPANDED_BYTES
|
||||
#error "PSX_MAIN_RAM_BYTES must select 2 MiB or 8 MiB"
|
||||
#endif
|
||||
uint32_t memory_get_ram_bytes(void);
|
||||
int psx_ram_8mb_active(void);
|
||||
void psx_ram_reset_size_request(void);
|
||||
/* memory_init(): latch the requested geometry for this boot. */
|
||||
void psx_ram_apply_size_request(void);
|
||||
|
||||
#define PSX_MAIN_RAM_MASK (PSX_MAIN_RAM_BYTES - 1u)
|
||||
#define PSX_MAIN_RAM_WORD_MASK (PSX_MAIN_RAM_MASK & ~3u)
|
||||
/* High pages [2 MiB, 8 MiB) decode uniquely only while registered; unregistered
|
||||
* high pages keep aliasing the low 2 MiB even in expanded mode. */
|
||||
#define PSX_RAM_HIGH_PAGE0 (PSX_MAIN_RAM_RETAIL_BYTES >> 12)
|
||||
#define PSX_RAM_HIGH_PAGES \
|
||||
((PSX_MAIN_RAM_EXPANDED_BYTES - PSX_MAIN_RAM_RETAIL_BYTES) >> 12)
|
||||
#define PSX_RAM_HIGH_BITWORDS ((PSX_RAM_HIGH_PAGES + 31u) / 32u)
|
||||
extern uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS];
|
||||
|
||||
/* Strip KUSEG/KSEG0/KSEG1 and canonicalize within the 8 MiB DRAM decode
|
||||
* window. Retail targets fold all four aliases; expanded targets preserve all
|
||||
* 23 address bits. */
|
||||
static inline uint32_t psx_ram_canonical_offset(uint32_t address) {
|
||||
return (address & 0x1FFFFFFFu) & PSX_MAIN_RAM_MASK;
|
||||
static inline uint32_t psx_ram_live_bytes(void) { return g_psx_ram_size; }
|
||||
|
||||
static inline int psx_ram_high_page_unique(uint32_t page) {
|
||||
uint32_t i, bit;
|
||||
if (page < PSX_RAM_HIGH_PAGE0 ||
|
||||
page >= (PSX_MAIN_RAM_EXPANDED_BYTES >> 12))
|
||||
return 1;
|
||||
i = page - PSX_RAM_HIGH_PAGE0;
|
||||
bit = 1u << (i & 31u);
|
||||
return (g_psx_ram_high_unique[i >> 5] & bit) != 0;
|
||||
}
|
||||
|
||||
/* Canonical RAM offset of a physical address inside the decode window.
|
||||
* Addresses at/above the window are returned unchanged (not RAM). */
|
||||
static inline uint32_t psx_ram_map_read(uint32_t phys) {
|
||||
phys &= 0x1FFFFFFFu;
|
||||
if (phys >= PSX_MAIN_RAM_WINDOW_BYTES)
|
||||
return phys;
|
||||
if (g_psx_ram_size <= PSX_MAIN_RAM_RETAIL_BYTES)
|
||||
return phys & (PSX_MAIN_RAM_RETAIL_BYTES - 1u);
|
||||
if (phys < PSX_MAIN_RAM_RETAIL_BYTES)
|
||||
return phys;
|
||||
if (psx_ram_high_page_unique(phys >> 12))
|
||||
return phys;
|
||||
return phys & (PSX_MAIN_RAM_RETAIL_BYTES - 1u);
|
||||
}
|
||||
|
||||
static inline uint32_t psx_ram_map_write(uint32_t phys) {
|
||||
return psx_ram_map_read(phys);
|
||||
}
|
||||
|
||||
/* Strip KUSEG/KSEG0/KSEG1 and canonicalize within the 8 MiB DRAM decode
|
||||
* window through the live map. Retail folds all four aliases; expanded mode
|
||||
* preserves every decoded bit of a registered high page. */
|
||||
static inline uint32_t psx_ram_canonical_offset(uint32_t address) {
|
||||
return psx_ram_map_read((address & 0x1FFFFFFFu) &
|
||||
(PSX_MAIN_RAM_WINDOW_BYTES - 1u));
|
||||
}
|
||||
|
||||
/* Resolve [address, address+width) to a live RAM offset. Fails for anything
|
||||
* outside the decode window, zero width, or a span past the live RAM end. */
|
||||
static inline int psx_ram_resolve(uint32_t address, uint32_t width,
|
||||
uint32_t *offset) {
|
||||
const uint32_t phys = address & 0x1FFFFFFFu;
|
||||
uint32_t off;
|
||||
if (phys >= PSX_MAIN_RAM_EXPANDED_BYTES || width == 0u) return 0;
|
||||
off = phys & PSX_MAIN_RAM_MASK;
|
||||
if (width > PSX_MAIN_RAM_BYTES - off) return 0;
|
||||
if (phys >= PSX_MAIN_RAM_WINDOW_BYTES || width == 0u) return 0;
|
||||
off = psx_ram_map_read(phys);
|
||||
if (off >= g_psx_ram_size || width > g_psx_ram_size - off) return 0;
|
||||
if (offset) *offset = off;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Code-address form: keep the segment bits, fold only aliased mirrors. */
|
||||
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_MAIN_RAM_WINDOW_BYTES && phys >= PSX_MAIN_RAM_RETAIL_BYTES &&
|
||||
(g_psx_ram_size <= PSX_MAIN_RAM_RETAIL_BYTES ||
|
||||
!psx_ram_high_page_unique(phys >> 12)))
|
||||
phys &= (PSX_MAIN_RAM_RETAIL_BYTES - 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);
|
||||
/* After a bulk RAM restore into `ram` (the host backing). */
|
||||
void psx_ram_resync_high_after_restore(const uint8_t *ram);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
#ifndef PSX_RAM_H
|
||||
#define PSX_RAM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
@@ -2,6 +2,7 @@
|
||||
#define PSX_WS_PRIMITIVE_ROLES_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "psx_memory.h"
|
||||
|
||||
/* Title-supplied packet identity, independent of draw order and sprite tags.
|
||||
* Keys name PsyQ P_TAG headers; GPU lookup names the following command word. */
|
||||
@@ -14,7 +15,7 @@ typedef struct {
|
||||
|
||||
static inline WsPrimitiveRole* ws_role_write(WsPrimitiveRole* tags,
|
||||
uint32_t primitive, uint32_t now) {
|
||||
uint32_t key = primitive & 0x1FFFFCu;
|
||||
uint32_t key = psx_ram_canonical_offset(primitive) & ~3u;
|
||||
if (!key) return NULL;
|
||||
uint32_t slot = (key >> 2) & (WS_ROLE_BUCKETS - 1u), victim = slot;
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
@@ -35,7 +36,7 @@ static inline WsPrimitiveRole* ws_role_write(WsPrimitiveRole* tags,
|
||||
static inline const WsPrimitiveRole* ws_role_read(const WsPrimitiveRole* tags,
|
||||
uint32_t source, uint32_t now) {
|
||||
if (source == 0xFFFFFFFFu) return NULL;
|
||||
uint32_t key = (source - 4u) & 0x1FFFFCu;
|
||||
uint32_t key = psx_ram_canonical_offset(source - 4u) & ~3u;
|
||||
if (!key) return NULL;
|
||||
uint32_t slot = (key >> 2) & (WS_ROLE_BUCKETS - 1u);
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
|
||||
@@ -330,6 +330,7 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_sdl_audio.cpp
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_stick.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/memory.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_ram_geometry.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/kernel_patch_ranges.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/guest_tty.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/gpu.c
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
*/
|
||||
|
||||
#include "bios_hle.h"
|
||||
#include "psx_memory.h"
|
||||
|
||||
#include "cpu_state.h"
|
||||
#include "psx_cycles.h"
|
||||
@@ -100,7 +101,7 @@ static void hle_record(uint32_t vector, uint32_t fn, const CPUState* cpu,
|
||||
* load-bearing for event-consumption races (MMX6 card check). */
|
||||
uint32_t pcb = cpu->read_word(0xA0000108u);
|
||||
uint32_t pcb_phys = pcb & 0x1FFFFFFFu;
|
||||
uint32_t tcb = (pcb != 0u && pcb_phys < 0x200000u)
|
||||
uint32_t tcb = (pcb != 0u && pcb_phys < psx_ram_live_bytes())
|
||||
? cpu->read_word(0xA0000000u | pcb_phys) : 0u;
|
||||
extern int psx_get_in_exception(void);
|
||||
uint8_t in_exc = (uint8_t)(psx_get_in_exception() ? 1u : 0u);
|
||||
|
||||
@@ -9,7 +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 "psx_memory.h"
|
||||
#include "pst_wire.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -550,7 +550,7 @@ static int apply_section(uint32_t tag, const uint8_t* p, uint32_t len,
|
||||
{
|
||||
extern void psx_kernel_bless_note_range(uint32_t phys, uint32_t l);
|
||||
psx_kernel_bless_note_range(0, memory_get_ram_bytes());
|
||||
psx_ram_resync_high_after_restore();
|
||||
psx_ram_resync_high_after_restore(memory_get_ram_ptr());
|
||||
}
|
||||
return 1;
|
||||
case BS_SEC_SPAD:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* and compiled-vs-compiled gates.
|
||||
*/
|
||||
#include "cosim_state.h"
|
||||
#include "psx_memory.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -50,18 +51,21 @@ extern uint64_t interrupts_cosim_hash(uint64_t seed);
|
||||
/* renderer-agnostic VRAM readback (sw renderer = cheap memcpy). */
|
||||
extern void gr_vram_transfer_out(int x, int y, int w, int h, uint16_t *dst);
|
||||
|
||||
#define RAM_SIZE (2u * 1024u * 1024u)
|
||||
/* Hash the live RAM (retail 2 MiB unless the 8 MB mod is on); page tables are
|
||||
* sized for the host backing. */
|
||||
#define RAM_SIZE psx_ram_live_bytes()
|
||||
#define SPAD_SIZE (1024u)
|
||||
#define PAGE 4096u
|
||||
#define RAM_PAGES (RAM_SIZE / PAGE) /* 512 */
|
||||
#define RAM_PAGES_CAP (PSX_MAIN_RAM_BACKING_BYTES / PAGE)
|
||||
#define RAM_PAGES (RAM_SIZE / PAGE)
|
||||
#define VRAM_W 1024
|
||||
#define VRAM_H 512
|
||||
#define VRAM_SIZE ((uint32_t)(VRAM_W * VRAM_H * 2)) /* 1 MB */
|
||||
#define VRAM_PAGES (VRAM_SIZE / PAGE) /* 256 */
|
||||
|
||||
/* ---- incremental RAM page hashes ---- */
|
||||
static uint64_t s_ram_page_h[RAM_PAGES];
|
||||
static uint8_t s_ram_page_dirty[RAM_PAGES];
|
||||
static uint64_t s_ram_page_h[RAM_PAGES_CAP];
|
||||
static uint8_t s_ram_page_dirty[RAM_PAGES_CAP];
|
||||
static int s_ram_all_dirty = 1;
|
||||
|
||||
/* ---- VRAM: full-recompute when dirty (draws are periodic, not per-block) ---- */
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <time.h>
|
||||
|
||||
#include "cpu_state.h"
|
||||
#include "psx_memory.h"
|
||||
#include "psx_bss.h"
|
||||
#include "crash_trace.h"
|
||||
#include "autocompile.h" /* autocompile_degraded_reason — stamp a degraded
|
||||
@@ -166,8 +167,9 @@ static int crash_peek_guest(uint32_t vaddr, uint8_t *dst, int len) {
|
||||
return len;
|
||||
}
|
||||
if (phys < 0x00800000u && g_psx_ram) {
|
||||
uint32_t folded = phys & 0x1FFFFFu;
|
||||
if (folded + (uint32_t)len > 0x200000u) len = (int)(0x200000u - folded);
|
||||
uint32_t folded = psx_ram_canonical_offset(phys);
|
||||
if (folded + (uint32_t)len > psx_ram_live_bytes())
|
||||
len = (int)(psx_ram_live_bytes() - folded);
|
||||
if (len <= 0) return 0;
|
||||
memcpy(dst, g_psx_ram + folded, (size_t)len);
|
||||
return len;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
* functions are data transforms; a GTE-using function must not be listed).
|
||||
*/
|
||||
#include "data_shards.h"
|
||||
#include "psx_memory.h"
|
||||
#include "psx_cycles.h"
|
||||
#include "interrupts.h"
|
||||
#include "crc32.h"
|
||||
@@ -47,7 +48,9 @@ extern void psx_write_word(uint32_t addr, uint32_t val);
|
||||
extern void psx_write_byte(uint32_t addr, uint8_t val);
|
||||
extern void dirty_ram_mark_executable_range(uint32_t addr, uint32_t len);
|
||||
|
||||
#define DS_RAM_BYTES (2u * 1024u * 1024u)
|
||||
/* Unified index space: RAM offsets cover the host backing (the opt-in 8 MB
|
||||
* map's unique high banks included), then the scratchpad. */
|
||||
#define DS_RAM_BYTES PSX_MAIN_RAM_BACKING_BYTES
|
||||
#define DS_SP_BYTES 1024u
|
||||
#define DS_SPACE (DS_RAM_BYTES + DS_SP_BYTES) /* unified index space */
|
||||
#define DS_PAGE_SHIFT 12
|
||||
@@ -139,7 +142,7 @@ void ds_init(const char* cache_dir, const char* game_id) {
|
||||
/* virtual addr -> unified index, or -1 (untracked: ROM/open bus), or -2 (MMIO) */
|
||||
static int64_t ds_index_of(uint32_t addr) {
|
||||
uint32_t phys = addr & 0x1FFFFFFFu;
|
||||
if (phys < 0x00800000u) return (int64_t)(phys & (DS_RAM_BYTES - 1u));
|
||||
if (phys < PSX_MAIN_RAM_WINDOW_BYTES) return (int64_t)psx_ram_canonical_offset(phys);
|
||||
if (phys >= 0x1F800000u && phys < 0x1F800000u + DS_SP_BYTES)
|
||||
return (int64_t)(DS_RAM_BYTES + (phys - 0x1F800000u));
|
||||
if (phys >= 0x1F801000u && phys < 0x1F803000u) return -2; /* MMIO */
|
||||
|
||||
+15
-14
@@ -41,6 +41,7 @@
|
||||
#include "psx_cycles.h"
|
||||
#include "timers.h"
|
||||
#include "dirty_ram_interp.h"
|
||||
#include "psx_memory.h"
|
||||
#include "card_read_summary.h"
|
||||
#include "card_data_writes.h"
|
||||
#include "crash_trace.h"
|
||||
@@ -162,7 +163,7 @@ uint64_t s_frame_count = 0;
|
||||
* frame, found in O(1) instead of O(n) function guesses. wr_hash vs pc_hash
|
||||
* classifies the fork: pc differs but wr matches => same writes via a different
|
||||
* control path; wr differs => actual state divergence. Reusable for any title.
|
||||
* Hashed over main RAM (phys < 0x200000) only — game state lives there; MMIO/
|
||||
* Hashed over live main RAM (phys < live RAM end) only — game state lives there; MMIO/
|
||||
* scratchpad churn (device polling) would add benign cross-backend noise. */
|
||||
uint64_t g_fp_wr_hash = 1469598103934665603ULL; /* FNV-1a-style seed (main RAM) */
|
||||
uint64_t g_fp_pc_hash = 1469598103934665603ULL; /* store-PC path sig (main RAM) */
|
||||
@@ -179,7 +180,7 @@ static PSX_BSS FpEntry s_fp_ring[FP_RING_CAP];
|
||||
static uint32_t s_fp_head = 0;
|
||||
static uint64_t s_fp_total = 0;
|
||||
|
||||
/* Record a guest WRITE into the per-frame fingerprint. Main RAM (phys<0x200000)
|
||||
/* Record a guest WRITE into the per-frame fingerprint. Live main RAM
|
||||
* feeds the proven wr/pc hashes. Scratchpad (0x1F800000..0x1F8003FF) feeds a
|
||||
* SEPARATE sp_hash — it was previously dropped entirely (the blind spot that
|
||||
* hid a possible pre-1823 scratchpad-state fork), but folding it into wr_hash
|
||||
@@ -196,7 +197,7 @@ static inline void fp_record_write(uint32_t phys, uint32_t val, uint32_t pc)
|
||||
g_fp_sp_count++;
|
||||
return;
|
||||
}
|
||||
if (phys >= 0x200000u) return; /* main RAM only */
|
||||
if (phys >= psx_ram_live_bytes()) return; /* main RAM only */
|
||||
uint64_t h = g_fp_wr_hash;
|
||||
h = (h ^ (uint64_t)phys) * 1099511628211ULL;
|
||||
h = (h ^ (uint64_t)val) * 1099511628211ULL;
|
||||
@@ -747,7 +748,7 @@ static uint32_t trace_read_word(CPUState *cpu, uint32_t addr)
|
||||
{
|
||||
(void)cpu;
|
||||
uint32_t phys = addr & 0x1FFFFFFFu;
|
||||
if (phys > 0x001FFFFCu &&
|
||||
if (phys > psx_ram_live_bytes() - 4u &&
|
||||
(phys < 0x1F800000u || phys > 0x1F8003FCu)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -764,7 +765,7 @@ static uint32_t trace_read_half(CPUState *cpu, uint32_t addr)
|
||||
{
|
||||
(void)cpu;
|
||||
uint32_t phys = addr & 0x1FFFFFFFu;
|
||||
if (phys > 0x001FFFFEu &&
|
||||
if (phys > psx_ram_live_bytes() - 2u &&
|
||||
(phys < 0x1F800000u || phys > 0x1F8003FEu)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -1367,7 +1368,7 @@ static void evcb_snapshot_capture(EvCBTag tag, uint64_t fn_entry_seq) {
|
||||
|
||||
/* Convert RAM base ptr to RAM offset (kernel uses cached + uncached
|
||||
* mirrors; mask off region bits). */
|
||||
uint32_t base_ram = base_ptr & 0x001FFFFFu;
|
||||
uint32_t base_ram = psx_ram_canonical_offset(base_ptr);
|
||||
|
||||
EvCBSnapshot *e = &s_evcb_ring[s_evcb_ring_seq % EVCB_RING_CAP];
|
||||
e->seq = s_evcb_ring_seq;
|
||||
@@ -1391,7 +1392,7 @@ static void evcb_snapshot_capture(EvCBTag tag, uint64_t fn_entry_seq) {
|
||||
|
||||
for (uint32_t i = 0; i < n_entries; i++) {
|
||||
uint32_t off = base_ram + i * EVCB_ENTRY_SIZE;
|
||||
if (off + EVCB_ENTRY_SIZE > 0x00200000u) break;
|
||||
if (off + EVCB_ENTRY_SIZE > psx_ram_live_bytes()) break;
|
||||
e->entries[i].cls = evcb_read_u32_ram(off + 0);
|
||||
e->entries[i].status = evcb_read_u32_ram(off + 4);
|
||||
e->entries[i].spec = evcb_read_u32_ram(off + 8);
|
||||
@@ -1702,7 +1703,7 @@ static void call_focus_record(uint32_t func_addr)
|
||||
e->obj_18 = psx_read_word(phys + 0x18u);
|
||||
e->obj_30 = psx_read_word(phys + 0x30u);
|
||||
uint32_t out_phys = e->obj_30 & 0x1FFFFFFFu;
|
||||
if (out_phys < 0x00200000u - 1u) {
|
||||
if (out_phys < psx_ram_live_bytes() - 1u) {
|
||||
e->obj_30_0 = psx_read_byte(out_phys);
|
||||
e->obj_30_1 = psx_read_byte(out_phys + 1u);
|
||||
}
|
||||
@@ -5171,9 +5172,9 @@ static void handle_read_ram(int id, const char *json)
|
||||
uint32_t addr = hex_to_u32(addr_str);
|
||||
int len = json_get_int(json, "len", 1);
|
||||
if (len < 1) len = 1;
|
||||
/* Effectively the entire 2 MB RAM in one shot. Response uses a heap-
|
||||
* sized envelope so we don't truncate. */
|
||||
if (len > 0x200000) len = 0x200000;
|
||||
/* Effectively the entire live RAM (2 MB, or 8 MB with the opt-in map) in
|
||||
* one shot. Response uses a heap-sized envelope so we don't truncate. */
|
||||
if (len > (int)psx_ram_live_bytes()) len = (int)psx_ram_live_bytes();
|
||||
|
||||
/* Heap buffer for hex chars + JSON envelope. Each byte = 2 hex chars. */
|
||||
size_t env = 256;
|
||||
@@ -7278,13 +7279,13 @@ static void handle_evcb_snapshot(int id, const char *json)
|
||||
snap.flag_755A = (uint32_t)psx_read_byte(0x0000755A);
|
||||
snap.flag_75C0 = evcb_read_u32_ram(0x000075C0);
|
||||
snap.frame = (uint32_t)s_frame_count;
|
||||
uint32_t base_ram = base_ptr & 0x001FFFFFu;
|
||||
uint32_t base_ram = psx_ram_canonical_offset(base_ptr);
|
||||
uint32_t n_entries = (total_bytes / EVCB_ENTRY_SIZE);
|
||||
if (n_entries > EVCB_MAX_ENTRIES) n_entries = EVCB_MAX_ENTRIES;
|
||||
snap.entry_count = n_entries;
|
||||
for (uint32_t i = 0; i < n_entries; i++) {
|
||||
uint32_t off = base_ram + i * EVCB_ENTRY_SIZE;
|
||||
if (off + EVCB_ENTRY_SIZE > 0x00200000u) break;
|
||||
if (off + EVCB_ENTRY_SIZE > psx_ram_live_bytes()) break;
|
||||
snap.entries[i].cls = evcb_read_u32_ram(off + 0);
|
||||
snap.entries[i].status = evcb_read_u32_ram(off + 4);
|
||||
snap.entries[i].spec = evcb_read_u32_ram(off + 8);
|
||||
@@ -9589,7 +9590,7 @@ void debug_server_trace_write_check(uint32_t phys, uint32_t old_val,
|
||||
fp_record_write(phys, new_val, g_debug_last_store_pc);
|
||||
{
|
||||
uint32_t ra = debug_cpu_ptr ? debug_cpu_ptr->gpr[31] : 0;
|
||||
if (phys < 0x200000u)
|
||||
if (phys < psx_ram_live_bytes())
|
||||
rec_event(REC_KIND_RAM_W, phys, new_val, g_debug_last_store_pc, ra);
|
||||
else if (phys >= 0x1F800000u && phys <= 0x1F8003FFu)
|
||||
rec_event(REC_KIND_SP_W, phys, new_val, g_debug_last_store_pc, ra);
|
||||
|
||||
@@ -30,7 +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 "psx_memory.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"
|
||||
@@ -257,7 +257,7 @@ static DirtyRamPcEntry *pc_table_get_or_insert(uint32_t pc) {
|
||||
* cache-unfriendly open-addressed lookup on every guest instruction. */
|
||||
static inline void exec_pc_table_record(uint32_t pc) {
|
||||
uint32_t phys = pc & 0x1FFFFFFFu;
|
||||
if (phys < 2u * 1024u * 1024u && (phys & 3u) == 0u) {
|
||||
if (phys < psx_ram_live_bytes() && (phys & 3u) == 0u) {
|
||||
uint32_t word = phys >> 2;
|
||||
uint32_t mask = 1u << (word & 31u);
|
||||
uint32_t *slot = &g_dirty_ram_exec_pc_bitmap[word >> 5];
|
||||
@@ -508,13 +508,16 @@ static inline uint32_t imm16_field (uint32_t i) { return i & 0xFFFFu; }
|
||||
static inline int32_t simm16_field(uint32_t i) { return (int32_t)(int16_t)imm16_field(i); }
|
||||
static inline uint32_t target26 (uint32_t i) { return i & 0x03FFFFFFu; }
|
||||
|
||||
/* Read a 32-bit instruction word from kernel RAM at the given physical addr.
|
||||
* Caller has already verified the address is in dirty kernel RAM. */
|
||||
/* Read a 32-bit instruction word from main RAM at the given physical addr.
|
||||
* Caller has already verified the address is RAM. The fetch decodes through
|
||||
* the live geometry exactly like a CPU load: a retail PC in the 2nd-4th mirror
|
||||
* fetches the folded 2 MiB bytes, an opt-in 8 MB PC its unique high page. */
|
||||
static inline uint32_t fetch_word(uint32_t phys) {
|
||||
/* Main RAM is a process-lifetime static allocation. Cache its address so
|
||||
* instruction fetch does not cross translation units for every guest op. */
|
||||
static const uint8_t *ram;
|
||||
if (!ram) ram = memory_get_ram_ptr();
|
||||
phys = psx_ram_map_read(phys);
|
||||
return (uint32_t)ram[phys]
|
||||
| ((uint32_t)ram[phys + 1] << 8)
|
||||
| ((uint32_t)ram[phys + 2] << 16)
|
||||
@@ -552,7 +555,7 @@ static int ws_cull_site(uint32_t pc) {
|
||||
return cache[slot].flag;
|
||||
uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u;
|
||||
uint32_t hi = phys + (uint32_t)(WIN * 4);
|
||||
if (hi > 0x200000u) hi = 0x200000u; /* 2 MB main RAM */
|
||||
if (hi > psx_ram_live_bytes()) hi = psx_ram_live_bytes();
|
||||
static uint32_t words[2 * WIN + 1];
|
||||
int n = 0;
|
||||
for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 1); a += 4u)
|
||||
@@ -578,7 +581,7 @@ static int ws_cull_bltz_site(uint32_t pc) {
|
||||
return cache[slot].flag;
|
||||
uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u;
|
||||
uint32_t hi = phys + (uint32_t)(WIN * 4);
|
||||
if (hi > 0x200000u) hi = 0x200000u; /* 2 MB main RAM */
|
||||
if (hi > psx_ram_live_bytes()) hi = psx_ram_live_bytes();
|
||||
static uint32_t words[2 * WIN + 1];
|
||||
int n = 0;
|
||||
for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 1); a += 4u)
|
||||
@@ -615,7 +618,7 @@ static int ws_backdrop_site_kind(uint32_t pc, int *out_cols) {
|
||||
}
|
||||
uint32_t lo = (phys > (uint32_t)(WIN * 4)) ? phys - (uint32_t)(WIN * 4) : 0u;
|
||||
uint32_t hi = phys + (uint32_t)(WIN * 4 + 4);
|
||||
if (hi > 0x200000u) hi = 0x200000u; /* 2 MB main RAM */
|
||||
if (hi > psx_ram_live_bytes()) hi = psx_ram_live_bytes();
|
||||
static uint32_t words[2 * WIN + 2];
|
||||
int n = 0;
|
||||
for (uint32_t a = lo; a + 4u <= hi && n < (int)(2 * WIN + 2); a += 4u)
|
||||
@@ -2874,7 +2877,7 @@ static int dirty_ram_dispatch_inner(CPUState* cpu, uint32_t addr, uint32_t stop_
|
||||
* a JALR into a CD-DMA'd overlay page then fell through to
|
||||
* psx_unknown_dispatch and fail-fast exit(1). Data and invalid targets
|
||||
* still fail closed via the decodability check. */
|
||||
if (phys < (2u * 1024u * 1024u) &&
|
||||
if (phys < psx_ram_live_bytes() &&
|
||||
phys_is_overlay_region(phys) &&
|
||||
dirty_ram_word_looks_decodable(fetch_word(phys))) {
|
||||
dirty_ram_mark_executable_range(phys, 4u);
|
||||
@@ -3831,7 +3834,7 @@ void ls_func_enter(uint32_t entry_pc, CPUState *cpu) {
|
||||
return;
|
||||
}
|
||||
uint32_t phys = entry_pc & 0x1FFFFFFFu;
|
||||
if (phys < 0x00010000u || phys >= 0x00200000u) return;
|
||||
if (phys < 0x00010000u || phys >= psx_ram_live_bytes()) return;
|
||||
|
||||
s_lsf_dispatch_entry = entry_pc;
|
||||
s_lsf_entry = entry_pc;
|
||||
@@ -3913,9 +3916,10 @@ void ls_at_leader(uint32_t leader_phys, CPUState *cpu) {
|
||||
* (per-instruction, for the cycle ruler); skip those — they're already
|
||||
* interpreted (clean game text dispatched FROM the dirty path still runs
|
||||
* compiled, so we can't use g_dirty_interp_active here).
|
||||
* - [0x10000, 0x200000): game EXE text in main RAM (above the low-RAM
|
||||
* - [0x10000, live RAM end): game EXE/overlay text in main RAM (above the low-RAM
|
||||
* kernel/relocated-BIOS area, which isn't the regression locus). */
|
||||
if (!g_ls_dirty_observe && leader_phys >= 0x00010000u && leader_phys < 0x00200000u) {
|
||||
if (!g_ls_dirty_observe && leader_phys >= 0x00010000u &&
|
||||
leader_phys < psx_ram_live_bytes()) {
|
||||
s_ls_R0 = *cpu;
|
||||
s_ls_block = leader_phys;
|
||||
s_ls_trace_n = 0; s_ls_trace_idx = 0; s_ls_overflow = 0; s_ls_mismatch = 0;
|
||||
|
||||
+50
-43
@@ -22,6 +22,7 @@
|
||||
#include "mod_memory.h"
|
||||
#include "overlay_capture.h"
|
||||
#include "psx_cycles.h"
|
||||
#include "psx_memory.h"
|
||||
#include "spu.h"
|
||||
#include "audio_trace.h"
|
||||
#include "event_ring.h"
|
||||
@@ -30,6 +31,10 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Word-aligned main-RAM offset of a DMA address (MADR / stepped cursor)
|
||||
* through the live geometry: retail folds to 2 MiB exactly as the DMAC's
|
||||
* 0x1FFFFC decode does; the opt-in 8 MB map addresses its unique high pages. */
|
||||
#define DMA_RAM_ADDR(a) (psx_ram_canonical_offset(a) & ~3u)
|
||||
/* Memory access — defined in memory.c */
|
||||
extern uint32_t psx_read_word(uint32_t addr);
|
||||
extern void psx_write_word(uint32_t addr, uint32_t val);
|
||||
@@ -256,7 +261,7 @@ static void start_cdrom_dma_capture(uint32_t requested_words) {
|
||||
memset(&cdrom_dma_active_entry, 0, sizeof(cdrom_dma_active_entry));
|
||||
cdrom_dma_active_entry.seq = cdrom_dma_history_seq++;
|
||||
cdrom_dma_active_entry.frame_start = (uint32_t)s_frame_count;
|
||||
cdrom_dma_active_entry.start_addr = channels[3].madr & 0x1FFFFCu;
|
||||
cdrom_dma_active_entry.start_addr = DMA_RAM_ADDR(channels[3].madr);
|
||||
cdrom_dma_active_entry.final_addr = cdrom_dma_active_entry.start_addr;
|
||||
cdrom_dma_active_entry.requested_words = requested_words;
|
||||
cdrom_dma_active_entry.bcr = channels[3].bcr;
|
||||
@@ -303,7 +308,7 @@ static void finish_cdrom_dma_capture(uint32_t final_addr, uint8_t completed) {
|
||||
cdrom_debug_snapshot(&s);
|
||||
|
||||
cdrom_dma_active_entry.frame_end = (uint32_t)s_frame_count;
|
||||
cdrom_dma_active_entry.final_addr = final_addr & 0x1FFFFCu;
|
||||
cdrom_dma_active_entry.final_addr = DMA_RAM_ADDR(final_addr);
|
||||
cdrom_dma_active_entry.dicr_end = dicr_read_value(dicr);
|
||||
cdrom_dma_active_entry.i_stat_end = i_stat;
|
||||
cdrom_dma_active_entry.sector_read_pos_end = s.sector_read_pos;
|
||||
@@ -400,7 +405,7 @@ static void cancel_async_transfer(int ch) {
|
||||
mdec_async[ch].cycles_accum = 0;
|
||||
}
|
||||
if (ch == 3) {
|
||||
finish_cdrom_dma_capture(channels[3].madr & 0x1FFFFCu, 0);
|
||||
finish_cdrom_dma_capture(DMA_RAM_ADDR(channels[3].madr), 0);
|
||||
cdrom_async.active = 0;
|
||||
cdrom_async.debug_started = 0;
|
||||
cdrom_async.total_words = 0;
|
||||
@@ -471,9 +476,9 @@ static void start_async_mdec_transfer(int ch) {
|
||||
a->cycles_accum = 0;
|
||||
|
||||
if (ch == 0) {
|
||||
mdec_debug_dma_in_start(channels[0].madr & 0x1FFFFCu, a->remaining_words);
|
||||
mdec_debug_dma_in_start(DMA_RAM_ADDR(channels[0].madr), a->remaining_words);
|
||||
} else {
|
||||
mdec_debug_dma_out_start(channels[1].madr & 0x1FFFFCu, a->remaining_words);
|
||||
mdec_debug_dma_out_start(DMA_RAM_ADDR(channels[1].madr), a->remaining_words);
|
||||
}
|
||||
a->debug_started = 1;
|
||||
}
|
||||
@@ -497,11 +502,11 @@ static void start_async_cdrom_transfer(void) {
|
||||
a->total_words = transfer_word_count(3);
|
||||
a->remaining_words = a->total_words;
|
||||
a->cycles_accum = 0;
|
||||
a->start_addr = channels[3].madr & 0x1FFFFCu;
|
||||
a->start_addr = DMA_RAM_ADDR(channels[3].madr);
|
||||
start_cdrom_dma_capture(a->total_words);
|
||||
|
||||
if (a->total_words == 0) {
|
||||
finish_cdrom_dma_capture(channels[3].madr & 0x1FFFFCu, 1);
|
||||
finish_cdrom_dma_capture(DMA_RAM_ADDR(channels[3].madr), 1);
|
||||
cancel_async_transfer(3);
|
||||
complete_transfer(3);
|
||||
}
|
||||
@@ -516,12 +521,13 @@ static void finish_async_cdrom_transfer(uint32_t final_addr) {
|
||||
|
||||
/* Log and capture game-data transfers only.
|
||||
* Transfers to 0x1C0000+ are FMV/streaming buffers; skip them. */
|
||||
if (!step && size > 0 && load_start < 0x1C0000u) {
|
||||
/* The DMA word loop wraps inside the 2 MB RAM mask; the capture
|
||||
if (!step && size > 0 &&
|
||||
(load_start < 0x1C0000u || load_start >= PSX_MAIN_RAM_RETAIL_BYTES)) {
|
||||
/* The DMA word loop wraps inside the live RAM mask; the capture
|
||||
* path below takes a flat ram+offset span and must not follow the
|
||||
* wrap past the end of host RAM. */
|
||||
if (size > 0x200000u - load_start)
|
||||
size = 0x200000u - load_start;
|
||||
if (size > psx_ram_live_bytes() - load_start)
|
||||
size = psx_ram_live_bytes() - load_start;
|
||||
int lba = cdrom_get_setloc_lba();
|
||||
if (lba >= 0) cd_dma_log_push(lba, load_start, size);
|
||||
|
||||
@@ -551,7 +557,7 @@ static void advance_mdec_channel(int ch, uint32_t cycles) {
|
||||
|
||||
if ((ch == 0 && direction == 0) || (ch == 1 && direction != 0)) {
|
||||
uint32_t words = a->total_words;
|
||||
uint32_t addr = channels[ch].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[ch].madr);
|
||||
finish_async_mdec_transfer(ch, addr, words);
|
||||
return;
|
||||
}
|
||||
@@ -571,7 +577,7 @@ static void advance_mdec_channel(int ch, uint32_t cycles) {
|
||||
uint32_t words_budget = a->cycles_accum / cycles_per_word;
|
||||
if (words_budget == 0) return;
|
||||
|
||||
uint32_t addr = channels[ch].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[ch].madr);
|
||||
uint32_t moved = 0;
|
||||
/* ch1 (MDEC→RAM) writes guest RAM here in a deferred step; mark it as
|
||||
* DMA-sourced so write-provenance tags these writes with ch1 + the kick PC
|
||||
@@ -592,13 +598,13 @@ static void advance_mdec_channel(int ch, uint32_t cycles) {
|
||||
if (!mdec_dma_write_ready()) break;
|
||||
uint32_t n = a->remaining_words < words_budget
|
||||
? a->remaining_words : words_budget;
|
||||
uint32_t max_by_ram = (0x200000u - addr) / 4u;
|
||||
uint32_t max_by_ram = (psx_ram_live_bytes() - addr) / 4u;
|
||||
if (max_by_ram == 0) break;
|
||||
if (n > max_by_ram) n = max_by_ram;
|
||||
uint32_t got =
|
||||
mdec_dma_write_words((const uint32_t *)(ram + addr), n);
|
||||
if (got == 0) break;
|
||||
addr = (addr + got * 4u) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + got * 4u));
|
||||
a->remaining_words -= got;
|
||||
words_budget -= got;
|
||||
moved += got;
|
||||
@@ -614,7 +620,7 @@ static void advance_mdec_channel(int ch, uint32_t cycles) {
|
||||
psx_write_word(addr, mdec_dma_read_word());
|
||||
}
|
||||
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
a->remaining_words--;
|
||||
words_budget--;
|
||||
moved++;
|
||||
@@ -637,26 +643,26 @@ static void execute_ch0_mdec_in(void) {
|
||||
uint32_t direction = chcr & 1; /* 1=from RAM to MDEC */
|
||||
uint32_t step = (chcr >> 1) & 1;
|
||||
uint32_t total_words = transfer_word_count(0);
|
||||
uint32_t addr = channels[0].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[0].madr);
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
|
||||
if (direction != 0) {
|
||||
mdec_debug_dma_in_start(addr, total_words);
|
||||
if (addr_step == 4 && total_words > 0 &&
|
||||
addr + total_words * 4u <= 0x200000u) {
|
||||
addr + total_words * 4u <= psx_ram_live_bytes()) {
|
||||
extern uint8_t *memory_get_ram_ptr(void);
|
||||
uint32_t got = mdec_dma_write_words(
|
||||
(const uint32_t *)(memory_get_ram_ptr() + addr), total_words);
|
||||
addr = (addr + got * 4u) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + got * 4u));
|
||||
/* If the FIFO stalled mid-burst, finish any remainder word-wise. */
|
||||
for (uint32_t i = got; i < total_words; i++) {
|
||||
mdec_dma_write_word(psx_read_word(addr));
|
||||
addr = (addr + 4u) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + 4u));
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
mdec_dma_write_word(psx_read_word(addr));
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
}
|
||||
mdec_debug_dma_in_end(addr, total_words);
|
||||
@@ -671,14 +677,14 @@ static void execute_ch1_mdec_out(void) {
|
||||
uint32_t direction = chcr & 1; /* 0=from MDEC to RAM */
|
||||
uint32_t step = (chcr >> 1) & 1;
|
||||
uint32_t total_words = transfer_word_count(1);
|
||||
uint32_t addr = channels[1].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[1].madr);
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
|
||||
if (direction == 0) {
|
||||
mdec_debug_dma_out_start(addr, total_words);
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
psx_write_word(addr, mdec_dma_read_word());
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
mdec_debug_dma_out_end(addr, total_words);
|
||||
channels[1].madr = addr;
|
||||
@@ -768,12 +774,12 @@ static uint32_t execute_ch2_gpu(void) {
|
||||
uint32_t block_size = channels[2].bcr & 0xFFFF;
|
||||
uint32_t block_count = (channels[2].bcr >> 16) & 0xFFFF;
|
||||
uint32_t total_words = block_size * block_count;
|
||||
uint32_t addr = channels[2].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[2].madr);
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
uint32_t pixel_data = gpu_read_gpuread();
|
||||
psx_write_word(addr, pixel_data);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
channels[2].madr = addr;
|
||||
actual_words = total_words;
|
||||
@@ -787,14 +793,14 @@ static uint32_t execute_ch2_gpu(void) {
|
||||
uint32_t block_size = channels[2].bcr & 0xFFFF;
|
||||
uint32_t block_count = (channels[2].bcr >> 16) & 0xFFFF;
|
||||
uint32_t total_words = block_size * block_count;
|
||||
uint32_t addr = channels[2].madr & 0x1FFFFCu; /* mask to RAM, word-aligned */
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[2].madr); /* mask to RAM, word-aligned */
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
uint32_t word = psx_read_word(addr);
|
||||
gpu_set_gp0_source(addr);
|
||||
gpu_write_gp0(word);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
channels[2].madr = addr;
|
||||
actual_words = total_words;
|
||||
@@ -806,14 +812,14 @@ static uint32_t execute_ch2_gpu(void) {
|
||||
/* Burst mode (sync_mode == 0) */
|
||||
uint32_t word_count = channels[2].bcr & 0xFFFF;
|
||||
if (word_count == 0) word_count = 0x10000; /* 0 means 0x10000 */
|
||||
uint32_t addr = channels[2].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[2].madr);
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
|
||||
for (uint32_t i = 0; i < word_count; i++) {
|
||||
uint32_t word = psx_read_word(addr);
|
||||
gpu_set_gp0_source(addr);
|
||||
gpu_write_gp0(word);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
channels[2].madr = addr;
|
||||
actual_words = word_count;
|
||||
@@ -843,18 +849,18 @@ static uint32_t execute_ch4_spu(void) {
|
||||
uint32_t direction = chcr & 1; /* 1=from RAM to SPU, 0=SPU to RAM */
|
||||
uint32_t step = (chcr >> 1) & 1;
|
||||
uint32_t total_words = transfer_word_count(4);
|
||||
uint32_t addr = channels[4].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[4].madr);
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
|
||||
if (direction != 0) {
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
spu_dma_write(psx_read_word(addr));
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
/* One aggregated event per SPU-bound transfer (per-word would flood
|
||||
* the ring: a full sound-bank upload is ~128k words). */
|
||||
audio_trace_event(AUDIO_EV_DMA_WRITE, total_words,
|
||||
channels[4].madr & 0x1FFFFCu);
|
||||
DMA_RAM_ADDR(channels[4].madr));
|
||||
} else {
|
||||
/* SPU RAM -> CPU RAM. This direction previously zero-filled the
|
||||
* destination, which is not a transfer at all: SPU RAM is readable
|
||||
@@ -865,10 +871,10 @@ static uint32_t execute_ch4_spu(void) {
|
||||
* fell back to a cold-boot path. */
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
psx_write_word(addr, spu_dma_read());
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
audio_trace_event(AUDIO_EV_DMA_READ, total_words,
|
||||
channels[4].madr & 0x1FFFFCu);
|
||||
DMA_RAM_ADDR(channels[4].madr));
|
||||
}
|
||||
|
||||
channels[4].madr = addr;
|
||||
@@ -882,7 +888,7 @@ static void execute_ch6_otc(void) {
|
||||
* BCR bits 0-15 = number of entries. */
|
||||
uint32_t num_entries = channels[6].bcr & 0xFFFF;
|
||||
if (num_entries == 0) num_entries = 0x10000;
|
||||
uint32_t addr = channels[6].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[6].madr);
|
||||
|
||||
for (uint32_t i = 0; i < num_entries; i++) {
|
||||
uint32_t val;
|
||||
@@ -894,7 +900,7 @@ static void execute_ch6_otc(void) {
|
||||
val = (addr - 4) & 0x00FFFFFFu;
|
||||
}
|
||||
psx_write_word(addr, val);
|
||||
addr = (addr - 4) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr - 4));
|
||||
}
|
||||
|
||||
complete_transfer(6);
|
||||
@@ -910,20 +916,20 @@ static void execute_ch5_pio(void) {
|
||||
uint32_t direction = chcr & 1u;
|
||||
uint32_t step = (chcr >> 1) & 1u;
|
||||
uint32_t total_words = transfer_word_count(5);
|
||||
uint32_t addr = channels[5].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[5].madr);
|
||||
int32_t addr_step = step ? -4 : 4;
|
||||
|
||||
if (direction == 1) {
|
||||
/* from RAM to device: just read and discard */
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
(void)psx_read_word(addr);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
} else {
|
||||
/* to RAM from device: write zeros (device not emulated) */
|
||||
for (uint32_t i = 0; i < total_words; i++) {
|
||||
psx_write_word(addr, 0);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
}
|
||||
}
|
||||
channels[5].madr = addr;
|
||||
@@ -1144,7 +1150,7 @@ void dma_advance(uint32_t cycles) {
|
||||
}
|
||||
|
||||
uint32_t words_budget = a->cycles_accum / DMA_CDROM_CYCLES_PER_WORD;
|
||||
uint32_t addr = channels[3].madr & 0x1FFFFCu;
|
||||
uint32_t addr = DMA_RAM_ADDR(channels[3].madr);
|
||||
uint32_t moved = 0;
|
||||
g_dma_cur_ch = 3; g_dma_cur_bcr = channels[3].bcr;
|
||||
g_dma_initiator_pc = s_dma_ch_initiator_pc[3]; /* deferred: restore kick PC */
|
||||
@@ -1153,9 +1159,10 @@ void dma_advance(uint32_t cycles) {
|
||||
* word. Scheduling-time capture was too early because guest code
|
||||
* can continue executing while the CD device is not ready. */
|
||||
if (a->remaining_words == a->total_words && words_budget > 0 &&
|
||||
addr < 0x1C0000u) {
|
||||
(addr < 0x1C0000u || addr >= PSX_MAIN_RAM_RETAIL_BYTES)) {
|
||||
uint32_t bytes = a->total_words * 4u;
|
||||
if (bytes > 0x200000u - addr) bytes = 0x200000u - addr;
|
||||
if (bytes > psx_ram_live_bytes() - addr)
|
||||
bytes = psx_ram_live_bytes() - addr;
|
||||
overlay_capture_before_dma(addr, bytes);
|
||||
}
|
||||
while (a->remaining_words > 0 && words_budget > 0 && cdrom_dma_ready()) {
|
||||
@@ -1164,7 +1171,7 @@ void dma_advance(uint32_t cycles) {
|
||||
psx_write_word(addr, word);
|
||||
record_cdrom_dma_word(word);
|
||||
dirty_ram_mark_executable_range(addr, 4);
|
||||
addr = (addr + addr_step) & 0x1FFFFCu;
|
||||
addr = DMA_RAM_ADDR((addr + addr_step));
|
||||
a->remaining_words--;
|
||||
words_budget--;
|
||||
moved++;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* freeze_heartbeat.c — see header for rationale. */
|
||||
|
||||
#include "mdec.h" /* mdec_recently_active: FMV parks the guest in BIOS MDEC code */
|
||||
#include "psx_memory.h"
|
||||
#include "freeze_heartbeat.h"
|
||||
#include "freeze_dump_policy.h"
|
||||
#include "debug_server.h"
|
||||
@@ -347,10 +348,10 @@ static int hb_append_ram_peek(char *out, int n, size_t cap, uint32_t vaddr, int
|
||||
ncopy = (int)(0x400u - off);
|
||||
}
|
||||
} else if (phys < 0x00800000u && g_psx_ram) {
|
||||
uint32_t folded = phys & 0x1FFFFFu;
|
||||
uint32_t folded = psx_ram_canonical_offset(phys);
|
||||
src = g_psx_ram + folded;
|
||||
if (folded + (uint32_t)ncopy > 0x200000u)
|
||||
ncopy = (int)(0x200000u - folded);
|
||||
if (folded + (uint32_t)ncopy > psx_ram_live_bytes())
|
||||
ncopy = (int)(psx_ram_live_bytes() - folded);
|
||||
}
|
||||
if (!src) ncopy = 0;
|
||||
int m = snprintf(out + n, cap - (size_t)n,
|
||||
@@ -376,7 +377,7 @@ static uint32_t hb_jal_target_from_ra(uint32_t ra) {
|
||||
uint8_t *sp = memory_get_scratchpad_ptr();
|
||||
if (sp) src = sp + (phys - 0x1F800000u);
|
||||
} else if (phys < 0x00800000u && g_psx_ram) {
|
||||
src = g_psx_ram + (phys & 0x1FFFFFu);
|
||||
src = g_psx_ram + psx_ram_canonical_offset(phys);
|
||||
}
|
||||
if (!src) return 0;
|
||||
memcpy(&insn, src, 4);
|
||||
|
||||
+26
-21
@@ -16,6 +16,7 @@
|
||||
#include "display_scanout.h"
|
||||
#include "pgxp.h"
|
||||
#include "mod_memory.h"
|
||||
#include "psx_memory.h"
|
||||
#include "gpu_primitive_reject.h"
|
||||
#include "gpu_sw_renderer.h"
|
||||
#include "gpu_vram_dirty.h"
|
||||
@@ -44,6 +45,10 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Word-aligned main-RAM key for a primitive/OT address through the live
|
||||
* geometry (retail: the DMAC's 0x1FFFFC fold). */
|
||||
#define GPU_RAM_KEY(a) (psx_ram_canonical_offset(a) & ~3u)
|
||||
|
||||
extern uint16_t psx_read_half(uint32_t addr);
|
||||
extern uint8_t psx_read_byte(uint32_t addr);
|
||||
extern uint32_t psx_read_word(uint32_t addr);
|
||||
@@ -1880,7 +1885,7 @@ void gpu_ws_configure(int aspect_num, int aspect_den,
|
||||
* start (the first character of a frame would be suppressed forever). */
|
||||
void psx_ws_sprite_tag(CPUState* cpu) {
|
||||
if (!ws_engaged() || !ws_anchor_addr) return;
|
||||
uint32_t key = cpu->gpr[4] & 0x1FFFFCu;
|
||||
uint32_t key = GPU_RAM_KEY(cpu->gpr[4]);
|
||||
if (!key) return;
|
||||
uint32_t sxy = cpu->read_word(ws_anchor_addr);
|
||||
int32_t ax = (int32_t)(int16_t)(sxy & 0xFFFFu);
|
||||
@@ -1904,7 +1909,7 @@ static int ws_hud_command_words(uint32_t command_addr, uint32_t *words,
|
||||
if (!words || !out_count) return 0;
|
||||
if ((command_addr & 3u) != 0) return 0;
|
||||
uint32_t phys = command_addr & 0x1FFFFFFFu;
|
||||
if (phys > 0x00200000u - 4u) return 0;
|
||||
if (phys > psx_ram_live_bytes() - 4u) return 0;
|
||||
|
||||
words[0] = psx_read_word(command_addr);
|
||||
uint8_t op = (uint8_t)(words[0] >> 24);
|
||||
@@ -1912,7 +1917,7 @@ static int ws_hud_command_words(uint32_t command_addr, uint32_t *words,
|
||||
int count = gp0_command_word_count(op);
|
||||
if (count <= 0 || count > 12) return 0;
|
||||
uint32_t bytes = (uint32_t)count * 4u;
|
||||
if (phys > 0x00200000u - bytes) return 0;
|
||||
if (phys > psx_ram_live_bytes() - bytes) return 0;
|
||||
|
||||
for (int i = 1; i < count; i++)
|
||||
words[i] = psx_read_word(command_addr + (uint32_t)i * 4u);
|
||||
@@ -1932,7 +1937,7 @@ void gpu_ws_tag_hud_prim(uint32_t prim, int anchor) {
|
||||
WsPrepassPacketGuard guard =
|
||||
ws_prepass_packet_guard(words, word_count);
|
||||
ws_hud_anchor_insert(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE,
|
||||
command_addr & 0x1FFFFCu, anchor, &guard,
|
||||
GPU_RAM_KEY(command_addr), anchor, &guard,
|
||||
(uint32_t)s_frame_count);
|
||||
}
|
||||
|
||||
@@ -1945,7 +1950,7 @@ void gpu_ws_tag_black_reveal_rect(uint32_t prim) {
|
||||
if ((words[0] >> 24) != 0x64u && (words[0] >> 24) != 0x65u) return;
|
||||
WsPrepassPacketGuard guard = ws_prepass_packet_guard(words, count);
|
||||
ws_hud_anchor_insert(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE,
|
||||
(prim + 4u) & 0x1FFFFCu, 0, &guard,
|
||||
GPU_RAM_KEY((prim + 4u)), 0, &guard,
|
||||
(uint32_t)s_frame_count);
|
||||
}
|
||||
|
||||
@@ -1960,7 +1965,7 @@ void gpu_ws_tag_repeat_rect(uint32_t prim, int32_t period) {
|
||||
if (!w || w > 256u || (words[2] & 255u) + w > 256u || !h || h > 511u)
|
||||
return;
|
||||
WsPrepassPacketGuard guard = ws_prepass_packet_guard(words, count);
|
||||
ws_repeat_rect_tag_insert(ws_repeat_rect_tags, (prim + 4u) & 0x1FFFFCu,
|
||||
ws_repeat_rect_tag_insert(ws_repeat_rect_tags, GPU_RAM_KEY((prim + 4u)),
|
||||
period, &guard, (uint32_t)s_frame_count);
|
||||
}
|
||||
|
||||
@@ -1971,7 +1976,7 @@ static int ws_nw_explicit_hud_delta(int32_t *out_delta) {
|
||||
if (gp0_words_needed <= 0 || gp0_words_needed > 12)
|
||||
return 0;
|
||||
uint32_t command_addr = gp0_cmd_source_addr & 0x1FFFFFFFu;
|
||||
if (command_addr >= 0x00200000u || (command_addr & 3u)) return 0;
|
||||
if (command_addr >= psx_ram_live_bytes() || (command_addr & 3u)) return 0;
|
||||
int anchor = 0;
|
||||
if (!ws_hud_anchor_lookup(ws_hud_anchor_tags, WS_HUD_ANCHOR_TABLE_SIZE,
|
||||
command_addr, gp0_cmd_buf,
|
||||
@@ -1991,7 +1996,7 @@ static int ws_tagged_anchor(int32_t *out_ax) {
|
||||
if (!ws_active() || gp0_cmd_source_addr == 0xFFFFFFFFu) return 0;
|
||||
uint32_t now = (uint32_t)s_frame_count;
|
||||
for (int variant = 0; variant < 2; variant++) {
|
||||
uint32_t key = (gp0_cmd_source_addr - (variant ? 0u : 4u)) & 0x1FFFFCu;
|
||||
uint32_t key = GPU_RAM_KEY((gp0_cmd_source_addr - (variant ? 0u : 4u)));
|
||||
uint32_t idx = (key >> 2) & (WS_TAG_BUCKETS - 1);
|
||||
for (int i = 0; i < WS_TAG_PROBES; i++) {
|
||||
WsTag *t = &ws_tags[(idx + i) & (WS_TAG_BUCKETS - 1)];
|
||||
@@ -2132,7 +2137,7 @@ int psx_ws_prim_is_tagged(void) {
|
||||
if (!ws_engaged() || gp0_cmd_source_addr == 0xFFFFFFFFu) return 0;
|
||||
uint32_t now = (uint32_t)s_frame_count;
|
||||
for (int variant = 0; variant < 2; variant++) {
|
||||
uint32_t key = (gp0_cmd_source_addr - (variant ? 0u : 4u)) & 0x1FFFFCu;
|
||||
uint32_t key = GPU_RAM_KEY((gp0_cmd_source_addr - (variant ? 0u : 4u)));
|
||||
uint32_t idx = (key >> 2) & (WS_TAG_BUCKETS - 1);
|
||||
for (int i = 0; i < WS_TAG_PROBES; i++) {
|
||||
WsTag *t = &ws_tags[(idx + i) & (WS_TAG_BUCKETS - 1)];
|
||||
@@ -2228,7 +2233,7 @@ static int ws_auto_ui_anchor(int32_t *out_anchor) {
|
||||
if (!ws_auto_ui_squash || !ws_active() ||
|
||||
gp0_cmd_source_addr == 0xFFFFFFFFu)
|
||||
return 0;
|
||||
uint32_t src = gp0_cmd_source_addr & 0x1FFFFCu;
|
||||
uint32_t src = GPU_RAM_KEY(gp0_cmd_source_addr);
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_count; i++) {
|
||||
if (ws_ui_prepass[i].src_addr != src) continue;
|
||||
if (!ws_prepass_packet_matches(&ws_ui_prepass[i].packet_guard,
|
||||
@@ -2770,7 +2775,7 @@ static void ws_clear_tagged_rect_reveal(int y, int h) {
|
||||
if (!ws_native_wide_active() || h <= 0 || gp0_cmd_source_addr == UINT32_MAX)
|
||||
return;
|
||||
uint32_t address = gp0_cmd_source_addr & 0x1FFFFFFFu;
|
||||
if (address >= 0x00200000u || (address & 3u) ||
|
||||
if (address >= psx_ram_live_bytes() || (address & 3u) ||
|
||||
!ws_hud_anchor_lookup(ws_reveal_clear_tags, WS_HUD_ANCHOR_TABLE_SIZE,
|
||||
address, gp0_cmd_buf, (uint32_t)gp0_words_needed,
|
||||
(uint32_t)s_frame_count, NULL)) return;
|
||||
@@ -3653,7 +3658,7 @@ static void prepare_texture_triangle(int i0, int i1, int i2) {
|
||||
int indices[3] = { i0, i1, i2 };
|
||||
uint16_t z[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
uint32_t addr = (gp0_cmd_source_addr + (uint32_t)indices[i] * 4u) & 0x1FFFFCu;
|
||||
uint32_t addr = GPU_RAM_KEY((gp0_cmd_source_addr + (uint32_t)indices[i] * 4u));
|
||||
if (!gte_precision_load_word(addr, gp0_cmd_buf[indices[i]], NULL, NULL, &z[i]) ||
|
||||
z[i] == 0) {
|
||||
s_texcorr.no_depth++;
|
||||
@@ -4384,7 +4389,7 @@ static void ws_repeat_textured_rect_reveal(int x, int y, int w, int h,
|
||||
draw_area_right - draw_area_left + 1u != (uint32_t)width)
|
||||
return;
|
||||
uint32_t address = gp0_cmd_source_addr & 0x1FFFFFFFu;
|
||||
if (address >= 0x00200000u || (address & 3u)) return;
|
||||
if (address >= psx_ram_live_bytes() || (address & 3u)) return;
|
||||
int32_t period = ws_repeat_rect_tag_lookup(ws_repeat_rect_tags, address,
|
||||
gp0_cmd_buf, (uint32_t)gp0_words_needed, (uint32_t)s_frame_count);
|
||||
if (!period) return;
|
||||
@@ -4778,8 +4783,8 @@ static void gp0_exec_cpu_to_vram(void) {
|
||||
a0_history[slot].s2_val = debug_cpu_ptr->gpr[18]; /* $s2 = source ptr */
|
||||
a0_history[slot].a0_val = debug_cpu_ptr->gpr[4]; /* $a0 */
|
||||
a0_history[slot].a1_val = debug_cpu_ptr->gpr[5]; /* $a1 */
|
||||
uint32_t sp_phys = sp & 0x1FFFFFu;
|
||||
for (int si = 0; si < 10 && sp_phys + (si + 1) * 4 <= 0x200000u; si++)
|
||||
uint32_t sp_phys = psx_ram_canonical_offset(sp);
|
||||
for (int si = 0; si < 10 && sp_phys + (si + 1) * 4 <= psx_ram_live_bytes(); si++)
|
||||
a0_history[slot].stack[si] = psx_read_word(sp + si * 4);
|
||||
}
|
||||
a0_capture_slot = slot;
|
||||
@@ -5022,7 +5027,7 @@ static void ws_ui_prepass_add(const uint32_t *words, uint32_t word_count,
|
||||
item->group.height = height;
|
||||
item->group.anchor = 0;
|
||||
item->group.root = ws_ui_prepass_count - 1u;
|
||||
item->src_addr = source_addr & 0x1FFFFCu;
|
||||
item->src_addr = GPU_RAM_KEY(source_addr);
|
||||
item->ot_rank = rank;
|
||||
item->y = min_y;
|
||||
item->h = height;
|
||||
@@ -5068,7 +5073,7 @@ void gpu_ws_prepass_linked_list(uint32_t start_addr) {
|
||||
}
|
||||
WsUiPrepassNode *node =
|
||||
&ws_ui_prepass_nodes[ws_ui_prepass_node_count++];
|
||||
node->addr = addr & 0x1FFFFCu;
|
||||
node->addr = GPU_RAM_KEY(addr);
|
||||
node->header = header;
|
||||
node->payload_guard =
|
||||
ws_prepass_packet_guard(payload, num_words);
|
||||
@@ -5165,7 +5170,7 @@ void gpu_ws_validate_linked_list_header(uint32_t addr, uint32_t header) {
|
||||
if (ws_ui_prepass_count == 0) return;
|
||||
|
||||
uint32_t resolved =
|
||||
psx_mod_gpu_dma_resolve_address(addr) & 0x1FFFFCu;
|
||||
GPU_RAM_KEY(psx_mod_gpu_dma_resolve_address(addr));
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_node_count; i++) {
|
||||
if (ws_ui_prepass_nodes[i].addr != resolved) continue;
|
||||
if (ws_ui_prepass_nodes[i].header != header)
|
||||
@@ -5179,7 +5184,7 @@ void gpu_ws_validate_linked_list_node(uint32_t addr, uint32_t num_words) {
|
||||
if (ws_ui_prepass_count == 0) return;
|
||||
|
||||
uint32_t resolved =
|
||||
psx_mod_gpu_dma_resolve_address(addr) & 0x1FFFFCu;
|
||||
GPU_RAM_KEY(psx_mod_gpu_dma_resolve_address(addr));
|
||||
const WsUiPrepassNode *node = NULL;
|
||||
for (uint32_t i = 0; i < ws_ui_prepass_node_count; i++) {
|
||||
if (ws_ui_prepass_nodes[i].addr == resolved) {
|
||||
@@ -5233,11 +5238,11 @@ static void gp0_capture_builder_chain(uint32_t out[6]) {
|
||||
for (int i = 0; i < 6; i++) out[i] = 0;
|
||||
uint8_t *ram = memory_get_ram_ptr();
|
||||
if (!ram) return;
|
||||
const uint32_t RMASK = 0x001FFFFFu; /* 2 MB, mirror-folded */
|
||||
const uint32_t RMASK = g_psx_ram_mask; /* live RAM, mirror-folded */
|
||||
uint32_t rawsp = debug_guest_sp();
|
||||
g_gp0_last_copy_sp = rawsp;
|
||||
if ((rawsp & 0x1FFFFFFFu) >= 0x00800000u) return; /* not in RAM/mirror */
|
||||
uint32_t sp = rawsp & RMASK & ~3u; /* fold to 2MB, word-align */
|
||||
uint32_t sp = psx_ram_canonical_offset(rawsp) & ~3u; /* fold, word-align */
|
||||
/* Two-pass: prefer words that are genuine return addresses (call at ret-8),
|
||||
* but if guest code validation yields none, fall back to any code-range
|
||||
* stack word so the chain is never silently empty. */
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
*/
|
||||
|
||||
#include "interrupts.h"
|
||||
#include "psx_memory.h"
|
||||
#include "sio.h"
|
||||
#include "timers.h"
|
||||
#include "gpu.h"
|
||||
@@ -1597,12 +1598,12 @@ irq_deliver_eval:
|
||||
if (psx_scheduler_top_level_resume_active() &&
|
||||
cpu->pc != 0u && (cpu->pc & 3u) == 0u) {
|
||||
uint32_t phys = cpu->pc & 0x1FFFFFFFu;
|
||||
if (phys < 0x00200000u ||
|
||||
if (phys < psx_ram_live_bytes() ||
|
||||
(phys >= 0x1FC00000u && phys < 0x1FC80000u))
|
||||
real_pc = cpu->pc;
|
||||
}
|
||||
}
|
||||
/* Accept the real resume PC from guest RAM (<2MB) OR the BIOS ROM
|
||||
/* Accept the real resume PC from live guest RAM OR the BIOS ROM
|
||||
* window. The old RAM-only guard rejected ROM-space block leaders
|
||||
* (e.g. OpenBIOS mcWaitForStatus spinning at 0xBFC076xx during a
|
||||
* card op), forcing EVERY such delivery onto the legacy sentinel.
|
||||
@@ -1617,7 +1618,7 @@ irq_deliver_eval:
|
||||
* ROM pc still falls back to the sentinel (pre-fix behavior);
|
||||
* RAM acceptance is unchanged byte-for-byte. */
|
||||
uint32_t real_phys = real_pc & 0x1FFFFFFFu;
|
||||
int resume_in_ram = real_phys < 0x00200000u;
|
||||
int resume_in_ram = real_phys < psx_ram_live_bytes();
|
||||
int resume_in_rom = real_phys >= 0x1FC00000u && real_phys < 0x1FC80000u &&
|
||||
psx_is_dispatchable(real_pc);
|
||||
if (real_pc != 0u && (real_pc & 0x3u) == 0u &&
|
||||
|
||||
+29
-122
@@ -25,14 +25,14 @@
|
||||
#include "dirty_ram_interp.h"
|
||||
#include "guest_tty.h"
|
||||
#include "psx_cycles.h"
|
||||
#include "psx_ram.h"
|
||||
#include "psx_memory.h"
|
||||
#include "starvation_ring.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define RAM_SIZE PSX_RAM_CAPACITY
|
||||
#define RAM_SIZE PSX_MAIN_RAM_BACKING_BYTES
|
||||
#define SCRATCHPAD_SIZE 1024
|
||||
#define BIOS_ROM_SIZE (512 * 1024)
|
||||
#define MOD_MEMORY_BASE 0x1F000000u
|
||||
@@ -45,67 +45,6 @@ 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
|
||||
? 16u + mod_memory_used + mod_gpu_dma_memory_used : 0u;
|
||||
@@ -205,59 +144,19 @@ int g_psx_load_delay = -1;
|
||||
* 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 < PSX_RAM_WINDOW) phys = psx_ram_map_read(phys);
|
||||
if (phys < PSX_MAIN_RAM_WINDOW_BYTES) 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);
|
||||
if (phys < PSX_MAIN_RAM_WINDOW_BYTES) 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);
|
||||
}
|
||||
@@ -287,6 +186,11 @@ void memory_clear_low_boot_scratch(void) {
|
||||
#define DIRTY_RAM_PAGE_SHIFT 12 /* 4 KB pages */
|
||||
#define DIRTY_RAM_PAGE_COUNT (RAM_SIZE >> DIRTY_RAM_PAGE_SHIFT)
|
||||
#define DIRTY_RAM_BITMAP_WORDS ((DIRTY_RAM_PAGE_COUNT + 31u) / 32u)
|
||||
/* Guest-visible extent (retail 2 MiB unless the 8 MB mod is live). Host arrays
|
||||
* are sized for the backing capacity; bounds and serialization use this. */
|
||||
#define RAM_LIVE (g_psx_ram_size)
|
||||
#define DIRTY_RAM_LIVE_BITMAP_WORDS \
|
||||
(((RAM_LIVE >> DIRTY_RAM_PAGE_SHIFT) + 31u) / 32u)
|
||||
static uint32_t dirty_ram_bitmap[DIRTY_RAM_BITMAP_WORDS];
|
||||
|
||||
/* Monotonic generation for RAM-resident CODE changes (kernel install-stub
|
||||
@@ -297,7 +201,7 @@ static uint32_t dirty_ram_bitmap[DIRTY_RAM_BITMAP_WORDS];
|
||||
uint32_t g_dirty_ram_code_gen = 1;
|
||||
|
||||
static inline void dirty_ram_mark_page(uint32_t phys) {
|
||||
if (phys >= RAM_SIZE) return;
|
||||
if (phys >= RAM_LIVE) return;
|
||||
uint32_t page = phys >> DIRTY_RAM_PAGE_SHIFT;
|
||||
uint32_t bit = 1u << (page & 31u);
|
||||
/* Generation bumps on the clean->dirty TRANSITION only: kernel-window data
|
||||
@@ -563,7 +467,7 @@ extern uint32_t g_overlay_region_floor;
|
||||
void dirty_ram_clear_image_baseline(void) {
|
||||
uint32_t floor = g_overlay_region_floor;
|
||||
if (floor <= DIRTY_RAM_KERNEL_TRACK_BYTES) return;
|
||||
if (floor > RAM_SIZE) floor = RAM_SIZE;
|
||||
if (floor > RAM_LIVE) floor = RAM_LIVE;
|
||||
uint32_t base = g_text_image_lo;
|
||||
if (base < DIRTY_RAM_KERNEL_TRACK_BYTES) base = DIRTY_RAM_KERNEL_TRACK_BYTES;
|
||||
if (base >= floor) return;
|
||||
@@ -605,8 +509,8 @@ static uint32_t g_text_exact_last_ref = 0;
|
||||
|
||||
void dirty_ram_register_text_image(uint32_t phys_lo, const uint8_t *bytes,
|
||||
uint32_t len) {
|
||||
if (!bytes || len == 0 || phys_lo >= RAM_SIZE) return;
|
||||
if (len > RAM_SIZE - phys_lo) len = RAM_SIZE - phys_lo;
|
||||
if (!bytes || len == 0 || phys_lo >= RAM_LIVE) return;
|
||||
if (len > RAM_LIVE - phys_lo) len = RAM_LIVE - phys_lo;
|
||||
text_ref_image = (uint8_t *)bytes; /* runtime-owned mutable heap buffer */
|
||||
text_ref_lo = phys_lo;
|
||||
text_ref_hi = phys_lo + len;
|
||||
@@ -799,10 +703,10 @@ uint32_t dirty_ram_text_diverged_bitmap_word(uint32_t word_index) {
|
||||
}
|
||||
|
||||
void dirty_ram_mark_executable_range(uint32_t phys, uint32_t len) {
|
||||
if (len == 0 || phys >= RAM_SIZE) return;
|
||||
if (len == 0 || phys >= RAM_LIVE) return;
|
||||
psx_kernel_bless_note_range(phys, len);
|
||||
uint32_t end = phys + len - 1u;
|
||||
if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u;
|
||||
if (end >= RAM_LIVE || end < phys) end = RAM_LIVE - 1u;
|
||||
|
||||
uint32_t first_page = phys >> DIRTY_RAM_PAGE_SHIFT;
|
||||
uint32_t last_page = end >> DIRTY_RAM_PAGE_SHIFT;
|
||||
@@ -846,7 +750,7 @@ static int dirty_ram_shellwin_interp(void) {
|
||||
}
|
||||
|
||||
int dirty_ram_is_dirty(uint32_t phys) {
|
||||
if (phys >= RAM_SIZE) return 0;
|
||||
if (phys >= RAM_LIVE) return 0;
|
||||
if (dirty_ram_force_interp() && phys >= DIRTY_RAM_KERNEL_TRACK_BYTES) return 1;
|
||||
if (dirty_ram_shellwin_interp() && phys >= 0x00030000u && phys <= 0x0005AFFFu) return 1;
|
||||
/* Experimental fallback for overlays copied into their final location by
|
||||
@@ -868,13 +772,16 @@ uint32_t dirty_ram_get_bitmap_word(uint32_t word_index) {
|
||||
return dirty_ram_bitmap[word_index];
|
||||
}
|
||||
|
||||
/* Serialized (savestate / netplay digest) extent: the LIVE RAM's pages only,
|
||||
* so a retail session's dirty-bitmap section is unchanged by the 8 MiB host
|
||||
* backing. */
|
||||
uint32_t dirty_ram_get_bitmap_word_count(void) {
|
||||
return DIRTY_RAM_BITMAP_WORDS;
|
||||
return DIRTY_RAM_LIVE_BITMAP_WORDS;
|
||||
}
|
||||
|
||||
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;
|
||||
if (count > DIRTY_RAM_LIVE_BITMAP_WORDS) count = DIRTY_RAM_LIVE_BITMAP_WORDS;
|
||||
for (uint32_t i = 0; i < count; i++)
|
||||
dirty_ram_bitmap[i] = words[i];
|
||||
/* Bitmap replace bypasses clean→dirty transitions; bump so interpreter
|
||||
@@ -914,9 +821,9 @@ void dirty_ram_reset_for_boot(void) {
|
||||
}
|
||||
|
||||
void overlay_watch_set_range(uint32_t phys, uint32_t len) {
|
||||
if (len == 0 || phys >= RAM_SIZE) return;
|
||||
if (len == 0 || phys >= RAM_LIVE) return;
|
||||
uint32_t end = phys + len - 1u;
|
||||
if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u;
|
||||
if (end >= RAM_LIVE || end < phys) end = RAM_LIVE - 1u;
|
||||
uint32_t fp = phys >> DIRTY_RAM_PAGE_SHIFT;
|
||||
uint32_t lp = end >> DIRTY_RAM_PAGE_SHIFT;
|
||||
for (uint32_t pg = fp; pg <= lp; pg++)
|
||||
@@ -924,9 +831,9 @@ void overlay_watch_set_range(uint32_t phys, uint32_t len) {
|
||||
}
|
||||
|
||||
void overlay_watch_clear_range(uint32_t phys, uint32_t len) {
|
||||
if (len == 0 || phys >= RAM_SIZE) return;
|
||||
if (len == 0 || phys >= RAM_LIVE) return;
|
||||
uint32_t end = phys + len - 1u;
|
||||
if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u;
|
||||
if (end >= RAM_LIVE || end < phys) end = RAM_LIVE - 1u;
|
||||
uint32_t fp = phys >> DIRTY_RAM_PAGE_SHIFT;
|
||||
uint32_t lp = end >> DIRTY_RAM_PAGE_SHIFT;
|
||||
for (uint32_t pg = fp; pg <= lp; pg++)
|
||||
@@ -937,9 +844,9 @@ void overlay_watch_clear_range(uint32_t phys, uint32_t len) {
|
||||
* loader stores this at validation time and compares on dispatch; any change
|
||||
* means a watched page in the range was written. */
|
||||
uint32_t overlay_watch_pagegen_sum(uint32_t phys, uint32_t len) {
|
||||
if (len == 0 || phys >= RAM_SIZE) return 0;
|
||||
if (len == 0 || phys >= RAM_LIVE) return 0;
|
||||
uint32_t end = phys + len - 1u;
|
||||
if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u;
|
||||
if (end >= RAM_LIVE || end < phys) end = RAM_LIVE - 1u;
|
||||
uint32_t fp = phys >> DIRTY_RAM_PAGE_SHIFT;
|
||||
uint32_t lp = end >> DIRTY_RAM_PAGE_SHIFT;
|
||||
uint32_t sum = 0;
|
||||
@@ -981,7 +888,7 @@ void dirty_ram_text_guard_resync_after_restore(void) {
|
||||
uint32_t hi = lo + (1u << DIRTY_RAM_PAGE_SHIFT);
|
||||
if (lo < text_ref_lo) lo = text_ref_lo;
|
||||
if (hi > text_ref_hi) hi = text_ref_hi;
|
||||
if (hi > RAM_SIZE) hi = RAM_SIZE;
|
||||
if (hi > RAM_LIVE) hi = RAM_LIVE;
|
||||
if (hi <= lo) continue;
|
||||
if (memcmp(ram + lo, text_ref_image + (lo - text_ref_lo), hi - lo) != 0)
|
||||
text_modified_bitmap[p >> 5] |= (1u << (p & 31u));
|
||||
@@ -1002,7 +909,7 @@ void overlay_watch_invalidate_after_ram_restore(void) {
|
||||
|
||||
static inline void overlay_watch_note_write(uint32_t phys, uint32_t size) {
|
||||
uint32_t pg = phys >> DIRTY_RAM_PAGE_SHIFT;
|
||||
if (pg >= DIRTY_RAM_PAGE_COUNT) return;
|
||||
if (pg >= (RAM_LIVE >> DIRTY_RAM_PAGE_SHIFT)) return;
|
||||
/* Never attach pre-write PC evidence to post-write bytes, including for
|
||||
* completely unknown/self-modifying code. This is deliberately a compact
|
||||
* page clear, not a capture: serializing snapshots from this universal
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "mod_packages.h"
|
||||
#include "mod_plugins.h"
|
||||
#include "gpu.h"
|
||||
#include "psx_ram.h"
|
||||
#include "psx_memory.h"
|
||||
#include "psx_sha256.h"
|
||||
|
||||
#if defined(RECOMP_LAUNCHER)
|
||||
|
||||
@@ -355,14 +355,14 @@ static void write_json_window(FILE *f, uint32_t win_lo_page,
|
||||
* boot/overlay capture-window boundary is required: those windows
|
||||
* stabilize region keys, but they are not MIPS execution barriers.
|
||||
* Adjacent dirty pages were already folded into this run. */
|
||||
const uint32_t ram_size = 2u * 1024u * 1024u;
|
||||
const uint32_t ram_size = psx_ram_live_bytes();
|
||||
uint32_t guard_bytes = 0u;
|
||||
if (phys <= ram_size && size <= ram_size - phys &&
|
||||
phys + size <= ram_size - 4u) {
|
||||
size += 4u;
|
||||
guard_bytes = 4u;
|
||||
}
|
||||
uint32_t virt = 0x80000000u | (phys & 0x1FFFFFu);
|
||||
uint32_t virt = 0x80000000u | psx_ram_canonical_offset(phys);
|
||||
in_run = 0;
|
||||
|
||||
/* Seeds: only per-PC interpreter hits — execution-verified. */
|
||||
@@ -801,7 +801,7 @@ static void capture_executed_pages(uint32_t *bitmap, uint32_t bw,
|
||||
uint32_t scope_lo, uint32_t scope_hi,
|
||||
int include_halo)
|
||||
{
|
||||
const uint32_t ram_size = 2u * 1024u * 1024u;
|
||||
const uint32_t ram_size = psx_ram_live_bytes();
|
||||
memset(bitmap, 0, (size_t)bw * sizeof(uint32_t));
|
||||
if (scope_hi > ram_size) scope_hi = ram_size;
|
||||
if (scope_lo >= scope_hi) return;
|
||||
@@ -855,16 +855,16 @@ void overlay_capture_write_json(void)
|
||||
* can otherwise merge them into a giant region that changes every run.
|
||||
* Final/manual captures use the same executed-page scope as autocapture. */
|
||||
(void)overlay_capture_write_current("shutdown-or-manual",
|
||||
0, 2u * 1024u * 1024u, 0);
|
||||
0, psx_ram_live_bytes(), 0);
|
||||
}
|
||||
|
||||
void overlay_capture_before_dma(uint32_t load_addr, uint32_t size)
|
||||
{
|
||||
if (!s_enabled || !s_active || size == 0) return;
|
||||
uint32_t lo = load_addr & 0x1FFFFFu;
|
||||
uint32_t lo = psx_ram_canonical_offset(load_addr);
|
||||
uint32_t hi = lo + size;
|
||||
if (lo >= 2u * 1024u * 1024u) return;
|
||||
if (hi > 2u * 1024u * 1024u || hi < lo) hi = 2u * 1024u * 1024u;
|
||||
if (lo >= psx_ram_live_bytes()) return;
|
||||
if (hi > psx_ram_live_bytes() || hi < lo) hi = psx_ram_live_bytes();
|
||||
|
||||
/* Snapshot complete pages touched by this DMA. Page scope prevents sticky
|
||||
* dirty runs from turning a small sector replacement into a multi-megabyte
|
||||
@@ -1191,7 +1191,7 @@ static AutocapWriteJob *capture_snapshot_create(uint32_t scope_lo,
|
||||
int scoped)
|
||||
{
|
||||
extern uint8_t *memory_get_ram_ptr(void);
|
||||
const size_t ram_size = 2u * 1024u * 1024u;
|
||||
const size_t ram_size = psx_ram_live_bytes();
|
||||
uint32_t bw = dirty_ram_get_bitmap_word_count();
|
||||
AutocapWriteJob *job = (AutocapWriteJob *)calloc(1, sizeof(*job));
|
||||
if (!job) return NULL;
|
||||
@@ -1229,7 +1229,7 @@ static int autocap_write_start(void)
|
||||
* RAM. Evidence-scoping keeps the background manifest proportional to live
|
||||
* coverage and avoids multi-megabyte rewrites every cooldown. */
|
||||
AutocapWriteJob *job = capture_snapshot_create(
|
||||
0, 2u * 1024u * 1024u, 1);
|
||||
0, psx_ram_live_bytes(), 1);
|
||||
if (!job) return 0;
|
||||
if (!autocap_write_launch(job)) {
|
||||
s_autocap_write_job = NULL;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "psx_cycles.h"
|
||||
#include "lockstep.h"
|
||||
#include "overlay_posix.h"
|
||||
#include "psx_memory.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -116,7 +117,7 @@ static int s_cand_n = 0;
|
||||
* scales catastrophically once a warmed cache contains hundreds of variant
|
||||
* DLLs. Index candidates by the 4 KiB RAM pages touched by their code ranges;
|
||||
* a continuation then examines only candidates that could contain its PC. */
|
||||
#define RANGE_PAGE_COUNT (2u * 1024u * 1024u / 4096u)
|
||||
#define RANGE_PAGE_COUNT (PSX_MAIN_RAM_BACKING_BYTES / 4096u)
|
||||
#define RANGE_LINK_CAP (CAND_CAP * 8)
|
||||
typedef struct { int cand, next; } RangeLink;
|
||||
static int s_range_page_head[RANGE_PAGE_COUNT];
|
||||
@@ -182,7 +183,7 @@ static uint32_t s_exact_entry_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS];
|
||||
|
||||
static void exact_entry_set(uint32_t phys) {
|
||||
phys &= 0x1FFFFFFFu;
|
||||
if (phys < 2u * 1024u * 1024u && (phys & 3u) == 0u) {
|
||||
if (phys < PSX_MAIN_RAM_BACKING_BYTES && (phys & 3u) == 0u) {
|
||||
uint32_t word = phys >> 2;
|
||||
s_exact_entry_bitmap[word >> 5] |= 1u << (word & 31u);
|
||||
}
|
||||
@@ -190,7 +191,7 @@ static void exact_entry_set(uint32_t phys) {
|
||||
|
||||
static int exact_entry_has(uint32_t phys) {
|
||||
phys &= 0x1FFFFFFFu;
|
||||
if (phys >= 2u * 1024u * 1024u || (phys & 3u) != 0u) return 0;
|
||||
if (phys >= PSX_MAIN_RAM_BACKING_BYTES || (phys & 3u) != 0u) return 0;
|
||||
uint32_t word = phys >> 2;
|
||||
return (s_exact_entry_bitmap[word >> 5] >> (word & 31u)) & 1u;
|
||||
}
|
||||
@@ -574,6 +575,15 @@ typedef struct {
|
||||
} StaticMatchCache;
|
||||
|
||||
static StaticMatchCache s_static_match_cache[STATIC_MATCH_CACHE_CAP];
|
||||
|
||||
/* Main-RAM offset of a variant code range, through the one geometry contract
|
||||
* (psx_memory.h). Retail targets fold the 2nd-4th mirrors of the 8 MiB KSEG
|
||||
* decode window and expanded targets decode it uniquely -- the same fold the
|
||||
* CPU applies, so an image a game places in a mirror (a mod engine copied to
|
||||
* 0x80780000 on 2 MiB hardware) is gated on exactly the bytes that execute. */
|
||||
static int static_range_offset(uint32_t lo, uint32_t len, uint32_t *off) {
|
||||
return len != 0u && psx_ram_resolve(lo, len, off);
|
||||
}
|
||||
static uint64_t s_static_match_rehashes = 0;
|
||||
static uint64_t s_static_match_crc_misses = 0;
|
||||
static uint64_t s_static_match_gen_fastpath = 0;
|
||||
@@ -589,14 +599,13 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs,
|
||||
|
||||
uint32_t gen_sum = 0;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
uint32_t lo = lo_len_pairs[i * 2u] & 0x1FFFFFFFu;
|
||||
uint32_t off = 0;
|
||||
uint32_t len = lo_len_pairs[i * 2u + 1u];
|
||||
if (len == 0u || lo >= 2u * 1024u * 1024u ||
|
||||
len > 2u * 1024u * 1024u - lo) {
|
||||
if (!static_range_offset(lo_len_pairs[i * 2u], len, &off)) {
|
||||
s_static_match_crc_misses++;
|
||||
return 0;
|
||||
}
|
||||
gen_sum += overlay_watch_pagegen_sum(lo, len);
|
||||
gen_sum += overlay_watch_pagegen_sum(off, len);
|
||||
}
|
||||
|
||||
uintptr_t raw = (uintptr_t)lo_len_pairs;
|
||||
@@ -632,15 +641,16 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs,
|
||||
* a band that swapped occupants would keep dispatching the stale variant.
|
||||
* Arming only sets bitmap bits -- it does not advance any generation, so
|
||||
* the gen_sum computed above stays valid for the cache write below. */
|
||||
for (uint32_t i = 0; i < count; i++)
|
||||
overlay_watch_set_range(lo_len_pairs[i * 2u] & 0x1FFFFFFFu,
|
||||
lo_len_pairs[i * 2u + 1u]);
|
||||
|
||||
uint32_t crc = 0xFFFFFFFFu;
|
||||
uint32_t lo_min = 0xFFFFFFFFu, hi_max = 0;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
uint32_t lo = lo_len_pairs[i * 2u] & 0x1FFFFFFFu;
|
||||
uint32_t off = 0;
|
||||
uint32_t len = lo_len_pairs[i * 2u + 1u];
|
||||
crc = crc32_update(crc, ram + lo, len);
|
||||
(void)static_range_offset(lo_len_pairs[i * 2u], len, &off); /* checked above */
|
||||
overlay_watch_set_range(off, len);
|
||||
crc = crc32_update(crc, ram + off, len);
|
||||
if (off < lo_min) lo_min = off;
|
||||
if (off + len > hi_max) hi_max = off + len;
|
||||
}
|
||||
crc ^= 0xFFFFFFFFu;
|
||||
int matches = (crc == expected_crc) ? 1 : 0;
|
||||
@@ -653,13 +663,6 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs,
|
||||
entry->expected_crc = expected_crc;
|
||||
entry->gen_sum = gen_sum;
|
||||
entry->matches = matches;
|
||||
uint32_t lo_min = 0xFFFFFFFFu, hi_max = 0;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
uint32_t lo = lo_len_pairs[i * 2u] & 0x1FFFFFFFu;
|
||||
uint32_t hi = lo + lo_len_pairs[i * 2u + 1u];
|
||||
if (lo < lo_min) lo_min = lo;
|
||||
if (hi > hi_max) hi_max = hi;
|
||||
}
|
||||
entry->lo_min = lo_min;
|
||||
entry->hi_max = hi_max;
|
||||
}
|
||||
@@ -687,17 +690,22 @@ int psx_overlay_static_code_matches(const uint32_t *lo_len_pairs,
|
||||
* to a capture / .EMI section offline. Called only on EXTERNAL interp
|
||||
* entries, so a 4096-slot scan is fine. */
|
||||
uint32_t psx_overlay_resident_crc_at(uint32_t phys, int *valid) {
|
||||
phys &= 0x1FFFFFFFu;
|
||||
uint32_t stale = 0;
|
||||
if (!psx_ram_resolve(phys, 1u, &phys)) {
|
||||
if (valid) *valid = 0;
|
||||
return s_last_crc;
|
||||
}
|
||||
for (uint32_t i = 0; i < STATIC_MATCH_CACHE_CAP; i++) {
|
||||
const StaticMatchCache *e = &s_static_match_cache[i];
|
||||
if (!e->ranges || phys < e->lo_min || phys >= e->hi_max)
|
||||
continue;
|
||||
if (e->matches) {
|
||||
uint32_t gen_sum = 0;
|
||||
for (uint32_t k = 0; k < e->count; k++)
|
||||
gen_sum += overlay_watch_pagegen_sum(e->ranges[k * 2u] & 0x1FFFFFFFu,
|
||||
e->ranges[k * 2u + 1u]);
|
||||
for (uint32_t k = 0; k < e->count; k++) {
|
||||
uint32_t off = 0;
|
||||
(void)static_range_offset(e->ranges[k * 2u], e->ranges[k * 2u + 1u], &off);
|
||||
gen_sum += overlay_watch_pagegen_sum(off, e->ranges[k * 2u + 1u]);
|
||||
}
|
||||
if (gen_sum == e->gen_sum) {
|
||||
if (valid) *valid = 1;
|
||||
return e->expected_crc;
|
||||
@@ -737,7 +745,11 @@ typedef struct {
|
||||
int n;
|
||||
} ManFn;
|
||||
|
||||
#define OVERLAY_RAM_SIZE (2u * 1024u * 1024u)
|
||||
/* Manifests are parsed at loader init, before memory_init() latches the live
|
||||
* geometry, so structural validity is the whole KSEG0 decode window. Whether
|
||||
* the bytes there are live code is decided per dispatch by the range CRC. */
|
||||
#define OVERLAY_RAM_SIZE PSX_MAIN_RAM_WINDOW_BYTES
|
||||
#define OVERLAY_KSEG0_WINDOW_MASK (~(PSX_MAIN_RAM_WINDOW_BYTES - 1u))
|
||||
#define MANIFEST_LINE_MAX 128u
|
||||
#define MANIFEST_PHYSICAL_LINE_MAX 159u
|
||||
#define MANIFEST_PROVENANCE_PREFIX "# psxrecomp overlay provenance "
|
||||
@@ -751,12 +763,12 @@ enum {
|
||||
static int man_structurally_valid(const ManFn *m) {
|
||||
if (!m || !m->has_crc || m->n < 1 || m->n > MAX_CODE_RANGES)
|
||||
return 0;
|
||||
if ((m->entry & 0xFFE00000u) != 0x80000000u) return 0;
|
||||
if ((m->entry & OVERLAY_KSEG0_WINDOW_MASK) != 0x80000000u) return 0;
|
||||
uint32_t entry = m->entry & 0x1FFFFFFFu;
|
||||
if ((entry & 3u) != 0u || entry >= OVERLAY_RAM_SIZE) return 0;
|
||||
int entry_covered = 0;
|
||||
for (int r = 0; r < m->n; r++) {
|
||||
if ((m->lo[r] & 0xFFE00000u) != 0x80000000u) return 0;
|
||||
if ((m->lo[r] & OVERLAY_KSEG0_WINDOW_MASK) != 0x80000000u) return 0;
|
||||
uint32_t lo = m->lo[r] & 0x1FFFFFFFu;
|
||||
uint32_t len = m->len[r];
|
||||
if ((lo & 3u) != 0u || (len & 3u) != 0u || len < 4u ||
|
||||
@@ -970,7 +982,7 @@ static int mips_control_kind(uint32_t instr) {
|
||||
static int ranges_contain_word(const uint32_t *lo_list,
|
||||
const uint32_t *len_list, int n,
|
||||
uint32_t phys) {
|
||||
if ((phys & 3u) != 0u || phys > (2u * 1024u * 1024u) - 4u) return 0;
|
||||
if ((phys & 3u) != 0u || phys > OVERLAY_RAM_SIZE - 4u) return 0;
|
||||
for (int r = 0; r < n; r++) {
|
||||
uint32_t lo = lo_list[r] & 0x1FFFFFFFu;
|
||||
uint32_t len = len_list[r];
|
||||
@@ -986,7 +998,7 @@ static int ranges_contain_word(const uint32_t *lo_list,
|
||||
static int ranges_delay_slots_hashed(const uint32_t *lo_list,
|
||||
const uint32_t *len_list, int n) {
|
||||
const uint8_t *ram = memory_get_ram_ptr();
|
||||
const uint32_t ram_size = 2u * 1024u * 1024u;
|
||||
const uint32_t ram_size = psx_ram_live_bytes();
|
||||
if (!ram || n < 1 || n > MAX_CODE_RANGES) return 0;
|
||||
for (int r = 0; r < n; r++) {
|
||||
uint32_t lo = lo_list[r] & 0x1FFFFFFFu;
|
||||
@@ -4078,9 +4090,11 @@ int overlay_fp_enabled(void) {
|
||||
* so the comparison isolates COMPUTATION (and is longjmp-safe). A divergence
|
||||
* here = a real codegen bug (function + exact register/RAM). Zero divergence =
|
||||
* computation is correct and the fault is timing/interrupt-ordering. */
|
||||
#define SHADOW_RAM_SIZE (2u * 1024u * 1024u)
|
||||
/* Snapshots cover the live RAM (retail 2 MiB unless the 8 MB mod is on). */
|
||||
#define SHADOW_RAM_SIZE psx_ram_live_bytes()
|
||||
#define SHADOW_SPAD_SIZE 1024u
|
||||
static uint8_t s_ram0[SHADOW_RAM_SIZE], s_ramN[SHADOW_RAM_SIZE], s_ramI[SHADOW_RAM_SIZE];
|
||||
static uint8_t s_ram0[PSX_MAIN_RAM_BACKING_BYTES], s_ramN[PSX_MAIN_RAM_BACKING_BYTES],
|
||||
s_ramI[PSX_MAIN_RAM_BACKING_BYTES];
|
||||
static uint8_t s_spad0[SHADOW_SPAD_SIZE], s_spadI[SHADOW_SPAD_SIZE];
|
||||
static uint64_t s_shadow_skipped_dev = 0; /* unsafe/incomplete traces skipped */
|
||||
/* s_diff_mode / s_in_shadow declared above (before dispatch). */
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "pgxp.h"
|
||||
#include "pgxp_hooks.h"
|
||||
#include "cpu_state.h"
|
||||
#include "psx_memory.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -61,12 +62,14 @@ struct PGXPValue {
|
||||
uint32_t gen; /* valid iff == s_gen (O(1) invalidate-all) */
|
||||
};
|
||||
|
||||
#define PGXP_RAM_WORDS (0x200000u >> 2) /* 2 MB RAM */
|
||||
/* Shadow covers the host RAM backing so the opt-in 8 MB map tracks its high
|
||||
* banks; retail sessions only ever touch the low 2 MiB of it. */
|
||||
#define PGXP_RAM_WORDS (PSX_MAIN_RAM_BACKING_BYTES >> 2)
|
||||
#define PGXP_SCRATCH_WORDS (0x400u >> 2) /* 1 KB scratchpad */
|
||||
#define PGXP_REG_HI 32
|
||||
#define PGXP_REG_LO 33
|
||||
|
||||
static PGXPValue *s_ram = nullptr; /* lazily allocated, ~10 MB */
|
||||
static PGXPValue *s_ram = nullptr; /* lazily allocated, ~40 MB VA */
|
||||
static PGXPValue s_scratch[PGXP_SCRATCH_WORDS];
|
||||
static PGXPValue s_gpr[34]; /* 32 GPRs + HI + LO */
|
||||
static PGXPValue s_gte[32]; /* GTE data registers */
|
||||
@@ -153,8 +156,8 @@ extern "C" void pgxp_get_stats(PGXPStats *out) {
|
||||
/* Guest address -> shadow slot, or NULL for BIOS/MMIO/KSEG2 (untrackable). */
|
||||
static inline PGXPValue *pgxp_ptr(uint32_t addr) {
|
||||
uint32_t m = addr & 0x1FFFFFFFu;
|
||||
if (m < 0x00800000u) /* RAM + its mirrors */
|
||||
return s_ram ? &s_ram[(m & 0x1FFFFCu) >> 2] : nullptr;
|
||||
if (m < PSX_MAIN_RAM_WINDOW_BYTES) /* RAM + its mirrors (live map) */
|
||||
return s_ram ? &s_ram[psx_ram_canonical_offset(m) >> 2] : nullptr;
|
||||
if ((m & 0xFFFFFC00u) == 0x1F800000u) /* scratchpad */
|
||||
return &s_scratch[(m & 0x3FCu) >> 2];
|
||||
return nullptr;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "psx_netplay_rb.h"
|
||||
#include "psx_memory.h"
|
||||
#include "cpu_state.h" /* full CPUState — stub path assigns *out = *in */
|
||||
|
||||
#if !defined(PSX_HAS_RECOMP_NET)
|
||||
@@ -1579,8 +1580,8 @@ static int rb_resume_pc_ok(uint32_t pc)
|
||||
if ((pc & 0xfff00000u) == 0xbfc00000u)
|
||||
return 1; /* BIOS ROM */
|
||||
phys = pc & 0x1fffffffu;
|
||||
/* 2MB main RAM; skip the low scratch page (catches 0xB0 / 0x800000B0). */
|
||||
if (phys >= 0x1000u && phys < 0x200000u)
|
||||
/* Live main RAM; skip the low scratch page (catches 0xB0 / 0x800000B0). */
|
||||
if (phys >= 0x1000u && phys < psx_ram_live_bytes())
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/* psx_ram_geometry.c -- live main-RAM geometry state (see psx_memory.h).
|
||||
*
|
||||
* Retail 2 MiB mirroring is the default and the only geometry reachable
|
||||
* without the opt-in 8 MB main RAM mod. A trusted activation plugin requests
|
||||
* expanded RAM (psx_mod_set_main_ram_8mb) before memory_init(), which applies
|
||||
* the request exactly once per boot (psx_ram_apply_size_request). Keeping the
|
||||
* state machine here, free of memory.c's device dependencies, lets the retail
|
||||
* guarantee be unit-tested against the same code the runtime links. */
|
||||
#include "psx_memory.h"
|
||||
#include "mod_plugins.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
uint32_t g_psx_ram_size = PSX_MAIN_RAM_RETAIL_BYTES;
|
||||
uint32_t g_psx_ram_mask = PSX_MAIN_RAM_RETAIL_BYTES - 1u;
|
||||
uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS];
|
||||
|
||||
static int s_ram_8mb_requested;
|
||||
static uint32_t s_ram_high_registered[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_MAIN_RAM_EXPANDED_BYTES >> 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;
|
||||
}
|
||||
|
||||
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_MAIN_RAM_WINDOW_BYTES)
|
||||
return;
|
||||
end = phys + len;
|
||||
if (end < phys || end > PSX_MAIN_RAM_WINDOW_BYTES)
|
||||
end = PSX_MAIN_RAM_WINDOW_BYTES;
|
||||
if (end <= PSX_MAIN_RAM_RETAIL_BYTES)
|
||||
return;
|
||||
if (phys < PSX_MAIN_RAM_RETAIL_BYTES)
|
||||
phys = PSX_MAIN_RAM_RETAIL_BYTES;
|
||||
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(const uint8_t *ram) {
|
||||
uint32_t page;
|
||||
if (g_psx_ram_size <= PSX_MAIN_RAM_RETAIL_BYTES) {
|
||||
memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique));
|
||||
return;
|
||||
}
|
||||
memcpy(g_psx_ram_high_unique, s_ram_high_registered,
|
||||
sizeof(g_psx_ram_high_unique));
|
||||
if (!ram) return;
|
||||
for (page = PSX_RAM_HIGH_PAGE0;
|
||||
page < (PSX_MAIN_RAM_EXPANDED_BYTES >> 12); page++) {
|
||||
uint32_t dst = page << 12;
|
||||
uint32_t src = dst & (PSX_MAIN_RAM_RETAIL_BYTES - 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 memory_get_ram_bytes(void) { return g_psx_ram_size; }
|
||||
int psx_ram_8mb_active(void) {
|
||||
return g_psx_ram_size > PSX_MAIN_RAM_RETAIL_BYTES;
|
||||
}
|
||||
|
||||
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_MAIN_RAM_RETAIL_BYTES,
|
||||
PSX_MAIN_RAM_EXPANDED_BYTES -
|
||||
PSX_MAIN_RAM_RETAIL_BYTES);
|
||||
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;
|
||||
}
|
||||
|
||||
void psx_ram_apply_size_request(void) {
|
||||
if (s_ram_8mb_requested) {
|
||||
g_psx_ram_size = PSX_MAIN_RAM_EXPANDED_BYTES;
|
||||
g_psx_ram_mask = PSX_MAIN_RAM_EXPANDED_BYTES - 1u;
|
||||
if (!psx_ram_any_high_registered())
|
||||
psx_ram_register_unique(PSX_MAIN_RAM_RETAIL_BYTES,
|
||||
PSX_MAIN_RAM_EXPANDED_BYTES -
|
||||
PSX_MAIN_RAM_RETAIL_BYTES);
|
||||
memcpy(g_psx_ram_high_unique, s_ram_high_registered,
|
||||
sizeof(g_psx_ram_high_unique));
|
||||
} else {
|
||||
g_psx_ram_size = PSX_MAIN_RAM_RETAIL_BYTES;
|
||||
g_psx_ram_mask = PSX_MAIN_RAM_RETAIL_BYTES - 1u;
|
||||
memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique));
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "psx_selfcheck.h"
|
||||
#include "psx_memory.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -925,7 +926,7 @@ static int sc_resume_pc_ok(uint32_t pc)
|
||||
if ((pc & 0xfff00000u) == 0xbfc00000u)
|
||||
return 1;
|
||||
phys = pc & 0x1fffffffu;
|
||||
if (phys >= 0x1000u && phys < 0x200000u)
|
||||
if (phys >= 0x1000u && phys < psx_ram_live_bytes())
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -582,7 +582,8 @@ int savestate_read_slot(int slot, uint8_t** data_out, size_t* size_out) {
|
||||
if (!f) return 0;
|
||||
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return 0; }
|
||||
sz = ftell(f);
|
||||
if (sz <= 0 || (size_t)sz > 8u * 1024u * 1024u) { fclose(f); return 0; }
|
||||
/* Raw 8 MB-RAM states run ~11.5 MiB (RAM + VRAM + SPU + dirty maps). */
|
||||
if (sz <= 0 || (size_t)sz > 16u * 1024u * 1024u) { fclose(f); return 0; }
|
||||
if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return 0; }
|
||||
buf = (uint8_t*)malloc((size_t)sz);
|
||||
if (!buf) { fclose(f); return 0; }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* See text_xlate.h + docs/STRING_TRANSLATION.md. */
|
||||
|
||||
#include "text_xlate.h"
|
||||
#include "psx_memory.h"
|
||||
#include "cpu_state.h"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -36,14 +37,13 @@ namespace {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Guest RAM access (little-endian, no swizzle). Main RAM is 2 MB, mirrored
|
||||
// across [0,0x800000). Returns 0 / no-op for out-of-range.
|
||||
// Guest RAM access (little-endian, no swizzle). The 8 MiB decode window
|
||||
// [0,0x800000) folds through the live geometry (retail: 2 MiB mirrored four
|
||||
// times). Returns 0 / no-op for out-of-range.
|
||||
// ---------------------------------------------------------------------------
|
||||
constexpr uint32_t kRamSize = 2u * 1024u * 1024u;
|
||||
|
||||
inline bool ram_fold(uint32_t va, uint32_t* pa_out) {
|
||||
uint32_t p = va & 0x1FFFFFFFu;
|
||||
if (p < 0x00800000u) { *pa_out = p & (kRamSize - 1u); return true; }
|
||||
if (p < PSX_MAIN_RAM_WINDOW_BYTES) { *pa_out = psx_ram_canonical_offset(p); return true; }
|
||||
return false; // I/O / BIOS / scratchpad — not translatable text storage
|
||||
}
|
||||
inline uint8_t grb(uint8_t* ram, uint32_t va) {
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "cpu_state.h"
|
||||
#include "psx_memory.h"
|
||||
#include "psx_bss.h"
|
||||
#include "psx_runtime.h" /* fix B: psx_exc_escape_reason_t + g_exc_escape_reason */
|
||||
#include "debug_server.h"
|
||||
@@ -1209,7 +1210,7 @@ void psx_unknown_dispatch(CPUState* cpu, uint32_t addr, uint32_t phys) {
|
||||
int hits = 0;
|
||||
pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos,
|
||||
"\n RAM scan for 0x%08X:\n", needles[n]);
|
||||
for (uint32_t off = 0; off + 4 <= 2u * 1024u * 1024u; off += 4) {
|
||||
for (uint32_t off = 0; off + 4 <= psx_ram_live_bytes(); off += 4) {
|
||||
uint32_t word;
|
||||
memcpy(&word, ram + off, sizeof(word));
|
||||
if (word != needles[n]) continue;
|
||||
@@ -1220,7 +1221,7 @@ void psx_unknown_dispatch(CPUState* cpu, uint32_t addr, uint32_t phys) {
|
||||
}
|
||||
if (!hits)
|
||||
pos += snprintf(buf + pos, sizeof(buf) - (size_t)pos,
|
||||
" (not found in 2MB PSX RAM)\n");
|
||||
" (not found in live PSX RAM)\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ def main() -> int:
|
||||
read = cd_slice.index("uint32_t word = cdrom_dma_read();")
|
||||
store = cd_slice.index("psx_write_word(addr, word);", read)
|
||||
admit = cd_slice.index("dirty_ram_mark_executable_range(addr, 4);", store)
|
||||
advance = cd_slice.index("addr = (addr + addr_step)", admit)
|
||||
advance = cd_slice.index("addr = DMA_RAM_ADDR((addr + addr_step))", admit)
|
||||
if not read < store < admit < advance:
|
||||
raise AssertionError("CD DMA overlay admission is not coupled to each RAM word")
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ harness=r"""
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "psx_memory.h"
|
||||
uint32_t g_psx_ram_size=PSX_MAIN_RAM_RETAIL_BYTES,g_psx_ram_mask=PSX_MAIN_RAM_RETAIL_BYTES-1u;
|
||||
uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS];
|
||||
static uint32_t gp0_cmd_buf[16],g_debug_current_func_addr;
|
||||
static uint16_t vram_write_x,vram_write_y,vram_write_w,vram_write_h,vram_write_col,vram_write_row;
|
||||
static unsigned vram_write_remaining,s_frame_count;
|
||||
@@ -55,5 +58,7 @@ with tempfile.TemporaryDirectory() as d:
|
||||
root=Path(d);c=root/'history.c';c.write_text(harness)
|
||||
for opt in ('-O0','-O2'):
|
||||
exe=root/(opt+'.exe')
|
||||
subprocess.run([a.cc,opt,'-std=c11','-Wall','-Wextra','-Werror',str(c),'-o',str(exe)],check=True)
|
||||
subprocess.run([a.cc,opt,'-std=c11','-Wall','-Wextra','-Werror',
|
||||
'-I',str(a.source.resolve().parents[1]/'include'),
|
||||
str(c),'-o',str(exe)],check=True)
|
||||
subprocess.run([str(exe)],check=True)
|
||||
|
||||
@@ -49,7 +49,6 @@ extern "C" int gpu_ws_precise_nclip_enabled(void) {
|
||||
return g_test_precise_nclip_enabled;
|
||||
}
|
||||
extern "C" void gpu_pgxp_rederive_enable(void) {}
|
||||
extern "C" uint32_t memory_get_ram_bytes(void) { return 2u * 1024u * 1024u; }
|
||||
extern "C" void psx_ws_note_gte_project(int) {}
|
||||
extern "C" {
|
||||
uint64_t s_frame_count = 0;
|
||||
|
||||
@@ -60,7 +60,7 @@ def main() -> int:
|
||||
|
||||
dirty_gate = body.find("!dirty_ram_is_dirty(phys) && !clean_game_text_miss")
|
||||
region_gate = body.find("phys_is_overlay_region(phys)", dirty_gate)
|
||||
ram_gate = body.find("phys < (2u * 1024u * 1024u)", dirty_gate)
|
||||
ram_gate = body.find("phys < psx_ram_live_bytes()", dirty_gate)
|
||||
decode_gate = body.find("dirty_ram_word_looks_decodable(fetch_word(phys))", dirty_gate)
|
||||
mark = body.find("dirty_ram_mark_executable_range(phys, 4u)", dirty_gate)
|
||||
if min(dirty_gate, region_gate, ram_gate, decode_gate, mark) < 0:
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
#include "psx_memory.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* The live geometry normally lives in memory.c; this test owns it so each
|
||||
* build of the test pins one geometry the way memory_init() would. */
|
||||
uint32_t g_psx_ram_size = PSX_MAIN_RAM_RETAIL_BYTES;
|
||||
uint32_t g_psx_ram_mask = PSX_MAIN_RAM_RETAIL_BYTES - 1u;
|
||||
uint32_t g_psx_ram_high_unique[PSX_RAM_HIGH_BITWORDS];
|
||||
|
||||
static int failures;
|
||||
|
||||
@@ -16,6 +23,14 @@ static void check(int condition, const char *name) {
|
||||
int main(void) {
|
||||
uint32_t off = 0xFFFFFFFFu;
|
||||
|
||||
#if defined(PSX_MEMORY_GEOMETRY_TEST_EXPANDED)
|
||||
/* What the 8 MB mod leaves behind after memory_init(): expanded size and
|
||||
* every high page registered unique. */
|
||||
g_psx_ram_size = PSX_MAIN_RAM_EXPANDED_BYTES;
|
||||
g_psx_ram_mask = PSX_MAIN_RAM_EXPANDED_BYTES - 1u;
|
||||
memset(g_psx_ram_high_unique, 0xFF, sizeof(g_psx_ram_high_unique));
|
||||
#endif
|
||||
|
||||
check(psx_ram_resolve(0x80000000u, 4u, &off) && off == 0u,
|
||||
"KSEG0 aliases physical RAM");
|
||||
check(psx_ram_resolve(0xA0000100u, 4u, &off) && off == 0x100u,
|
||||
@@ -24,23 +39,31 @@ int main(void) {
|
||||
"first byte beyond DRAM decode is rejected");
|
||||
check(!psx_ram_resolve(0x1F801000u, 4u, &off),
|
||||
"MMIO is never mistaken for DRAM");
|
||||
check(!psx_ram_resolve(PSX_MAIN_RAM_BYTES - 2u, 4u, &off),
|
||||
check(!psx_ram_resolve(psx_ram_live_bytes() - 2u, 4u, &off),
|
||||
"cross-geometry word is rejected");
|
||||
|
||||
#if PSX_MAIN_RAM_BYTES == PSX_MAIN_RAM_RETAIL_BYTES
|
||||
#if !defined(PSX_MEMORY_GEOMETRY_TEST_EXPANDED)
|
||||
check(psx_ram_canonical_offset(0x80612340u) == 0x00012340u,
|
||||
"retail canonicalization folds KSEG0 mirrors");
|
||||
check(psx_ram_resolve(0x00212340u, 4u, &off) && off == 0x00012340u,
|
||||
"retail geometry folds the second mirror");
|
||||
check(psx_ram_resolve(0x807FFFFCu, 4u, &off) && off == 0x001FFFFCu,
|
||||
"retail geometry folds the fourth mirror top");
|
||||
#elif PSX_MAIN_RAM_BYTES == PSX_MAIN_RAM_EXPANDED_BYTES
|
||||
check(psx_ram_canon_code_addr_inline(0x80780000u) == 0x80180000u,
|
||||
"retail code address folds the fourth mirror");
|
||||
check(psx_ram_live_bytes() == PSX_MAIN_RAM_RETAIL_BYTES,
|
||||
"retail live size is 2 MiB");
|
||||
#else
|
||||
check(psx_ram_canonical_offset(0x80612340u) == 0x00612340u,
|
||||
"expanded canonicalization preserves all decoded address bits");
|
||||
check(psx_ram_resolve(0x00212340u, 4u, &off) && off == 0x00212340u,
|
||||
"expanded geometry uniquely decodes the second bank");
|
||||
check(psx_ram_resolve(0x807FFFFCu, 4u, &off) && off == 0x007FFFFCu,
|
||||
"expanded geometry uniquely decodes the eighth MiB top");
|
||||
check(psx_ram_canon_code_addr_inline(0x80780000u) == 0x80780000u,
|
||||
"expanded code address stays in the high bank");
|
||||
check(psx_ram_live_bytes() == PSX_MAIN_RAM_EXPANDED_BYTES,
|
||||
"expanded live size is 8 MiB");
|
||||
#endif
|
||||
|
||||
return failures ? 1 : 0;
|
||||
|
||||
@@ -1,41 +1,131 @@
|
||||
#include "psx_ram.h"
|
||||
/* Live main-RAM geometry: links the runtime's own psx_ram_geometry.c so the
|
||||
* state machine memory_init() drives is the thing under test.
|
||||
*
|
||||
* The load-bearing claim is the opt-in guarantee: unless the 8 MB mod asks for
|
||||
* expanded RAM before memory_init(), every address in the 8 MiB decode window
|
||||
* folds onto the 2 MiB retail DRAM exactly as hardware mirrors it, for loads,
|
||||
* stores, code addresses and span resolution alike. */
|
||||
#include "psx_memory.h"
|
||||
#include "mod_plugins.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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 uint8_t s_ram[PSX_MAIN_RAM_BACKING_BYTES];
|
||||
|
||||
static int failures;
|
||||
|
||||
static int check(int cond, const char *label) {
|
||||
if (!cond) {
|
||||
fprintf(stderr, "FAIL: %s\n", label);
|
||||
failures++;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Every page of the decode window, at its first, middle and last word, through
|
||||
* all three CPU segments: the retail fold must be exact everywhere. */
|
||||
static void check_retail_everywhere(const char *phase) {
|
||||
static const uint32_t segs[3] = { 0x00000000u, 0x80000000u, 0xA0000000u };
|
||||
static const uint32_t offs[3] = { 0x000u, 0x7F0u, 0xFFCu };
|
||||
uint32_t page, s, o, bad = 0;
|
||||
for (page = 0; page < (PSX_MAIN_RAM_WINDOW_BYTES >> 12); page++) {
|
||||
for (o = 0; o < 3; o++) {
|
||||
uint32_t phys = (page << 12) | offs[o];
|
||||
uint32_t want = phys & (PSX_MAIN_RAM_RETAIL_BYTES - 1u);
|
||||
uint32_t off = 0xFFFFFFFFu;
|
||||
if (psx_ram_map_read(phys) != want) bad++;
|
||||
if (psx_ram_map_write(phys) != want) bad++;
|
||||
for (s = 0; s < 3; s++) {
|
||||
uint32_t va = segs[s] | phys;
|
||||
if (psx_ram_canonical_offset(va) != want) bad++;
|
||||
if (!psx_ram_resolve(va, 4u, &off) || off != want) bad++;
|
||||
if (psx_ram_canon_code_addr(va) != (segs[s] | want)) bad++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bad) fprintf(stderr, " %s: %u retail-fold violations\n", phase, bad);
|
||||
check(bad == 0u, phase);
|
||||
check(memory_get_ram_bytes() == PSX_MAIN_RAM_RETAIL_BYTES, phase);
|
||||
check(!psx_ram_8mb_active(), phase);
|
||||
check(!psx_ram_resolve(0x801FFFFEu, 4u, 0), "retail span past 2 MiB end");
|
||||
}
|
||||
|
||||
static void check_expanded_everywhere(const char *phase) {
|
||||
uint32_t page, bad = 0;
|
||||
for (page = 0; page < (PSX_MAIN_RAM_WINDOW_BYTES >> 12); page++) {
|
||||
uint32_t phys = (page << 12) | 0x7F0u;
|
||||
uint32_t off = 0xFFFFFFFFu;
|
||||
if (psx_ram_map_read(phys) != phys) bad++;
|
||||
if (psx_ram_map_write(phys) != phys) bad++;
|
||||
if (psx_ram_canonical_offset(0x80000000u | phys) != phys) bad++;
|
||||
if (!psx_ram_resolve(0x80000000u | phys, 4u, &off) || off != phys) bad++;
|
||||
if (psx_ram_canon_code_addr(0x80000000u | phys) != (0x80000000u | phys))
|
||||
bad++;
|
||||
}
|
||||
if (bad) fprintf(stderr, " %s: %u expanded-decode violations\n", phase, bad);
|
||||
check(bad == 0u, phase);
|
||||
check(memory_get_ram_bytes() == PSX_MAIN_RAM_EXPANDED_BYTES, phase);
|
||||
check(psx_ram_8mb_active(), phase);
|
||||
}
|
||||
|
||||
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");
|
||||
/* Process start: nothing requested. */
|
||||
check_retail_everywhere("default geometry is retail 2 MiB mirroring");
|
||||
|
||||
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");
|
||||
/* memory_init() with no activation plugin. */
|
||||
psx_ram_apply_size_request();
|
||||
check_retail_everywhere("memory_init without the mod stays retail");
|
||||
|
||||
/* mod_runtime_activate_plugins() always resets first; a plan without the
|
||||
* 8 MB package must leave retail in place. */
|
||||
psx_ram_reset_size_request();
|
||||
psx_ram_apply_size_request();
|
||||
check_retail_everywhere("reset + apply without the mod stays retail");
|
||||
|
||||
/* The mod requests expanded RAM; nothing changes until memory_init(). */
|
||||
check(psx_mod_set_main_ram_8mb(1) == 1, "8 MB request accepted");
|
||||
check_retail_everywhere("8 MB request is inert before memory_init");
|
||||
psx_ram_apply_size_request();
|
||||
check_expanded_everywhere("8 MB request applies unique 8 MiB decode");
|
||||
|
||||
/* A later boot whose plan drops the mod returns to retail mirroring. */
|
||||
psx_ram_reset_size_request();
|
||||
psx_ram_apply_size_request();
|
||||
check_retail_everywhere("reboot without the mod restores retail");
|
||||
|
||||
/* Savestate restore in retail never leaves unique pages behind. */
|
||||
memset(g_psx_ram_high_unique, 0xFF, sizeof(g_psx_ram_high_unique));
|
||||
psx_ram_resync_high_after_restore(s_ram);
|
||||
check_retail_everywhere("retail restore clears unique high pages");
|
||||
|
||||
/* Expanded geometry with partial registration: unregistered high pages
|
||||
* keep aliasing the low 2 MiB; registered ones decode uniquely. */
|
||||
psx_mod_set_main_ram_8mb(1);
|
||||
psx_ram_apply_size_request();
|
||||
memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique));
|
||||
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");
|
||||
check(psx_ram_map_read(high) == high,
|
||||
"registered 8 MB high page maps uniquely");
|
||||
check(psx_ram_canon_code_addr_inline(0x80201000u) == 0x80201000u,
|
||||
"registered high code PC remains unique");
|
||||
|
||||
return ok ? 0 : 1;
|
||||
/* Expanded restore resync re-applies the registered (all-high) map. */
|
||||
memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique));
|
||||
psx_mod_set_main_ram_8mb(1);
|
||||
psx_ram_apply_size_request();
|
||||
memset(g_psx_ram_high_unique, 0, sizeof(g_psx_ram_high_unique));
|
||||
psx_ram_resync_high_after_restore(s_ram);
|
||||
check_expanded_everywhere("expanded restore resync keeps unique decode");
|
||||
|
||||
if (failures == 0) printf("PASS psx_ram_runtime_map (%s)\n", "all phases");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -545,7 +545,7 @@ NON_AUTHORITY_MANIFEST_PROVENANCES = {
|
||||
HOSTED_MANIFEST_MARKER = (
|
||||
f'# psxrecomp overlay provenance {HOSTED_MANIFEST_PROVENANCE}')
|
||||
HOSTED_UNIQUE_GUARDED_BYTE_CAP = 1024 * 1024
|
||||
# Full 8 MiB host capacity (PSX_RAM_CAPACITY): 8 MB-mod captures carry
|
||||
# Full 8 MiB host capacity (PSX_MAIN_RAM_BACKING_BYTES): 8 MB-mod captures carry
|
||||
# high-bank enhancement code and their ranges must validate.
|
||||
PSX_RAM_SIZE = 8 * 1024 * 1024
|
||||
|
||||
|
||||
Reference in New Issue
Block a user