From c0fa9b1e89fb6c210011dc25a92b124df43692d4 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 13 Jul 2026 21:52:25 -0700 Subject: [PATCH] overlay: load cached exports on POSIX Port Douglas Vanderveer's manifest-driven macOS/Linux overlay discovery onto the current cache index and exact-dispatch loader. Validate both filename key fields, preserve ABI cleanup and rescan idempotence, and cover the POSIX primitives with a real shared-library fixture. Co-authored-by: douglasjv --- .../upstream/posix-overlay-discovery.md | 37 ++++ runtime/include/overlay_posix.h | 45 +++++ runtime/runtime.cmake | 4 + runtime/src/overlay_loader.c | 177 +++++++++++++++--- runtime/src/overlay_posix.c | 161 ++++++++++++++++ runtime/tests/overlay_posix_fixture.c | 12 ++ runtime/tests/run_overlay_posix_test.sh | 27 +++ runtime/tests/test_overlay_posix.c | 79 ++++++++ 8 files changed, 514 insertions(+), 28 deletions(-) create mode 100644 docs/internal/upstream/posix-overlay-discovery.md create mode 100644 runtime/include/overlay_posix.h create mode 100644 runtime/src/overlay_posix.c create mode 100644 runtime/tests/overlay_posix_fixture.c create mode 100644 runtime/tests/run_overlay_posix_test.sh create mode 100644 runtime/tests/test_overlay_posix.c diff --git a/docs/internal/upstream/posix-overlay-discovery.md b/docs/internal/upstream/posix-overlay-discovery.md new file mode 100644 index 00000000..21bea776 --- /dev/null +++ b/docs/internal/upstream/posix-overlay-discovery.md @@ -0,0 +1,37 @@ +# POSIX overlay discovery provenance + +This change ports one focused capability from Douglas Vanderveer's PSXRecomp +work: Linux/macOS discovery of cached overlay libraries and manifest-driven +resolution of their `func_XXXXXXXX` exports. + +The original implementation was developed while validating the +[R4 recompilation](https://github.com/douglasjv/r4) and appeared in Douglas's +framework commits +[`df7d1faa5f27be0ba357463a763c77efc43e1f91`](https://github.com/douglasjv/psxrecomp-tweaks/commit/df7d1faa5f27be0ba357463a763c77efc43e1f91) +and +[`7abbfdf38b488d3764b96c155549bc930e0521b6`](https://github.com/douglasjv/psxrecomp-tweaks/commit/7abbfdf38b488d3764b96c155549bc930e0521b6). +It is rebuilt here against the newer cache index, ABI tag, codegen namespace, +lazy manifest index, exact native dispatch, and rescan behavior on current +master. The earlier upstream context is +[mstan/psxrecomp PR #15](https://github.com/mstan/psxrecomp/pull/15). + +Included scope: + +- strict `_.dll` validation for both cache-key fields; +- POSIX directory enumeration with GCC-before-TCC deduplication; +- POSIX codegen-tag mismatch diagnostics and ABI preflight cleanup; +- manifest-authoritative `dlsym` registration, with failed/empty libraries + closed and successful libraries retained while their function pointers live; +- additive, idempotent rescan behavior inherited from the current loader. + +Explicitly excluded are frame splitting, SPU changes, forced-interpreter pages, +macOS GL context changes, CPU-copied overlay fallback, game configuration, +R4 addresses or timing policy, and all other R4-specific work. + +The focused Linux test builds a real shared-library fixture, exercises strict +name parsing and directory filtering, detects a sibling codegen tag, resolves a +manifest-style `func_80012345` symbol, and verifies close/reopen behavior: + +```sh +bash runtime/tests/run_overlay_posix_test.sh +``` diff --git a/runtime/include/overlay_posix.h b/runtime/include/overlay_posix.h new file mode 100644 index 00000000..3f3b6473 --- /dev/null +++ b/runtime/include/overlay_posix.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Parsed _.dll cache filename. The parser is shared by the + * Windows and POSIX directory walkers so both platforms reject partial hex + * fields and lookalike suffixes identically. */ +typedef struct PsxOverlayCacheFile { + char name[22]; + char path[768]; + uint32_t region_start; + uint32_t content_crc; + uint64_t mtime; +} PsxOverlayCacheFile; + +typedef int (*PsxOverlayCacheVisitor)(const PsxOverlayCacheFile *file, + void *opaque); + +int psx_overlay_cache_name_parse(const char *name, uint32_t *region_start, + uint32_t *content_crc); + +/* POSIX helpers used by overlay_loader.c and its focused regression test. + * The Windows build provides inert stubs; it keeps using Win32 enumeration and + * LoadLibrary while still sharing the strict filename parser above. */ +int psx_overlay_posix_scan_cache_dir(const char *dir, + PsxOverlayCacheVisitor visitor, + void *opaque); +int psx_overlay_posix_find_other_cache_tag(const char *base_dir, + const char *expected_tag, + char *found_tag, + size_t found_tag_size); +void *psx_overlay_posix_library_open(const char *path, char *error, + size_t error_size); +void *psx_overlay_posix_library_symbol(void *handle, const char *name); +void *psx_overlay_posix_library_entry(void *handle, uint32_t entry); +void psx_overlay_posix_library_close(void *handle); + +#ifdef __cplusplus +} +#endif diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index cc167f95..c4557e7b 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -194,6 +194,7 @@ set(PSXRECOMP_RUNTIME_SOURCES ${PSXRECOMP_ROOT}/runtime/src/card_data_writes.c ${PSXRECOMP_ROOT}/runtime/src/overlay_capture.c ${PSXRECOMP_ROOT}/runtime/src/overlay_loader.c + ${PSXRECOMP_ROOT}/runtime/src/overlay_posix.c ${PSXRECOMP_ROOT}/runtime/src/overlay_compile_worker.c ${PSXRECOMP_ROOT}/runtime/src/overlay_sljit.c ${PSXRECOMP_ROOT}/runtime/src/overlay_backend.c @@ -506,6 +507,9 @@ function(psxrecomp_add_runtime_target target) # by opengl32; Phase 2b will load modern GL via SDL_GL_GetProcAddress. target_link_libraries(${target} PRIVATE ws2_32 dbghelp comdlg32 opengl32) else() + if(CMAKE_DL_LIBS) + target_link_libraries(${target} PRIVATE ${CMAKE_DL_LIBS}) + endif() find_package(OpenGL) if(OpenGL_FOUND) target_link_libraries(${target} PRIVATE OpenGL::GL) diff --git a/runtime/src/overlay_loader.c b/runtime/src/overlay_loader.c index b0efd562..426dbfa9 100644 --- a/runtime/src/overlay_loader.c +++ b/runtime/src/overlay_loader.c @@ -8,6 +8,7 @@ #include "interrupts.h" #include "debug_server.h" #include "psx_cycles.h" +#include "overlay_posix.h" #include #include #include @@ -18,7 +19,7 @@ # define WIN32_LEAN_AND_MEAN # include #else -# include +# include #endif /* ============================================================================ @@ -1059,6 +1060,26 @@ static int cache_idx_has_basename(const char *fname) { const char *overlay_loader_arch_abi(void) { return PSX_OVERLAY_ARCH_ABI; } +#ifndef _WIN32 +static int add_posix_cache_file(const PsxOverlayCacheFile *file, void *opaque) { + (void)opaque; + if (s_cache_idx_count >= CACHE_IDX_CAP) { + loader_log("*** CACHE INDEX FULL (%d): further DLLs near %s are being " + "IGNORED — their regions will run interpreted. Raise " + "CACHE_IDX_CAP.", CACHE_IDX_CAP, file->path); + return 1; + } + if (cache_idx_has_basename(file->name)) return 0; + CacheEntry *e = &s_cache_idx[s_cache_idx_count++]; + e->region_start = file->region_start; + e->mtime = file->mtime; + e->func_count = 0; + e->indexed_func_count = 0; + snprintf(e->path, sizeof(e->path), "%s", file->path); + return 0; +} +#endif + /* Scan one directory for _.dll cache entries into the index. * `dir` is a full directory path. Idempotent (skips already-indexed paths). */ static void scan_one_cache_dir(const char *dir) { @@ -1069,18 +1090,9 @@ static void scan_one_cache_dir(const char *dir) { HANDLE h = FindFirstFileA(pattern, &fd); if (h == INVALID_HANDLE_VALUE) return; do { - if (strlen(fd.cFileName) != 21) continue; /* 8+1+8+4 = 21 */ - /* Validate the _.dll shape explicitly: region_start 0 is - * LEGAL (the kernel-RAM window starts at phys 0), so a zero parse - * result can't be used as the invalid sentinel. */ - int valid = (fd.cFileName[8] == '_'); - for (int ci = 0; valid && ci < 8; ci++) { - char c = fd.cFileName[ci]; - valid = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f'); - } - if (!valid) continue; - uint32_t addr = (uint32_t)strtoul(fd.cFileName, NULL, 16); + uint32_t addr = 0, crc = 0; + if (!psx_overlay_cache_name_parse(fd.cFileName, &addr, &crc)) continue; + (void)crc; if (s_cache_idx_count >= CACHE_IDX_CAP) { /* Never-again (the silent-256 truncation): overflowing the index * means real native coverage is being IGNORED. Shout once. */ @@ -1104,7 +1116,7 @@ static void scan_one_cache_dir(const char *dir) { } while (FindNextFileA(h, &fd)); FindClose(h); #else - (void)dir; + psx_overlay_posix_scan_cache_dir(dir, add_posix_cache_file, NULL); #endif } @@ -1152,10 +1164,82 @@ static void warn_on_cgtag_mismatch(const char *tier) { } while (FindNextFileA(h, &fd)); FindClose(h); #else - (void)tier; + char base[768], expect[64], found[256]; + snprintf(base, sizeof base, "%s/%s/%s/%s", + s_cache_dir, s_game_id, tier, PSX_OVERLAY_ARCH_ABI); + snprintf(expect, sizeof expect, "cg%d_%08x", + PSX_OVERLAY_CODEGEN_VER, (unsigned)PSX_OVERLAY_CODEGEN_HASH); + if (psx_overlay_posix_find_other_cache_tag(base, expect, found, + sizeof(found))) { + loader_log("*** OVERLAY CACHE HASH MISMATCH: this build reads %s/%s but " + "shards exist under %s/%s. The autocompile is writing to a " + "DIFFERENT codegen hash than this runtime reads -> ALL overlays " + "run INTERPRETED (slow). Fix overlay_autocompile_cmd's " + "--recompiler/--runtime-include to match THIS build's framework.", + tier, expect, tier, found); + } #endif } +static void cache_idx_remove_path(const char *path) { + for (int i = 0; i < s_cache_idx_count; i++) { + if (strcmp(s_cache_idx[i].path, path) == 0) { + s_cache_idx[i] = s_cache_idx[--s_cache_idx_count]; + return; + } + } +} + +static void remove_posix_dll_and_manifest(const char *dll_path) { +#ifndef _WIN32 + remove(dll_path); + char ranges[800]; + int n = snprintf(ranges, sizeof(ranges), "%s", dll_path); + if (n >= 4 && (size_t)n + 4 < sizeof(ranges) && + strcmp(ranges + n - 4, ".dll") == 0) { + memcpy(ranges + n - 4, ".ranges", 8); + remove(ranges); + } +#else + (void)dll_path; +#endif +} + +#ifndef _WIN32 +typedef struct PosixAbiSweep { + int purged; + int kept; +} PosixAbiSweep; + +static int posix_abi_sweep_file(const PsxOverlayCacheFile *file, void *opaque) { + PosixAbiSweep *sweep = (PosixAbiSweep *)opaque; + char error[256] = {0}; + void *handle = psx_overlay_posix_library_open(file->path, error, sizeof(error)); + int abi = 0; + if (handle) { + typedef int (*AbiFn)(void); + AbiFn abi_fn = (AbiFn)psx_overlay_posix_library_symbol(handle, "overlay_abi"); + abi = abi_fn ? abi_fn() : 0; + psx_overlay_posix_library_close(handle); + } + if (handle && abi == PSX_OVERLAY_ABI_TAG) { + sweep->kept++; + return 0; + } + + if (handle) + loader_log("ABI preflight rejecting %s: dll=0x%X runtime=0x%X", + file->path, abi, PSX_OVERLAY_ABI_TAG); + else + loader_log("ABI preflight rejecting unloadable %s: %s", + file->path, error); + remove_posix_dll_and_manifest(file->path); + cache_idx_remove_path(file->path); + sweep->purged++; + return 0; +} +#endif + /* ---- ABI pre-flight sweep (batch purge) ---------------------------------- * A contract-ABI bump (e.g. v9 -> v10) invalidates EVERY cached DLL at once. * The lazy path handled that per-dispatch: try_load_region -> LoadLibrary -> @@ -1225,7 +1309,18 @@ static void abi_preflight_sweep(const char *dir) { FILE_ATTRIBUTE_NORMAL, NULL); if (m != INVALID_HANDLE_VALUE) CloseHandle(m); #else - (void)dir; + char marker[900]; + snprintf(marker, sizeof marker, "%s/.abi_%08x.ok", dir, + (unsigned)PSX_OVERLAY_ABI_TAG); + if (access(marker, F_OK) == 0) return; + + PosixAbiSweep sweep = {0}; + psx_overlay_posix_scan_cache_dir(dir, posix_abi_sweep_file, &sweep); + if (sweep.purged) + loader_log("abi preflight: purged %d stale DLL(s), kept %d in %s", + sweep.purged, sweep.kept, dir); + FILE *m = fopen(marker, "wb"); + if (m) fclose(m); #endif } @@ -1833,34 +1928,60 @@ static int load_overlay_dll(const char *dll_path, ManFn *man, int man_n, int dll } #else static int load_overlay_dll(const char *dll_path, ManFn *man, int man_n, int dll) { - (void)man; (void)man_n; (void)dll; - void *h = dlopen(dll_path, RTLD_NOW | RTLD_LOCAL); - if (!h) { loader_log("dlopen(%s) failed: %s", dll_path, dlerror()); return 0; } + char error[256] = {0}; + void *h = psx_overlay_posix_library_open(dll_path, error, sizeof(error)); + if (!h) { loader_log("dlopen(%s) failed: %s", dll_path, error); return 0; } /* ABI gate (see the _WIN32 branch). */ typedef int (*AbiFn)(void); - AbiFn abi_fn = (AbiFn)dlsym(h, "overlay_abi"); + AbiFn abi_fn = (AbiFn)psx_overlay_posix_library_symbol(h, "overlay_abi"); int abi = abi_fn ? abi_fn() : 0; if (abi != PSX_OVERLAY_ABI_TAG) { loader_log("ABI/flavor mismatch in %s: dll=0x%X runtime=0x%X — rejecting " "and deleting stale cache entry", dll_path, abi, PSX_OVERLAY_ABI_TAG); - dlclose(h); - remove(dll_path); + psx_overlay_posix_library_close(h); + remove_posix_dll_and_manifest(dll_path); return 0; } typedef void (*InitFn)(const OverlayCallbacks *); - InitFn init_fn = (InitFn)dlsym(h, "overlay_init"); - if (!init_fn) { loader_log("no overlay_init in %s", dll_path); dlclose(h); return 0; } + InitFn init_fn = (InitFn)psx_overlay_posix_library_symbol(h, "overlay_init"); + if (!init_fn) { + loader_log("no overlay_init in %s", dll_path); + psx_overlay_posix_library_close(h); + return 0; + } init_fn(&s_callbacks); - OverlayFlushFn flush_fn = (OverlayFlushFn)dlsym(h, "overlay_flush_cycles"); + OverlayFlushFn flush_fn = (OverlayFlushFn)psx_overlay_posix_library_symbol( + h, "overlay_flush_cycles"); if (!flush_fn || dll < 0 || dll >= (int)(sizeof(s_dll_flush) / sizeof(s_dll_flush[0]))) { loader_log("no ABI-v11 cycle flush export in %s", dll_path); - dlclose(h); + psx_overlay_posix_library_close(h); return 0; } s_dll_flush[dll] = flush_fn; - loader_log("%s loaded (posix export scan TODO)", dll_path); - return 0; + + /* ELF/Mach-O do not expose a portable export-table walker. The range + * manifest is already the loader's authority, so resolve only its exact + * callable entries. Missing symbols stay interpreted rather than acquiring + * a guessed extent. */ + int registered = 0; + for (int i = 0; i < man_n; i++) { + ManFn *m = &man[i]; + if (m->entry == 0 || m->n == 0) continue; + OverlayFn fn = (OverlayFn)psx_overlay_posix_library_entry(h, m->entry); + if (!fn) continue; + cand_register(m->entry & 0x1FFFFFFFu, fn, m, dll); + registered++; + } + loader_log("loaded %s -> %d manifest candidates", dll_path, registered); + if (registered == 0) { + s_dll_flush[dll] = NULL; + psx_overlay_posix_library_close(h); + } + /* A successful handle intentionally stays open for process lifetime: the + * registered function and flush pointers refer into it. Rescans are + * idempotent through s_loaded_paths and never acquire a second reference. */ + return registered; } #endif diff --git a/runtime/src/overlay_posix.c b/runtime/src/overlay_posix.c new file mode 100644 index 00000000..efe6396a --- /dev/null +++ b/runtime/src/overlay_posix.c @@ -0,0 +1,161 @@ +#include "overlay_posix.h" + +#include +#include + +static int hex_nibble(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static int parse_hex8(const char *text, uint32_t *value) { + uint32_t parsed = 0; + for (int i = 0; i < 8; i++) { + int nibble = hex_nibble(text[i]); + if (nibble < 0) return 0; + parsed = (parsed << 4) | (uint32_t)nibble; + } + if (value) *value = parsed; + return 1; +} + +int psx_overlay_cache_name_parse(const char *name, uint32_t *region_start, + uint32_t *content_crc) { + uint32_t addr = 0, crc = 0; + if (!name || strlen(name) != 21 || name[8] != '_' || + strcmp(name + 17, ".dll") != 0 || + !parse_hex8(name, &addr) || !parse_hex8(name + 9, &crc)) + return 0; + if (region_start) *region_start = addr; + if (content_crc) *content_crc = crc; + return 1; +} + +#ifndef _WIN32 + +#include +#include +#include + +int psx_overlay_posix_scan_cache_dir(const char *dir, + PsxOverlayCacheVisitor visitor, + void *opaque) { + DIR *dp = opendir(dir); + if (!dp) return 0; + + int visited = 0; + struct dirent *de; + while ((de = readdir(dp)) != NULL) { + PsxOverlayCacheFile file; + memset(&file, 0, sizeof(file)); + if (!psx_overlay_cache_name_parse(de->d_name, &file.region_start, + &file.content_crc)) + continue; + int n = snprintf(file.path, sizeof(file.path), "%s/%s", dir, de->d_name); + if (n < 0 || (size_t)n >= sizeof(file.path)) continue; + memcpy(file.name, de->d_name, sizeof(file.name) - 1); + file.name[sizeof(file.name) - 1] = '\0'; + + struct stat st; + if (stat(file.path, &st) != 0 || !S_ISREG(st.st_mode)) continue; + file.mtime = (uint64_t)st.st_mtime; + visited++; + if (visitor && visitor(&file, opaque)) break; + } + closedir(dp); + return visited; +} + +static int note_first_cache_file(const PsxOverlayCacheFile *file, void *opaque) { + (void)file; + *(int *)opaque = 1; + return 1; +} + +int psx_overlay_posix_find_other_cache_tag(const char *base_dir, + const char *expected_tag, + char *found_tag, + size_t found_tag_size) { + DIR *dp = opendir(base_dir); + if (!dp) return 0; + + int found = 0; + struct dirent *de; + while (!found && (de = readdir(dp)) != NULL) { + if (strncmp(de->d_name, "cg", 2) != 0 || + strcmp(de->d_name, expected_tag) == 0) + continue; + char candidate[768]; + int n = snprintf(candidate, sizeof(candidate), "%s/%s", base_dir, + de->d_name); + if (n < 0 || (size_t)n >= sizeof(candidate)) continue; + int has_cache_file = 0; + psx_overlay_posix_scan_cache_dir(candidate, note_first_cache_file, + &has_cache_file); + if (!has_cache_file) continue; + if (found_tag && found_tag_size) + snprintf(found_tag, found_tag_size, "%s", de->d_name); + found = 1; + } + closedir(dp); + return found; +} + +void *psx_overlay_posix_library_open(const char *path, char *error, + size_t error_size) { + dlerror(); + void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (!handle && error && error_size) { + const char *message = dlerror(); + snprintf(error, error_size, "%s", message ? message : "unknown dlopen error"); + } + return handle; +} + +void *psx_overlay_posix_library_symbol(void *handle, const char *name) { + return handle ? dlsym(handle, name) : NULL; +} + +void *psx_overlay_posix_library_entry(void *handle, uint32_t entry) { + char name[32]; + snprintf(name, sizeof(name), "func_%08X", entry); + return psx_overlay_posix_library_symbol(handle, name); +} + +void psx_overlay_posix_library_close(void *handle) { + if (handle) dlclose(handle); +} + +#else + +int psx_overlay_posix_scan_cache_dir(const char *dir, + PsxOverlayCacheVisitor visitor, + void *opaque) { + (void)dir; (void)visitor; (void)opaque; + return 0; +} +int psx_overlay_posix_find_other_cache_tag(const char *base_dir, + const char *expected_tag, + char *found_tag, + size_t found_tag_size) { + (void)base_dir; (void)expected_tag; (void)found_tag; (void)found_tag_size; + return 0; +} +void *psx_overlay_posix_library_open(const char *path, char *error, + size_t error_size) { + (void)path; (void)error; (void)error_size; + return NULL; +} +void *psx_overlay_posix_library_symbol(void *handle, const char *name) { + (void)handle; (void)name; + return NULL; +} +void *psx_overlay_posix_library_entry(void *handle, uint32_t entry) { + (void)handle; (void)entry; + return NULL; +} +void psx_overlay_posix_library_close(void *handle) { (void)handle; } + +#endif diff --git a/runtime/tests/overlay_posix_fixture.c b/runtime/tests/overlay_posix_fixture.c new file mode 100644 index 00000000..df718922 --- /dev/null +++ b/runtime/tests/overlay_posix_fixture.c @@ -0,0 +1,12 @@ +#include + +#if defined(__GNUC__) +#define EXPORT __attribute__((visibility("default"))) +#else +#define EXPORT +#endif + +EXPORT int overlay_abi(void) { return 0x12345678; } +EXPORT void overlay_init(const void *callbacks) { (void)callbacks; } +EXPORT void overlay_flush_cycles(void) {} +EXPORT uint32_t func_80012345(void) { return 0xC0DEF00Du; } diff --git a/runtime/tests/run_overlay_posix_test.sh b/runtime/tests/run_overlay_posix_test.sh new file mode 100644 index 00000000..1a4e7730 --- /dev/null +++ b/runtime/tests/run_overlay_posix_test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="$(cd "$(dirname "$0")/../.." && pwd)" +tmp="$(mktemp -d "${TMPDIR:-/tmp}/psxrecomp-posix-overlay.XXXXXX")" +trap 'rm -rf "$tmp"' EXIT + +cache="$tmp/cache" +base="$tmp/tags" +expected="cg12_11111111" +other="cg12_22222222" +mkdir -p "$cache/00000000_11111111.dll" "$base/$expected" "$base/$other" + +cc -std=c99 -Wall -Wextra -Werror -fPIC -shared \ + "$root/runtime/tests/overlay_posix_fixture.c" \ + -o "$cache/80010000_DEADBEEF.dll" +: > "$cache/00000000_00000000.dll" +: > "$cache/80010000_DEADBEG0.dll" +: > "$base/$other/80020000_12345678.dll" + +cc -std=c99 -Wall -Wextra -Werror \ + -I"$root/runtime/include" \ + "$root/runtime/tests/test_overlay_posix.c" \ + "$root/runtime/src/overlay_posix.c" \ + -ldl -o "$tmp/test_overlay_posix" + +"$tmp/test_overlay_posix" "$cache" "$base" "$expected" "$other" diff --git a/runtime/tests/test_overlay_posix.c b/runtime/tests/test_overlay_posix.c new file mode 100644 index 00000000..dde63008 --- /dev/null +++ b/runtime/tests/test_overlay_posix.c @@ -0,0 +1,79 @@ +#include "overlay_posix.h" + +#include +#include +#include +#include + +typedef struct ScanResult { + int count; + uint32_t addr; + uint32_t crc; +} ScanResult; + +static int record_cache_file(const PsxOverlayCacheFile *file, void *opaque) { + ScanResult *result = (ScanResult *)opaque; + result->count++; + if (strcmp(file->name, "80010000_DEADBEEF.dll") == 0) { + result->addr = file->region_start; + result->crc = file->content_crc; + } + return 0; +} + +static void check_name_parser(void) { + uint32_t addr = 1, crc = 1; + assert(psx_overlay_cache_name_parse("00000000_00000000.dll", &addr, &crc)); + assert(addr == 0 && crc == 0); + assert(psx_overlay_cache_name_parse("89abcdef_ABCDEF01.dll", &addr, &crc)); + assert(addr == 0x89ABCDEFu && crc == 0xABCDEF01u); + + assert(!psx_overlay_cache_name_parse("G0010000_DEADBEEF.dll", &addr, &crc)); + assert(!psx_overlay_cache_name_parse("80010000_DEADBEG0.dll", &addr, &crc)); + assert(!psx_overlay_cache_name_parse("80010000_DEADBEEF.so", &addr, &crc)); + assert(!psx_overlay_cache_name_parse("80010000-DEADBEEF.dll", &addr, &crc)); + assert(!psx_overlay_cache_name_parse("80010000_DEADBEEF.dll.extra", &addr, &crc)); +} + +int main(int argc, char **argv) { + assert(argc == 5); + check_name_parser(); + + ScanResult result = {0}; + assert(psx_overlay_posix_scan_cache_dir(argv[1], record_cache_file, &result) == 2); + assert(result.count == 2); + assert(result.addr == 0x80010000u); + assert(result.crc == 0xDEADBEEFu); + + char found[64] = {0}; + assert(psx_overlay_posix_find_other_cache_tag(argv[2], argv[3], found, + sizeof(found)) == 1); + assert(strcmp(found, argv[4]) == 0); + + char error[256] = {0}; + void *handle = psx_overlay_posix_library_open(argv[1], error, sizeof(error)); + assert(handle == NULL); /* A directory is not a loadable overlay library. */ + + char fixture[1024]; + snprintf(fixture, sizeof(fixture), "%s/80010000_DEADBEEF.dll", argv[1]); + handle = psx_overlay_posix_library_open(fixture, error, sizeof(error)); + assert(handle != NULL); + typedef int (*AbiFn)(void); + typedef uint32_t (*EntryFn)(void); + AbiFn abi = (AbiFn)psx_overlay_posix_library_symbol(handle, "overlay_abi"); + EntryFn entry = (EntryFn)psx_overlay_posix_library_entry(handle, 0x80012345u); + assert(abi && abi() == 0x12345678); + assert(entry && entry() == 0xC0DEF00Du); + assert(psx_overlay_posix_library_entry(handle, 0x80099999u) == NULL); + assert(psx_overlay_posix_library_symbol(handle, "func_80099999") == NULL); + psx_overlay_posix_library_close(handle); + + /* A close followed by a fresh open must remain usable; the production + * loader closes rejected/empty libraries and retains successful handles. */ + handle = psx_overlay_posix_library_open(fixture, error, sizeof(error)); + assert(handle != NULL); + psx_overlay_posix_library_close(handle); + + puts("overlay POSIX discovery tests passed"); + return 0; +}