mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
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 <vanderveerdouglas@gmail.com>
This commit is contained in:
@@ -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 `<addr8>_<crc8>.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
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Parsed <addr8>_<crc8>.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
|
||||
@@ -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)
|
||||
|
||||
+149
-28
@@ -8,6 +8,7 @@
|
||||
#include "interrupts.h"
|
||||
#include "debug_server.h"
|
||||
#include "psx_cycles.h"
|
||||
#include "overlay_posix.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -18,7 +19,7 @@
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <dlfcn.h>
|
||||
# include <unistd.h>
|
||||
#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 <addr8>_<crc8>.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 <addr8>_<crc8>.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
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "overlay_posix.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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 <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#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; }
|
||||
@@ -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"
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "overlay_posix.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user