mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
SIO alias dispatch + instant disc speed + game miss collector
SIO handler alias (0x0CF0 → func_0000641C): Runtime capture showed BIOS installs lui+addiu+jalr to RAM 0x641C at 0x0CF0. New [[recompiler.bios_aliases]] TOML mechanism emits a one-liner wrapper and registers it in the dispatch table. Eliminates 30K dirty_ram blocks/session. Instant disc speed (disc_speed = "instant" in game.toml [runtime]): cdrom_set_speed(0) collapses all seek/read delays to 1 cycle. Correct INT sequence still fires; no artificial wait. "2x" and "4x" divisors available. Default remains authentic 1x. Tomba set to instant. Persistent game miss collector (tools/collect_game_misses.py): Appends newly-seen dirty_ram dispatch misses in the game text range to a file (TombaRecomp/seeds/dirty_ram_misses.txt) for later seeding. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,10 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
|
||||
rt.memcard_dir = fs::absolute(root / rel);
|
||||
rt.has_memcard_dir = true;
|
||||
}
|
||||
if (runtime.contains("disc_speed")) {
|
||||
rt.disc_speed = toml::find<std::string>(runtime, "disc_speed");
|
||||
rt.has_disc_speed = true;
|
||||
}
|
||||
return rt;
|
||||
}
|
||||
|
||||
@@ -204,6 +208,20 @@ BiosConfig load_bios_config(const fs::path& config_path_in) {
|
||||
}
|
||||
}
|
||||
|
||||
// [[recompiler.bios_aliases]] — fixed-target RAM trampolines
|
||||
std::vector<BiosAlias> bios_aliases;
|
||||
if (recomp.contains("bios_aliases")) {
|
||||
const auto& arr = recomp.at("bios_aliases").as_array();
|
||||
for (const auto& v : arr) {
|
||||
BiosAlias ba;
|
||||
ba.ram_addr = parse_hex(toml::find<std::string>(v, "ram_addr"),
|
||||
"bios_aliases.ram_addr");
|
||||
ba.target_key = parse_hex(toml::find<std::string>(v, "target_key"),
|
||||
"bios_aliases.target_key");
|
||||
bios_aliases.push_back(ba);
|
||||
}
|
||||
}
|
||||
|
||||
return BiosConfig{
|
||||
/*config_path*/ config_path,
|
||||
/*project_root*/ root,
|
||||
@@ -218,6 +236,7 @@ BiosConfig load_bios_config(const fs::path& config_path_in) {
|
||||
/*strict*/ strict,
|
||||
/*out_stem*/ out_stem,
|
||||
/*bios_vectors*/ std::move(bios_vectors),
|
||||
/*bios_aliases*/ std::move(bios_aliases),
|
||||
/*runtime*/ parse_runtime_block(cfg, root),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ struct RuntimeConfig {
|
||||
|
||||
bool has_memcard_dir = false;
|
||||
std::filesystem::path memcard_dir; // absolute path (resolved against project root)
|
||||
|
||||
// disc_speed: "1x" (default) | "2x" | "4x" | "instant"
|
||||
// Controls how quickly CD-ROM timing delays fire. "instant" collapses all
|
||||
// seek/read delays to 1 cycle — correct INT sequence, no artificial wait.
|
||||
bool has_disc_speed = false;
|
||||
std::string disc_speed; // raw string; main.cpp converts to divisor
|
||||
};
|
||||
|
||||
// One entry from [[recompiler.bios_vectors]].
|
||||
@@ -54,6 +60,15 @@ struct BiosVectorTable {
|
||||
uint32_t table_ram_addr;
|
||||
};
|
||||
|
||||
// One entry from [[recompiler.bios_aliases]].
|
||||
// A RAM address the BIOS installs a simple fixed-target trampoline at
|
||||
// (e.g. the SIO handler at 0x0CF0 which just jalrs to 0x641C). Emitted as
|
||||
// a one-liner wrapper in the dispatch table — no table lookup, no switch.
|
||||
struct BiosAlias {
|
||||
uint32_t ram_addr; // the installed stub address (e.g. 0x0CF0)
|
||||
uint32_t target_key; // normalized dispatch key of the target (e.g. 0x641C)
|
||||
};
|
||||
|
||||
struct BiosConfig {
|
||||
std::filesystem::path config_path; // the toml file itself
|
||||
std::filesystem::path project_root; // resolved via .gitignore/.git/CMakeLists.txt walk
|
||||
@@ -72,6 +87,7 @@ struct BiosConfig {
|
||||
bool strict; // currently always true
|
||||
std::string out_stem; // derived if not explicit
|
||||
std::vector<BiosVectorTable> bios_vectors; // optional vector dispatch tables
|
||||
std::vector<BiosAlias> bios_aliases; // optional fixed-target trampolines
|
||||
|
||||
// [runtime] block (optional)
|
||||
RuntimeConfig runtime;
|
||||
|
||||
@@ -908,7 +908,8 @@ void FullFunctionEmitter::emit_dispatch(
|
||||
const std::string& bios_sha256,
|
||||
const std::vector<uint8_t>& rom,
|
||||
uint32_t base_addr,
|
||||
const std::vector<BiosVectorTable>& bios_vectors)
|
||||
const std::vector<BiosVectorTable>& bios_vectors,
|
||||
const std::vector<BiosAlias>& bios_aliases)
|
||||
{
|
||||
out += "/* AUTO-GENERATED by psxrecomp-bios --emit-full. DO NOT EDIT.\n";
|
||||
out += " *\n";
|
||||
@@ -955,12 +956,31 @@ void FullFunctionEmitter::emit_dispatch(
|
||||
struct VecHandler { uint32_t ram_addr; std::string func_name; };
|
||||
std::vector<VecHandler> vec_handlers;
|
||||
|
||||
if (!bios_vectors.empty()) {
|
||||
if (!bios_vectors.empty() || !bios_aliases.empty()) {
|
||||
// psx_dispatch_call is defined later in this file; forward-declare it
|
||||
// so the vector handlers can call it for runtime-table fallbacks.
|
||||
out += "void psx_dispatch_call(CPUState* cpu, uint32_t addr, uint32_t return_addr);\n\n";
|
||||
}
|
||||
|
||||
// --- BIOS fixed-target aliases ---
|
||||
// Simple one-liner wrappers for trampolines that always redirect to a
|
||||
// single known function (e.g. SIO handler at 0x0CF0 → func_0000641C).
|
||||
for (const auto& ba : bios_aliases) {
|
||||
if (emitted_normalized.count(ba.target_key) == 0) {
|
||||
out += fmt::format("/* bios_alias 0x{:08X} -> 0x{:08X}: "
|
||||
"target not in dispatch table, skipped */\n",
|
||||
ba.ram_addr, ba.target_key);
|
||||
continue;
|
||||
}
|
||||
out += fmt::format("/* BIOS fixed alias: 0x{:08X} -> func_{:08X} */\n",
|
||||
ba.ram_addr, ba.target_key);
|
||||
out += fmt::format("static void bios_alias_{:08X}(CPUState* cpu) "
|
||||
"{{ func_{:08X}(cpu); }}\n\n",
|
||||
ba.ram_addr, ba.target_key);
|
||||
vec_handlers.push_back({ba.ram_addr,
|
||||
fmt::format("bios_alias_{:08X}", ba.ram_addr)});
|
||||
}
|
||||
|
||||
{
|
||||
const uint32_t base_phys = base_addr & 0x1FFFFFFFu;
|
||||
for (const auto& bvt : bios_vectors) {
|
||||
@@ -1148,7 +1168,8 @@ EmitStats FullFunctionEmitter::emit(
|
||||
const DiscoveryResult& dr,
|
||||
const std::string& bios_sha256,
|
||||
const std::string& out_dir,
|
||||
const std::vector<BiosVectorTable>& bios_vectors)
|
||||
const std::vector<BiosVectorTable>& bios_vectors,
|
||||
const std::vector<BiosAlias>& bios_aliases)
|
||||
{
|
||||
EmitStats stats;
|
||||
|
||||
@@ -1332,7 +1353,7 @@ EmitStats FullFunctionEmitter::emit(
|
||||
{
|
||||
std::string dispatch_c;
|
||||
emit_dispatch(dispatch_c, dr, emitted_normalized, unique_continuations,
|
||||
bios_sha256, rom, base_addr, bios_vectors);
|
||||
bios_sha256, rom, base_addr, bios_vectors, bios_aliases);
|
||||
std::string path = out_dir + "/SCPH1001_dispatch.c";
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
if (!f) throw std::runtime_error(fmt::format("cannot write {}", path));
|
||||
|
||||
@@ -63,7 +63,8 @@ public:
|
||||
const DiscoveryResult& dr,
|
||||
const std::string& bios_sha256,
|
||||
const std::string& out_dir,
|
||||
const std::vector<BiosVectorTable>& bios_vectors = {});
|
||||
const std::vector<BiosVectorTable>& bios_vectors = {},
|
||||
const std::vector<BiosAlias>& bios_aliases = {});
|
||||
|
||||
private:
|
||||
// Emit a single function's C code to the output stream.
|
||||
@@ -97,7 +98,8 @@ private:
|
||||
const std::string& bios_sha256,
|
||||
const std::vector<uint8_t>& rom,
|
||||
uint32_t base_addr,
|
||||
const std::vector<BiosVectorTable>& bios_vectors);
|
||||
const std::vector<BiosVectorTable>& bios_vectors,
|
||||
const std::vector<BiosAlias>& bios_aliases);
|
||||
|
||||
static uint32_t normalize_address(uint32_t addr);
|
||||
static uint32_t read_u32_le(const std::vector<uint8_t>& rom, uint32_t offset);
|
||||
|
||||
@@ -709,7 +709,8 @@ int run_boot_slice(const fs::path& bios_path, const fs::path& out_dir,
|
||||
|
||||
int run_emit_full(const fs::path& bios_path, const fs::path& out_dir,
|
||||
const fs::path& seed_path,
|
||||
const std::vector<PSXRecompV4::BiosVectorTable>& bios_vectors = {}) {
|
||||
const std::vector<PSXRecompV4::BiosVectorTable>& bios_vectors = {},
|
||||
const std::vector<PSXRecompV4::BiosAlias>& bios_aliases = {}) {
|
||||
// 1. Load + validate BIOS file.
|
||||
const auto rom = load_file_strict(bios_path, kBiosSize);
|
||||
const std::string sha = sha256_hex(rom);
|
||||
@@ -735,7 +736,7 @@ int run_emit_full(const fs::path& bios_path, const fs::path& out_dir,
|
||||
// 4. Emit full C.
|
||||
const auto stats = PSXRecompV4::FullFunctionEmitter::emit(
|
||||
rom, kBiosBase, kBiosBase + static_cast<uint32_t>(kBiosSize) - 1,
|
||||
dr, sha, out_dir.string(), bios_vectors);
|
||||
dr, sha, out_dir.string(), bios_vectors, bios_aliases);
|
||||
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp-bios: EMIT OK emitted=%u skipped=%u instructions=%u "
|
||||
@@ -835,13 +836,13 @@ int main(int argc, char** argv) {
|
||||
cfg.out_dir.string().c_str(),
|
||||
cfg.out_stem.c_str());
|
||||
return run_emit_full(cfg.rom_path, cfg.out_dir, cfg.seeds_path,
|
||||
cfg.bios_vectors);
|
||||
cfg.bios_vectors, cfg.bios_aliases);
|
||||
}
|
||||
if (a == "--config=" || a.rfind("--config=", 0) == 0) {
|
||||
const fs::path config_path = a.substr(std::string("--config=").size());
|
||||
const auto cfg = PSXRecompV4::load_bios_config(config_path);
|
||||
return run_emit_full(cfg.rom_path, cfg.out_dir, cfg.seeds_path,
|
||||
cfg.bios_vectors);
|
||||
cfg.bios_vectors, cfg.bios_aliases);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@ extern "C" {
|
||||
|
||||
void cdrom_init(const char* cue_path);
|
||||
|
||||
/* Set disc speed multiplier. divisor=0 → instant (all delays collapse to
|
||||
* 1 cycle, correct INT sequence fires as fast as host can process).
|
||||
* divisor=1 → authentic 1x. divisor=2 → 2x. Call before first cdrom_tick. */
|
||||
void cdrom_set_speed(int divisor);
|
||||
|
||||
/* MMIO read/write (0x1F801800-0x1F801803) */
|
||||
uint32_t cdrom_read(uint32_t addr);
|
||||
void cdrom_write(uint32_t addr, uint32_t value);
|
||||
|
||||
+21
-6
@@ -86,10 +86,25 @@ static uint8_t xa_stream_file;
|
||||
static uint8_t xa_stream_channel;
|
||||
static int xa_stream_active;
|
||||
|
||||
/* 0 = instant (every delay collapses to 1 cycle), >0 = divisor applied to
|
||||
* all timing constants. Set via cdrom_set_speed() from main before first use. */
|
||||
static int g_disc_speed_divisor = 1;
|
||||
|
||||
void cdrom_set_speed(int divisor) {
|
||||
g_disc_speed_divisor = divisor;
|
||||
}
|
||||
|
||||
static int apply_speed(int delay) {
|
||||
if (g_disc_speed_divisor == 0) return 1;
|
||||
int d = delay / g_disc_speed_divisor;
|
||||
return d < 1 ? 1 : d;
|
||||
}
|
||||
|
||||
static int sector_delay_cycles(void) {
|
||||
/* PS1 CPU is 33.8688 MHz. CD-ROM sectors arrive at 75 Hz in 1x
|
||||
* mode, or twice that rate when SetMode bit 7 enables double speed. */
|
||||
return (mode_reg & 0x80) ? 225792 : 451584;
|
||||
int base = (mode_reg & 0x80) ? 225792 : 451584;
|
||||
return apply_speed(base);
|
||||
}
|
||||
|
||||
/* Pending command */
|
||||
@@ -453,7 +468,7 @@ static void exec_command(uint8_t cmd) {
|
||||
set_irq(CDIRQ_ACK);
|
||||
pending.cmd = 0x09;
|
||||
pending.pending = 1;
|
||||
pending.delay = 10000;
|
||||
pending.delay = apply_speed(10000);
|
||||
pending.phase = 1;
|
||||
break;
|
||||
|
||||
@@ -466,7 +481,7 @@ static void exec_command(uint8_t cmd) {
|
||||
set_irq(CDIRQ_ACK);
|
||||
pending.cmd = 0x0A;
|
||||
pending.pending = 1;
|
||||
pending.delay = 50000;
|
||||
pending.delay = apply_speed(50000);
|
||||
pending.phase = 1;
|
||||
break;
|
||||
|
||||
@@ -517,7 +532,7 @@ static void exec_command(uint8_t cmd) {
|
||||
set_irq(CDIRQ_ACK);
|
||||
pending.cmd = 0x15;
|
||||
pending.pending = 1;
|
||||
pending.delay = 20000;
|
||||
pending.delay = apply_speed(20000);
|
||||
pending.phase = 1;
|
||||
break;
|
||||
|
||||
@@ -529,7 +544,7 @@ static void exec_command(uint8_t cmd) {
|
||||
set_irq(CDIRQ_ACK);
|
||||
pending.cmd = 0x1A;
|
||||
pending.pending = 1;
|
||||
pending.delay = 30000;
|
||||
pending.delay = apply_speed(30000);
|
||||
pending.phase = 1;
|
||||
break;
|
||||
|
||||
@@ -549,7 +564,7 @@ static void exec_command(uint8_t cmd) {
|
||||
set_irq(CDIRQ_ACK);
|
||||
pending.cmd = 0x1E;
|
||||
pending.pending = 1;
|
||||
pending.delay = 100000;
|
||||
pending.delay = apply_speed(100000);
|
||||
pending.phase = 1;
|
||||
break;
|
||||
|
||||
|
||||
@@ -1072,6 +1072,7 @@ int main(int argc, char** argv) {
|
||||
uint16_t debug_port = (uint16_t)DEFAULT_DEBUG_PORT;
|
||||
std::string game_name;
|
||||
std::string game_id;
|
||||
std::string disc_speed; /* "1x" | "2x" | "4x" | "instant" */
|
||||
|
||||
if (game_config_path) {
|
||||
try {
|
||||
@@ -1082,6 +1083,7 @@ int main(int argc, char** argv) {
|
||||
if (gc.runtime.has_memcard_dir) memcard_dir = gc.runtime.memcard_dir;
|
||||
if (gc.runtime.has_window_title) window_title = gc.runtime.window_title;
|
||||
if (gc.runtime.has_debug_port) debug_port = gc.runtime.debug_port;
|
||||
if (gc.runtime.has_disc_speed) disc_speed = gc.runtime.disc_speed;
|
||||
std::fprintf(stdout, "psxrecomp: loaded game config %s (%s, %s)\n",
|
||||
game_config_path, game_name.c_str(), game_id.c_str());
|
||||
} catch (const std::exception& ex) {
|
||||
@@ -1123,6 +1125,16 @@ int main(int argc, char** argv) {
|
||||
sio_connect_pad(0); /* Controller on port 1 */
|
||||
spu_init();
|
||||
cdrom_init(disc_path_str.empty() ? NULL : disc_path_str.c_str());
|
||||
{
|
||||
int divisor = 1; /* default: authentic 1x timing */
|
||||
if (disc_speed == "instant") divisor = 0;
|
||||
else if (disc_speed == "4x") divisor = 4;
|
||||
else if (disc_speed == "2x") divisor = 2;
|
||||
if (divisor != 1)
|
||||
std::fprintf(stdout, "psxrecomp: disc_speed=%s (divisor=%d)\n",
|
||||
disc_speed.c_str(), divisor);
|
||||
cdrom_set_speed(divisor);
|
||||
}
|
||||
memcard_init(memcard_dir_str.c_str());
|
||||
std::atexit(memcard_flush_all);
|
||||
#ifndef PSX_NO_DEBUG_TOOLS
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Collect dirty_ram dispatch misses in the game text range and append
|
||||
new unique addresses to a persistent log file. Run this during gameplay
|
||||
to build up the seed list over time.
|
||||
|
||||
Usage: python3 collect_game_misses.py <port> <game_text_lo> <game_text_hi> <output_file>
|
||||
Example (Tomba): python3 collect_game_misses.py 4470 0x10000 0x98000 seeds/dirty_ram_misses.txt
|
||||
"""
|
||||
import socket, json, sys, os
|
||||
|
||||
port = int(sys.argv[1], 0) if len(sys.argv) > 1 else 4470
|
||||
lo = int(sys.argv[2], 0) if len(sys.argv) > 2 else 0x10000
|
||||
hi = int(sys.argv[3], 0) if len(sys.argv) > 3 else 0x98000
|
||||
out_file = sys.argv[4] if len(sys.argv) > 4 else 'dirty_ram_misses.txt'
|
||||
|
||||
def send(cmd, **kw):
|
||||
s = socket.create_connection(('127.0.0.1', port), timeout=5)
|
||||
s.sendall(json.dumps({'id':1,'cmd':cmd,**kw}).encode() + b'\n')
|
||||
d = b''
|
||||
while True:
|
||||
c = s.recv(65536)
|
||||
if not c: break
|
||||
d += c
|
||||
try: json.loads(d.decode()); break
|
||||
except: pass
|
||||
s.close()
|
||||
return json.loads(d.decode())
|
||||
|
||||
# Load existing known addresses
|
||||
known = set()
|
||||
if os.path.exists(out_file):
|
||||
with open(out_file) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
try:
|
||||
known.add(int(line.split()[0], 0))
|
||||
except:
|
||||
pass
|
||||
|
||||
r = send('dirty_ram_stats')
|
||||
if not r.get('ok'):
|
||||
print('error:', r); sys.exit(1)
|
||||
|
||||
new_entries = []
|
||||
for entry in r.get('per_pc', []):
|
||||
pc = int(entry['pc'], 16)
|
||||
if lo <= pc < hi:
|
||||
vaddr = 0x80000000 | pc
|
||||
if vaddr not in known:
|
||||
new_entries.append((vaddr, entry['hits'], entry['insns']))
|
||||
known.add(vaddr)
|
||||
|
||||
if new_entries:
|
||||
with open(out_file, 'a') as f:
|
||||
for vaddr, hits, insns in sorted(new_entries, key=lambda x: -x[1]):
|
||||
f.write(f"0x{vaddr:08X} # dirty_ram miss: {hits} hits, {insns} insns\n")
|
||||
print(f"Appended {len(new_entries)} new addresses to {out_file}")
|
||||
for vaddr, hits, insns in sorted(new_entries, key=lambda x: -x[1]):
|
||||
print(f" 0x{vaddr:08X} ({hits} hits, {insns} insns)")
|
||||
else:
|
||||
print(f"No new addresses (already have {len(known)} in {out_file})")
|
||||
Reference in New Issue
Block a user