mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
fix(mods): reapply executable patches after state load
This commit is contained in:
@@ -35,6 +35,10 @@ extern "C" {
|
||||
/* Called before a guest dispatch. Applies the complete main-EXE plan
|
||||
* transactionally on the first dispatch to the configured entry point. */
|
||||
void mod_runtime_on_dispatch(uint32_t target);
|
||||
/* A full-machine savestate restores guest RAM after the initial entry-point
|
||||
* application. Reapply the already-validated main-EXE plan so the current
|
||||
* enabled mod selection remains authoritative after the restore. */
|
||||
void mod_runtime_on_savestate_loaded(void);
|
||||
/* Invokes activation callbacks for the committed plan. Call after the final
|
||||
* launcher commit and before renderer/window initialization. */
|
||||
void mod_runtime_activate_plugins(void);
|
||||
|
||||
@@ -409,6 +409,7 @@ static void present_session_reset(void) {
|
||||
* longjmp). Clears present latches and forces the next vblank to show the
|
||||
* restored VRAM — including a blank if display was disabled in the snapshot. */
|
||||
extern "C" void psx_frontend_on_savestate_loaded(void) {
|
||||
mod_runtime_on_savestate_loaded();
|
||||
s_disabled_frame_presented = false;
|
||||
s_force_present_after_load = true;
|
||||
smooth_60_reset();
|
||||
|
||||
+90
-20
@@ -153,6 +153,66 @@ void set_error(const std::string& error) {
|
||||
state().error = error;
|
||||
}
|
||||
|
||||
void apply_main_write(const ModResolution::Write& write) {
|
||||
if (write.fields.empty()) {
|
||||
for (size_t i = 0; i < write.replacement.size(); ++i)
|
||||
psx_write_byte((uint32_t)write.location + (uint32_t)i,
|
||||
write.replacement[i]);
|
||||
dirty_ram_mark_executable_range(
|
||||
(uint32_t)write.location & 0x1FFFFFFFu,
|
||||
(uint32_t)write.replacement.size());
|
||||
return;
|
||||
}
|
||||
for (const ModResolution::Write::Field& field : write.fields) {
|
||||
for (size_t i = 0; i < field.replacement.size(); ++i)
|
||||
psx_write_byte(
|
||||
(uint32_t)write.location +
|
||||
(uint32_t)field.offset + (uint32_t)i,
|
||||
field.replacement[i]);
|
||||
dirty_ram_mark_executable_range(
|
||||
((uint32_t)write.location +
|
||||
(uint32_t)field.offset) & 0x1FFFFFFFu,
|
||||
(uint32_t)field.replacement.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool restored_main_matches_plan(const RuntimeMods& s, uint32_t& failed_at) {
|
||||
std::map<uint32_t, uint8_t> desired;
|
||||
for (const ModResolution::Write& write : s.plan.writes) {
|
||||
if (write.target != ModPatchTarget::MainExe) continue;
|
||||
if (write.fields.empty()) {
|
||||
for (size_t i = 0; i < write.replacement.size(); ++i)
|
||||
desired[(uint32_t)write.location + (uint32_t)i] =
|
||||
write.replacement[i];
|
||||
} else {
|
||||
for (const ModResolution::Write::Field& field : write.fields) {
|
||||
for (size_t i = 0; i < field.replacement.size(); ++i)
|
||||
desired[
|
||||
(uint32_t)write.location +
|
||||
(uint32_t)field.offset + (uint32_t)i] =
|
||||
field.replacement[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const ModResolution::Write& write : s.plan.writes) {
|
||||
if (write.target != ModPatchTarget::MainExe) continue;
|
||||
for (size_t i = 0; i < write.expected.size(); ++i) {
|
||||
const uint32_t address =
|
||||
(uint32_t)write.location + (uint32_t)i;
|
||||
const uint8_t observed = psx_read_byte(address);
|
||||
if (observed == write.expected[i]) continue;
|
||||
const auto replacement = desired.find(address);
|
||||
if (replacement != desired.end() &&
|
||||
observed == replacement->second)
|
||||
continue;
|
||||
failed_at = address;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sha256_file(const std::filesystem::path& path, std::string& out,
|
||||
std::string* error) {
|
||||
out.clear();
|
||||
@@ -1066,26 +1126,7 @@ extern "C" void mod_runtime_on_dispatch(uint32_t target) {
|
||||
}
|
||||
for (const ModResolution::Write& write : s.plan.writes) {
|
||||
if (write.target != ModPatchTarget::MainExe) continue;
|
||||
if (write.fields.empty()) {
|
||||
for (size_t i = 0; i < write.replacement.size(); ++i)
|
||||
psx_write_byte((uint32_t)write.location + (uint32_t)i,
|
||||
write.replacement[i]);
|
||||
dirty_ram_mark_executable_range(
|
||||
(uint32_t)write.location & 0x1FFFFFFFu,
|
||||
(uint32_t)write.replacement.size());
|
||||
} else {
|
||||
for (const ModResolution::Write::Field& field : write.fields) {
|
||||
for (size_t i = 0; i < field.replacement.size(); ++i)
|
||||
psx_write_byte(
|
||||
(uint32_t)write.location +
|
||||
(uint32_t)field.offset + (uint32_t)i,
|
||||
field.replacement[i]);
|
||||
dirty_ram_mark_executable_range(
|
||||
((uint32_t)write.location +
|
||||
(uint32_t)field.offset) & 0x1FFFFFFFu,
|
||||
(uint32_t)field.replacement.size());
|
||||
}
|
||||
}
|
||||
apply_main_write(write);
|
||||
}
|
||||
s.main_applied = true;
|
||||
if (!s.plan.writes.empty())
|
||||
@@ -1093,6 +1134,35 @@ extern "C" void mod_runtime_on_dispatch(uint32_t target) {
|
||||
s.plan.fingerprint.c_str());
|
||||
}
|
||||
|
||||
extern "C" void mod_runtime_on_savestate_loaded(void) {
|
||||
using namespace PSXRecompV4;
|
||||
RuntimeMods& s = state();
|
||||
if (!s.initialized || !s.plan.ok) return;
|
||||
|
||||
if (!s.main_applied) {
|
||||
uint32_t failed_at = 0;
|
||||
if (!restored_main_matches_plan(s, failed_at)) {
|
||||
std::fprintf(stderr,
|
||||
"psxrecomp: mod plan %s rejected after savestate restore at "
|
||||
"0x%08X (expected-byte guard failed)\n",
|
||||
s.plan.fingerprint.c_str(), (unsigned)failed_at);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool applied = false;
|
||||
for (const ModResolution::Write& write : s.plan.writes) {
|
||||
if (write.target != ModPatchTarget::MainExe) continue;
|
||||
apply_main_write(write);
|
||||
applied = true;
|
||||
}
|
||||
s.main_applied = true;
|
||||
if (applied)
|
||||
std::fprintf(stdout,
|
||||
"psxrecomp: reapplied mod plan %s after savestate restore\n",
|
||||
s.plan.fingerprint.c_str());
|
||||
}
|
||||
|
||||
extern "C" void mod_runtime_enable_disc_patches(void) {
|
||||
PSXRecompV4::state().disc_enabled = true;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,39 @@ extern "C" void psx_write_byte(uint32_t address, uint8_t value) {
|
||||
ram[address & 0x1fffffu] = value;
|
||||
}
|
||||
|
||||
extern "C" uint16_t psx_read_half(uint32_t address) {
|
||||
const uint32_t offset = address & 0x1fffffu;
|
||||
return (uint16_t)(ram[offset] | ((uint16_t)ram[offset + 1] << 8));
|
||||
}
|
||||
|
||||
extern "C" void psx_write_half(uint32_t address, uint16_t value) {
|
||||
const uint32_t offset = address & 0x1fffffu;
|
||||
ram[offset] = (uint8_t)value;
|
||||
ram[offset + 1] = (uint8_t)(value >> 8);
|
||||
}
|
||||
|
||||
extern "C" uint32_t psx_read_word(uint32_t address) {
|
||||
const uint32_t offset = address & 0x1fffffu;
|
||||
return (uint32_t)ram[offset] |
|
||||
((uint32_t)ram[offset + 1] << 8) |
|
||||
((uint32_t)ram[offset + 2] << 16) |
|
||||
((uint32_t)ram[offset + 3] << 24);
|
||||
}
|
||||
|
||||
extern "C" void psx_write_word(uint32_t address, uint32_t value) {
|
||||
const uint32_t offset = address & 0x1fffffu;
|
||||
ram[offset] = (uint8_t)value;
|
||||
ram[offset + 1] = (uint8_t)(value >> 8);
|
||||
ram[offset + 2] = (uint8_t)(value >> 16);
|
||||
ram[offset + 3] = (uint8_t)(value >> 24);
|
||||
}
|
||||
|
||||
extern "C" uint32_t psx_mod_memory_alloc(uint32_t, uint32_t) { return 0; }
|
||||
extern "C" uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t, uint32_t) {
|
||||
return 0;
|
||||
}
|
||||
extern "C" int psx_ws_x_margin(void) { return 0; }
|
||||
|
||||
extern "C" void dirty_ram_mark_executable_range(uint32_t, uint32_t) {}
|
||||
extern "C" int fntrace_is_game_started(void) { return 1; }
|
||||
|
||||
@@ -292,6 +325,21 @@ int main() {
|
||||
"adjacent sparse fields must compose while preserving guard-only "
|
||||
"bytes");
|
||||
|
||||
/* A full-machine savestate replaces main RAM after the entry-point plan
|
||||
* has already applied. Loading a stock checkpoint must not silently turn
|
||||
* the currently enabled main-EXE features back off. */
|
||||
ram[0x1000] = 1; ram[0x1001] = 2; ram[0x1002] = 3; ram[0x1003] = 4;
|
||||
ram[0x1100] = 0; ram[0x1101] = 0;
|
||||
ram[0x1102] = 1; ram[0x1103] = 0;
|
||||
ram[0x1200] = 2; ram[0x1201] = 0;
|
||||
ram[0x1202] = 1; ram[0x1203] = 0x32;
|
||||
mod_runtime_on_savestate_loaded();
|
||||
check(ram[0x1000] == 0xa1 && ram[0x1003] == 0xa4 &&
|
||||
ram[0x1100] == 42 && ram[0x1102] == 43 &&
|
||||
ram[0x1200] == 1 && ram[0x1201] == 0x42 &&
|
||||
ram[0x1202] == 0 && ram[0x1203] == 0x32,
|
||||
"savestate restore must reapply the complete enabled main plan");
|
||||
|
||||
std::array<uint8_t, 2352> sector{};
|
||||
sector[10] = 0xaa;
|
||||
mod_runtime_patch_disc_sector(2, 1, sector.data(), (uint32_t)sector.size());
|
||||
@@ -374,6 +422,26 @@ int main() {
|
||||
"a failed sparse guard-only byte must leave the complete main plan "
|
||||
"untouched");
|
||||
|
||||
/* Loading can be requested while the boot executable is still running,
|
||||
* before the configured game entry point has dispatched. The restored
|
||||
* checkpoint itself supplies the bytes used to validate and apply the
|
||||
* plan in that case. */
|
||||
check(PSXRecompV4::mod_runtime_initialize(
|
||||
root, "SLUS-RUNTIME", 0x80002000, {}, &error),
|
||||
error.c_str());
|
||||
check(PSXRecompV4::mod_runtime_commit(stock_path, &error), error.c_str());
|
||||
ram[0x1000] = 1; ram[0x1001] = 2; ram[0x1002] = 3; ram[0x1003] = 4;
|
||||
ram[0x1100] = 0; ram[0x1101] = 0;
|
||||
ram[0x1102] = 1; ram[0x1103] = 0;
|
||||
ram[0x1200] = 2; ram[0x1201] = 0;
|
||||
ram[0x1202] = 1; ram[0x1203] = 0x32;
|
||||
mod_runtime_on_savestate_loaded();
|
||||
check(ram[0x1000] == 0xa1 && ram[0x1003] == 0xa4 &&
|
||||
ram[0x1100] == 42 && ram[0x1102] == 43 &&
|
||||
ram[0x1200] == 1 && ram[0x1201] == 0x42 &&
|
||||
ram[0x1202] == 0 && ram[0x1203] == 0x32,
|
||||
"pre-entry savestate restore must validate and apply the main plan");
|
||||
|
||||
mod_runtime_enable_disc_patches();
|
||||
std::array<uint8_t, 2352> bad_sparse_disc{};
|
||||
bad_sparse_disc[15] = 2;
|
||||
|
||||
Reference in New Issue
Block a user