mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
227 lines
9.0 KiB
C++
227 lines
9.0 KiB
C++
/* The capture writer records PC evidence for the dirty-page run only, never
|
|
* for the trailing delay-slot guard word it appends (bead beads-eio.3.190).
|
|
*
|
|
* write_json_window grows a run by one guard word so a branch at the run's
|
|
* last word has its delay slot. That word is the first word of the NEXT page.
|
|
* The writer used to walk phys..phys+size for executed_pcs /
|
|
* dispatch_entry_pcs / seeds AFTER adding the guard, so a PC recorded in the
|
|
* next page's first word was attributed to this run. compile_overlays then
|
|
* requested it as a fragment and the recompiler (whose analysis ends before
|
|
* the guard word) returned an empty manifest: Ace Combat 3 `no-func-ids` at
|
|
* 0x800D1000 / 0x800EA000.
|
|
*
|
|
* Two shapes, both through the real writer (overlay_capture_write_json):
|
|
* A. A dispatch-only PC in the guard word of an ordinary run.
|
|
* B. Two executed runs on either side of the kernel/boot capture-window
|
|
* boundary (0x10000). The kernel run's guard word is the boot run's
|
|
* first word, executed: it must be listed by the boot run and not by the
|
|
* kernel run. */
|
|
#include "code_provider.h"
|
|
#include "dirty_ram_interp.h"
|
|
#include "overlay_capture.h"
|
|
|
|
#include "psx_sdl.h"
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <string>
|
|
|
|
extern "C" {
|
|
uint32_t g_dirty_ram_exec_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS]{};
|
|
uint32_t g_dirty_ram_dispatch_pc_bitmap[DIRTY_RAM_EXEC_BITMAP_WORDS]{};
|
|
uint32_t g_dirty_ram_dispatch_seg_bitmap[PSX_CODE_SEGMENT_COUNT][DIRTY_RAM_EXEC_BITMAP_WORDS]{};
|
|
uint32_t g_psx_ram_size = 2u * 1024u * 1024u;
|
|
uint32_t g_psx_ram_mask = 2u * 1024u * 1024u - 1u;
|
|
uint32_t g_dirty_ram_exec_page_bitmap[DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS]{};
|
|
uint64_t g_dirty_ram_insns_run = 0;
|
|
uint64_t g_dirty_window_dispatches = 0;
|
|
uint64_t s_frame_count = 0;
|
|
uint32_t g_overlay_region_floor = 0x00010000u;
|
|
}
|
|
|
|
namespace {
|
|
|
|
uint8_t g_ram[2u * 1024u * 1024u]{};
|
|
uint32_t g_dirty_pages[DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS]{};
|
|
|
|
int provider_available() { return 0; }
|
|
int provider_request() { return 0; }
|
|
int provider_busy() { return 0; }
|
|
|
|
const CodeProvider kProvider = {
|
|
"capture-guard-word-test", provider_available, provider_request,
|
|
provider_busy, nullptr,
|
|
};
|
|
|
|
void set_bit(uint32_t *bitmap, uint32_t phys) {
|
|
const uint32_t word = phys >> 2;
|
|
bitmap[word >> 5] |= 1u << (word & 31u);
|
|
}
|
|
|
|
void set_exec(uint32_t phys) {
|
|
set_bit(g_dirty_ram_exec_pc_bitmap, phys);
|
|
set_bit(g_dirty_ram_dispatch_pc_bitmap, phys);
|
|
for (int seg = 0; seg < PSX_CODE_SEGMENT_COUNT; ++seg)
|
|
set_bit(g_dirty_ram_dispatch_seg_bitmap[seg], phys);
|
|
const uint32_t page = phys >> 12;
|
|
g_dirty_ram_exec_page_bitmap[page >> 5] |= 1u << (page & 31u);
|
|
g_dirty_pages[page >> 5] |= 1u << (page & 31u);
|
|
}
|
|
|
|
std::string read_all(const std::filesystem::path &path) {
|
|
std::ifstream input(path, std::ios::binary);
|
|
return std::string(std::istreambuf_iterator<char>(input),
|
|
std::istreambuf_iterator<char>());
|
|
}
|
|
|
|
/* The JSON object of the region whose load_addr is `virt`, or "". */
|
|
std::string region(const std::string &json, uint32_t virt) {
|
|
char key[64];
|
|
std::snprintf(key, sizeof(key), "\"load_addr\": \"0x%08X\"", virt);
|
|
const size_t at = json.find(key);
|
|
if (at == std::string::npos) return std::string();
|
|
const size_t open = json.rfind('{', at);
|
|
size_t close = open;
|
|
unsigned depth = 0;
|
|
bool quoted = false, escaped = false;
|
|
for (; close < json.size(); ++close) {
|
|
const char c = json[close];
|
|
if (quoted) {
|
|
if (escaped) escaped = false;
|
|
else if (c == '\\') escaped = true;
|
|
else if (c == '"') quoted = false;
|
|
} else if (c == '"') quoted = true;
|
|
else if (c == '{') ++depth;
|
|
else if (c == '}' && --depth == 0) break;
|
|
}
|
|
return json.substr(open, close - open + 1);
|
|
}
|
|
|
|
/* The array text of `field` inside one region object. */
|
|
std::string field(const std::string &obj, const char *name) {
|
|
const std::string key = std::string("\"") + name + "\": [";
|
|
const size_t at = obj.find(key);
|
|
if (at == std::string::npos) return std::string();
|
|
const size_t start = at + key.size();
|
|
return obj.substr(start, obj.find(']', start) - start);
|
|
}
|
|
|
|
bool lists(const std::string &obj, const char *name, uint32_t virt) {
|
|
char pc[16];
|
|
std::snprintf(pc, sizeof(pc), "\"0x%08X\"", virt);
|
|
return field(obj, name).find(pc) != std::string::npos;
|
|
}
|
|
|
|
int failures = 0;
|
|
|
|
void expect(bool ok, const char *what) {
|
|
if (!ok) {
|
|
std::fprintf(stderr, "FAIL: %s\n", what);
|
|
++failures;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" uint8_t *memory_get_ram_ptr(void) { return g_ram; }
|
|
extern "C" uint32_t dirty_ram_get_bitmap_word_count(void) {
|
|
return DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS;
|
|
}
|
|
extern "C" uint32_t dirty_ram_get_bitmap_word(uint32_t index) {
|
|
return index < DIRTY_RAM_EXEC_PAGE_BITMAP_WORDS ? g_dirty_pages[index] : 0u;
|
|
}
|
|
extern "C" int cdrom_load_in_progress(void) { return 0; }
|
|
extern "C" int fntrace_is_game_started(void) { return 1; }
|
|
extern "C" void overlay_loader_check_cache(uint32_t, uint32_t,
|
|
const uint8_t *) {}
|
|
extern "C" int overlay_loader_registered_count(void) { return 0; }
|
|
extern "C" const CodeProvider *code_provider_active(void) { return &kProvider; }
|
|
extern "C" uint32_t crc32_compute(const uint8_t *data, size_t size) {
|
|
uint32_t hash = 2166136261u;
|
|
for (size_t i = 0; i < size; ++i) hash = (hash ^ data[i]) * 16777619u;
|
|
return hash ? hash : 1u;
|
|
}
|
|
|
|
int main() {
|
|
if (psx_sdl_init(0) != 0) {
|
|
std::fprintf(stderr, "FAIL: SDL_Init: %s\n", SDL_GetError());
|
|
return 1;
|
|
}
|
|
const auto root = std::filesystem::temp_directory_path() /
|
|
("psxrecomp-capture-guard-" + std::to_string(
|
|
static_cast<unsigned long long>(SDL_GetPerformanceCounter())));
|
|
std::error_code ignored;
|
|
std::filesystem::remove_all(root, ignored);
|
|
std::filesystem::create_directories(root);
|
|
const auto capture = root / "overlay_captures.json";
|
|
overlay_capture_set_path(capture.string().c_str());
|
|
overlay_capture_set_enabled(1);
|
|
/* The writer activates on the first post-handoff DMA. */
|
|
overlay_capture_on_dma(0x30000u, 4u, &g_ram[0x30000]);
|
|
|
|
/* A: run [0x30000, 0x31000) + guard word 0x31000, dispatch-only there. */
|
|
set_exec(0x30000u);
|
|
set_exec(0x30FFCu);
|
|
set_bit(g_dirty_ram_dispatch_pc_bitmap, 0x31000u);
|
|
for (int seg = 0; seg < PSX_CODE_SEGMENT_COUNT; ++seg)
|
|
set_bit(g_dirty_ram_dispatch_seg_bitmap[seg], 0x31000u);
|
|
/* B: kernel run [0xF000, 0x10000) + guard 0x10000; boot run
|
|
* [0x10000, 0x11000) whose first word is executed. */
|
|
set_exec(0x0F000u);
|
|
set_exec(0x0FFFCu);
|
|
set_exec(0x10000u);
|
|
set_exec(0x10FFCu);
|
|
|
|
overlay_capture_write_json();
|
|
overlay_capture_wait_pending();
|
|
const std::string json = read_all(capture);
|
|
|
|
const std::string a = region(json, 0x80030000u);
|
|
expect(!a.empty(), "A: region 0x80030000 written");
|
|
expect(a.find("\"size\": 4100") != std::string::npos,
|
|
"A: run still carries its guard word");
|
|
expect(a.find("\"guard_bytes\": 4") != std::string::npos,
|
|
"A: guard word declared");
|
|
expect(lists(a, "dispatch_entry_pcs", 0x80030FFCu),
|
|
"A: last word of the run is listed");
|
|
expect(!lists(a, "dispatch_entry_pcs", 0x80031000u),
|
|
"A: guard-word PC not a dispatch entry of the run");
|
|
expect(!lists(a, "seeds", 0x80031000u),
|
|
"A: guard-word PC not a seed of the run");
|
|
expect(region(json, 0x80031000u).empty(),
|
|
"A: a dispatch-only guard word does not make a region");
|
|
|
|
const std::string kernel = region(json, 0x8000F000u);
|
|
const std::string boot = region(json, 0x80010000u);
|
|
expect(!kernel.empty() && !boot.empty(), "B: both window runs written");
|
|
expect(kernel.find("\"guard_bytes\": 4") != std::string::npos,
|
|
"B: kernel run carries the cross-window guard word");
|
|
expect(lists(kernel, "executed_pcs", 0x8000FFFCu),
|
|
"B: kernel run lists its last word");
|
|
expect(!lists(kernel, "executed_pcs", 0x80010000u),
|
|
"B: kernel run does not list the boot run's first word");
|
|
expect(!lists(kernel, "dispatch_entry_pcs", 0x80010000u),
|
|
"B: nor as a dispatch entry");
|
|
expect(lists(boot, "executed_pcs", 0x80010000u) &&
|
|
lists(boot, "dispatch_entry_pcs", 0x80010000u),
|
|
"B: the boot run still owns its first word");
|
|
|
|
const size_t segment_map = kernel.find("\"dispatch_entry_segments\"");
|
|
expect(segment_map != std::string::npos, "B: segment evidence exists");
|
|
expect(kernel.substr(segment_map).find("0x00010000") == std::string::npos &&
|
|
kernel.substr(segment_map).find("0x80010000") == std::string::npos &&
|
|
kernel.substr(segment_map).find("0xA0010000") == std::string::npos,
|
|
"B: no segment claims the next run guard word");
|
|
|
|
overlay_capture_wait_pending();
|
|
std::filesystem::remove_all(root, ignored);
|
|
SDL_Quit();
|
|
if (failures) return 1;
|
|
std::puts("PASS: capture writer records no PC in a run's guard word");
|
|
return 0;
|
|
}
|