Make host disc reads see the effective modded disc

Apply committed sector mods during plugin activation, resolve paths through
patched directory records, and bound file size by CD capacity.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-10-04 18:53:09 -07:00
parent 167babab9f
commit 4e8652fe2d
2 changed files with 200 additions and 26 deletions
+108 -26
View File
@@ -1447,6 +1447,86 @@ extern "C" void mod_runtime_enable_disc_patches(void) {
PSXRecompV4::state().disc_enabled = true;
}
static void patch_committed_disc_sector(uint32_t lba, int raw_sector,
uint8_t* bytes, uint32_t size);
/* The largest file a CD can hold (80-minute disc, 360,000 sectors). Mods may
* grow an archive past its original extent, e.g. into a padding file. */
static constexpr uint32_t kMaxDiscFileBytes = 360000u * 2048u;
/* One 2048-byte user-data sector of the effective disc: the committed plan's
* raw and user-data writes/overlays applied. Form 2 (XA) sectors are refused. */
static bool read_effective_user_sector(PS1::ISOReader& reader, uint32_t lba,
uint8_t* sector) {
uint8_t raw[2352];
if (reader.ReadRawSector(lba, raw)) {
if (raw[15] != 1 && (raw[15] != 2 || (raw[18] & 0x20u))) return false;
patch_committed_disc_sector(lba, 1, raw, sizeof raw);
std::memcpy(sector, raw + (raw[15] == 1 ? 16 : 24), 2048);
if (raw[15] == 1) patch_committed_disc_sector(lba, 0, sector, 2048);
} else {
if (!reader.ReadSector(lba, sector)) return false;
patch_committed_disc_sector(lba, 0, sector, 2048);
}
return !PSXRecompV4::state().disc_guard_failed;
}
/* ISO 9660 path lookup through the effective directory records. Mods may
* relocate or grow a file by patching its directory record (e.g. an archive
* extended into the following padding file); the emulated drive serves those
* patched records, so host reads must resolve paths through them too. Names
* compare case-insensitively without the ";1" version suffix. */
static bool find_effective_file(PS1::ISOReader& reader, const std::string& path,
uint32_t& lba, uint32_t& bytes, bool& directory) {
uint8_t sector[2048];
auto le32 = [](const uint8_t* p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24);
};
if (!read_effective_user_sector(reader, 16, sector) || sector[0] != 1 ||
std::memcmp(sector + 1, "CD001", 5) != 0) return false;
lba = le32(sector + 156 + 2);
bytes = le32(sector + 156 + 10);
directory = true;
size_t at = 0;
while (at < path.size()) {
size_t next = path.find_first_of("/\\", at);
if (next == std::string::npos) next = path.size();
std::string want = path.substr(at, next - at);
at = next + 1;
if (want.empty()) continue;
if (!directory) return false;
std::transform(want.begin(), want.end(), want.begin(),
[](unsigned char c) { return (char)std::toupper(c); });
bool found = false;
const uint32_t sectors = (bytes + 2047u) / 2048u;
for (uint32_t i = 0; i < sectors && !found; ++i) {
if (!read_effective_user_sector(reader, lba + i, sector)) return false;
for (uint32_t p = 0; p < 2048u && sector[p] && !found;) {
const uint32_t length = sector[p];
if (length < 34 || p + length > 2048u ||
33u + sector[p + 32] > length) return false;
std::string name(reinterpret_cast<const char*>(sector + p + 33),
sector[p + 32]);
name = name.substr(0, name.find(';'));
std::transform(name.begin(), name.end(), name.begin(),
[](unsigned char c) { return (char)std::toupper(c); });
if (sector[p + 32] == 1 && (sector[p + 33] == 0 || sector[p + 33] == 1))
name.clear();
if (!name.empty() && name == want) {
lba = le32(sector + p + 2);
bytes = le32(sector + p + 10);
directory = (sector[p + 25] & 2u) != 0;
found = true;
}
p += length;
}
}
if (!found) return false;
}
return true;
}
extern "C" int psx_mod_read_disc_file(const char* path, void* buffer,
uint32_t capacity, uint32_t* size) {
using namespace PSXRecompV4;
@@ -1457,29 +1537,21 @@ extern "C" int psx_mod_read_disc_file(const char* path, void* buffer,
const auto& mount = s.effective_disc_path.empty() ? s.disc_path : s.effective_disc_path;
if (mount.empty()) return 0;
PS1::ISOReader reader;
PS1::ISOFileEntry entry;
if (!reader.Open(mount.string()) || !reader.FindFile(path, entry) ||
entry.is_directory || !entry.size || entry.size > 64u * 1024u * 1024u)
uint32_t lba = 0, bytes = 0;
bool directory = false;
if (!reader.Open(mount.string()) ||
!find_effective_file(reader, path, lba, bytes, directory) ||
directory || !bytes || bytes > kMaxDiscFileBytes)
return 0;
if (!buffer) { *size = entry.size; return 1; }
if (capacity < entry.size) return 0;
uint8_t sector[2048], raw[2352];
for (uint32_t offset = 0; offset < entry.size; offset += 2048u) {
const uint32_t lba = entry.lba + offset / 2048u;
if (reader.ReadRawSector(lba, raw)) {
if (raw[15] != 1 && (raw[15] != 2 || (raw[18] & 0x20u))) return 0;
mod_runtime_patch_disc_sector(lba, 1, raw, sizeof raw);
std::memcpy(sector, raw + (raw[15] == 1 ? 16 : 24), sizeof sector);
if (raw[15] == 1) mod_runtime_patch_disc_sector(lba, 0, sector, sizeof sector);
} else {
if (!reader.ReadSector(lba, sector)) return 0;
mod_runtime_patch_disc_sector(lba, 0, sector, sizeof sector);
}
if (state().disc_guard_failed) return 0;
const uint32_t count = std::min(2048u, entry.size - offset);
if (!buffer) { *size = bytes; return 1; }
if (capacity < bytes) return 0;
uint8_t sector[2048];
for (uint32_t offset = 0; offset < bytes; offset += 2048u) {
if (!read_effective_user_sector(reader, lba + offset / 2048u, sector)) return 0;
const uint32_t count = std::min(2048u, bytes - offset);
std::memcpy(static_cast<uint8_t*>(buffer) + offset, sector, count);
}
*size = entry.size;
*size = bytes;
return 1;
} catch (...) { return 0; }
}
@@ -1783,12 +1855,16 @@ extern "C" int psx_mod_function_entry_active(void) {
return PSXRecompV4::function_entry_depth != 0;
}
extern "C" void mod_runtime_patch_disc_sector(uint32_t lba, int raw_sector,
uint8_t* bytes, uint32_t size) {
/* Apply the committed plan's disc writes and overlays to one sector. The
* emulated drive reaches this only after mod_runtime_enable_disc_patches()
* (reference reads before then must see the original image); host-side
* preparation through psx_mod_read_disc_file() always sees the effective disc,
* including during plugin activation, which runs before the drive is enabled. */
static void patch_committed_disc_sector(uint32_t lba, int raw_sector,
uint8_t* bytes, uint32_t size) {
using namespace PSXRecompV4;
RuntimeMods& s = state();
if (!s.initialized || !s.disc_enabled || s.disc_guard_failed ||
!bytes || size == 0) return;
if (!s.initialized || s.disc_guard_failed || !bytes || size == 0) return;
/* Raw Mode2 Form1 reads are also the source of the 2048-byte logical
* stream consumed by the emulated CD controller. Apply raw claims to the
* complete sector, then user-data claims to its payload window. Form2/XA
@@ -1807,7 +1883,7 @@ extern "C" void mod_runtime_patch_disc_sector(uint32_t lba, int raw_sector,
const auto overlay_sector = overlay_index.find(lba);
if (sector == index.end() && overlay_sector == overlay_index.end()) {
if (has_mode2_form1_user_data)
mod_runtime_patch_disc_sector(lba, 0, bytes + 24, 2048);
patch_committed_disc_sector(lba, 0, bytes + 24, 2048);
return;
}
if (sector != index.end()) {
@@ -1862,5 +1938,11 @@ extern "C" void mod_runtime_patch_disc_sector(uint32_t lba, int raw_sector,
}
}
if (has_mode2_form1_user_data && !s.disc_guard_failed)
mod_runtime_patch_disc_sector(lba, 0, bytes + 24, 2048);
patch_committed_disc_sector(lba, 0, bytes + 24, 2048);
}
extern "C" void mod_runtime_patch_disc_sector(uint32_t lba, int raw_sector,
uint8_t* bytes, uint32_t size) {
if (!PSXRecompV4::state().disc_enabled) return;
patch_committed_disc_sector(lba, raw_sector, bytes, size);
}
+92
View File
@@ -956,6 +956,98 @@ int main() {
check(PSXRecompV4::mod_runtime_commit(raw_path,&error),"reader mount raw disc");
check(psx_mod_read_disc_file("S0/LEVEL.NSF",result.data(),(uint32_t)result.size(),&bytes) &&
std::equal(result.begin(),result.end(),iso.begin()+22*2048),"raw and ISO reads identical");
/* Plugin activation (native asset preparation) runs before the emulated
* drive's disc patches are enabled. Host reads must already return the
* committed plan's effective bytes, or a prepared cache would silently
* serve original assets under a modded plan. The drive path stays gated. */
{
const auto patched_root = root / "host-reader-patched";
const auto patched_iso = patched_root / "original.iso";
/* A byte past the original 3000-byte extent; a mod that grows the file
* through its directory record exposes it. */
std::vector<uint8_t> grown_iso = iso;
grown_iso[22 * 2048 + 3500] = 0x77;
write_bytes(patched_iso, grown_iso);
write_text(patched_root / "packages/reader.patch/1.0.0/manifest.toml",
"format_version = 5\n"
"id = \"reader.patch\"\n"
"version = \"1.0.0\"\n"
"name = \"Reader Patch\"\n"
"[[target]]\n"
"game_id = \"READER\"\n"
"disc_sha256 = \"" + sha256_hex(grown_iso) + "\"\n"
"[[feature]]\n"
"id = \"asset\"\n"
"name = \"Asset\"\n"
"[[feature]]\n"
"id = \"grow\"\n"
"name = \"Grow\"\n"
"[[patch]]\n"
"feature = \"grow\"\n"
"target = \"disc_user\"\n"
"offset = " + std::to_string(21 * 2048 + 10) + "\n"
"expected = \"b80b0000\"\n"
"replace = \"a00f0000\"\n"
"[[feature]]\n"
"id = \"huge\"\n"
"name = \"Huge\"\n"
"[[patch]]\n"
"feature = \"huge\"\n"
"target = \"disc_user\"\n"
"offset = " + std::to_string(21 * 2048 + 10) + "\n"
"expected = \"b80b0000\"\n"
"replace = \"00f82204\"\n"
"[[patch]]\n"
"feature = \"asset\"\n"
"target = \"disc_user\"\n"
"offset = " + std::to_string(22 * 2048 + 5) + "\n"
"expected = \"23\"\n"
"replace = \"5a\"\n");
write_text(patched_root / "state.toml",
"format_version = 2\n"
"[[feature]]\n"
"package_id = \"reader.patch\"\n"
"id = \"asset\"\n"
"enabled = true\n"
"[[feature]]\n"
"package_id = \"reader.patch\"\n"
"id = \"grow\"\n"
"enabled = true\n");
check(PSXRecompV4::mod_runtime_initialize(patched_root, "READER", 0, {}, &error),
"patched reader initialize");
check(PSXRecompV4::mod_runtime_commit(patched_iso, &error), "patched reader commit");
check(psx_mod_read_disc_file("S0/LEVEL.NSF", nullptr, 0, &bytes) && bytes == 4000,
"host disc reads resolve paths through patched directory records");
std::vector<uint8_t> effective(4000);
check(psx_mod_read_disc_file("S0/LEVEL.NSF", effective.data(),
(uint32_t)effective.size(), &bytes) &&
bytes == 4000 && effective[5] == 0x5a && effective[3500] == 0x77 &&
std::equal(effective.begin() + 6, effective.begin() + 3000,
iso.begin() + 22 * 2048 + 6),
"host disc reads apply the committed plan before the drive is enabled");
check(!psx_mod_read_disc_file("S0", nullptr, 0, &bytes),
"effective lookup still rejects directories");
std::array<uint8_t, 2048> drive_sector{};
std::copy_n(iso.begin() + 22 * 2048, 2048, drive_sector.begin());
mod_runtime_patch_disc_sector(22, 0, drive_sector.data(), (uint32_t)drive_sector.size());
check(drive_sector[5] == 0x23,
"the emulated drive path stays original until disc patches are enabled");
mod_runtime_enable_disc_patches();
mod_runtime_patch_disc_sector(22, 0, drive_sector.data(), (uint32_t)drive_sector.size());
check(drive_sector[5] == 0x5a, "enabled drive path applies the same plan");
/* Grown archives may exceed 64 MiB (an extended MMX6 ROCK_X6.DAT is
* 69,400,576 bytes); the bound is CD capacity, not a fixed cap. */
write_text(patched_root / "state.toml",
"format_version = 2\n"
"[[feature]]\n"
"package_id = \"reader.patch\"\n"
"id = \"huge\"\n"
"enabled = true\n");
check(PSXRecompV4::mod_runtime_initialize(patched_root, "READER", 0, {}, &error) &&
PSXRecompV4::mod_runtime_commit(patched_iso, &error), "huge plan commit");
check(psx_mod_read_disc_file("S0/LEVEL.NSF", nullptr, 0, &bytes) && bytes == 69400576u,
"files grown past 64 MiB keep their effective size");
}
fs::remove_all(root, ec);
if (failures) return 1;
std::cout << "mod runtime tests passed\n";