mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
fix: guard complete dynamic patch records
This commit is contained in:
+11
-7
@@ -109,22 +109,26 @@ default = 2
|
||||
feature = "starting-lives"
|
||||
target = "main_exe"
|
||||
address = 0x8001DE64
|
||||
expected = "02 00"
|
||||
replace_from = { option = "count", encoding = "u16le" }
|
||||
expected = "02 00 02 24"
|
||||
replace_from = { option = "count", encoding = "u16le", offset = 0 }
|
||||
|
||||
[[patch]]
|
||||
feature = "starting-lives"
|
||||
target = "main_exe"
|
||||
address = 0x8001DE70
|
||||
expected = "03 00"
|
||||
replace_from = { option = "count", encoding = "u16le", addend = 1 }
|
||||
expected = "03 00 02 24"
|
||||
replace_from = { option = "count", encoding = "u16le", offset = 0, addend = 1 }
|
||||
```
|
||||
|
||||
`replace_from` and literal `replace` are mutually exclusive. The referenced
|
||||
option must be a bounded integer owned by the same feature. The initial
|
||||
encodings are `u8`, `u16le`, and `u32le`; the expected guard must have exactly
|
||||
the selected width. `addend` is the only supported transform, and the complete
|
||||
declared option range after that addend must fit the unsigned encoding.
|
||||
encodings are `u8`, `u16le`, and `u32le`. `offset` selects a byte field inside
|
||||
the expected guard and defaults to zero. Generated replacement bytes begin as
|
||||
an exact copy of the expected bytes, then the encoded value replaces only that
|
||||
field. This lets a MIPS immediate, for example, retain a guard and collision
|
||||
claim over its complete instruction. `addend` is the only supported transform,
|
||||
and the complete declared option range after that addend must fit the unsigned
|
||||
encoding.
|
||||
|
||||
There is deliberately no host-endian encoding, signed inference, mask, shift,
|
||||
scale, expression language, or partial-field merge. A package uses multiple
|
||||
|
||||
@@ -74,6 +74,7 @@ struct ModPatch {
|
||||
std::vector<uint8_t> replacement;
|
||||
std::string replace_from_option;
|
||||
ModValueEncoding replace_encoding = ModValueEncoding::U8;
|
||||
uint64_t replace_offset = 0;
|
||||
int64_t replace_addend = 0;
|
||||
std::string when_option;
|
||||
std::string when_value;
|
||||
|
||||
@@ -1155,7 +1155,7 @@ bool ModPackageManager::read_manifest(const fs::path& path, ModPackage& out,
|
||||
for (const auto& [key, unused] : table) {
|
||||
(void)unused;
|
||||
if (key != "option" && key != "encoding" &&
|
||||
key != "addend")
|
||||
key != "offset" && key != "addend")
|
||||
throw std::runtime_error(
|
||||
"replace_from has unknown field: " + key);
|
||||
}
|
||||
@@ -1163,6 +1163,13 @@ bool ModPackageManager::read_manifest(const fs::path& path, ModPackage& out,
|
||||
toml::find<std::string>(replacement, "option");
|
||||
const std::string encoding =
|
||||
toml::find<std::string>(replacement, "encoding");
|
||||
const int64_t replace_offset =
|
||||
toml::find_or<int64_t>(replacement, "offset", 0);
|
||||
if (replace_offset < 0)
|
||||
throw std::runtime_error(
|
||||
"replace_from offset must not be negative");
|
||||
patch.replace_offset =
|
||||
static_cast<uint64_t>(replace_offset);
|
||||
patch.replace_addend =
|
||||
toml::find_or<int64_t>(replacement, "addend", 0);
|
||||
if (!parse_value_encoding(
|
||||
@@ -1174,10 +1181,13 @@ bool ModPackageManager::read_manifest(const fs::path& path, ModPackage& out,
|
||||
if (!option || option->type != ModOptionType::Integer)
|
||||
throw std::runtime_error(
|
||||
"replace_from must reference a same-feature integer option");
|
||||
if (patch.expected.size() !=
|
||||
value_encoding_size(patch.replace_encoding))
|
||||
const uint64_t encoded_size =
|
||||
value_encoding_size(patch.replace_encoding);
|
||||
if (patch.replace_offset > patch.expected.size() ||
|
||||
encoded_size >
|
||||
patch.expected.size() - patch.replace_offset)
|
||||
throw std::runtime_error(
|
||||
"replace_from encoding width must match expected bytes");
|
||||
"replace_from value exceeds expected byte range");
|
||||
if (!option_range_fits_encoding(
|
||||
*option, patch.replace_encoding,
|
||||
patch.replace_addend))
|
||||
@@ -1893,18 +1903,24 @@ ModResolution ModPackageManager::resolve(const std::string& game_id,
|
||||
patch->replace_from_option);
|
||||
int64_t parsed = 0;
|
||||
int64_t adjusted = 0;
|
||||
std::vector<uint8_t> encoded;
|
||||
if (!parse_canonical_int64(selected_value, parsed) ||
|
||||
!checked_add_int64(
|
||||
parsed, patch->replace_addend, adjusted) ||
|
||||
!encode_unsigned_value(
|
||||
patch->replace_encoding, adjusted,
|
||||
write.replacement)) {
|
||||
encoded)) {
|
||||
result.errors.push_back(
|
||||
package->id + "/" + patch->feature_id +
|
||||
": could not encode replace_from option " +
|
||||
patch->replace_from_option);
|
||||
continue;
|
||||
}
|
||||
write.replacement = write.expected;
|
||||
std::copy(
|
||||
encoded.begin(), encoded.end(),
|
||||
write.replacement.begin() +
|
||||
static_cast<size_t>(patch->replace_offset));
|
||||
if (write.replacement == write.expected)
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -478,6 +478,12 @@ int main() {
|
||||
"expected = \"00000000\"\n"
|
||||
"replace_from = { option = \"dword\", encoding = \"u32le\" }\n"
|
||||
"[[patch]]\n"
|
||||
"feature = \"numeric\"\n"
|
||||
"target = \"main_exe\"\n"
|
||||
"address = 2147500040\n"
|
||||
"expected = \"0000aabb\"\n"
|
||||
"replace_from = { option = \"word\", encoding = \"u16le\", offset = 0 }\n"
|
||||
"[[patch]]\n"
|
||||
"feature = \"numeric-collision\"\n"
|
||||
"target = \"main_exe\"\n"
|
||||
"address = 2147500032\n"
|
||||
@@ -507,6 +513,8 @@ int main() {
|
||||
const ModResolution::Write* noop_write = numeric_write(0x80004001ull);
|
||||
const ModResolution::Write* word_write = numeric_write(0x80004002ull);
|
||||
const ModResolution::Write* dword_write = numeric_write(0x80004004ull);
|
||||
const ModResolution::Write* guarded_word_write =
|
||||
numeric_write(0x80004008ull);
|
||||
check(parametric.ok && byte_write &&
|
||||
byte_write->replacement == std::vector<uint8_t>({7}),
|
||||
"u8 replace_from must encode the selected value");
|
||||
@@ -519,6 +527,10 @@ int main() {
|
||||
dword_write->replacement ==
|
||||
std::vector<uint8_t>({0x78, 0x56, 0x34, 0x12}),
|
||||
"u32le replace_from must encode little-endian");
|
||||
check(guarded_word_write &&
|
||||
guarded_word_write->replacement ==
|
||||
std::vector<uint8_t>({0x34, 0x12, 0xaa, 0xbb}),
|
||||
"replace_from must preserve guarded bytes outside its value field");
|
||||
const std::string parametric_fingerprint = parametric.fingerprint;
|
||||
check(feature_reload.set_feature_option(
|
||||
"parametric.mod", "numeric", "byte", "9", &error),
|
||||
@@ -577,8 +589,8 @@ int main() {
|
||||
"format_version=2\n" + dynamic_prelude +
|
||||
"[[patch]]\nfeature=\"bad\"\ntarget=\"main_exe\"\n"
|
||||
"address=2147487744\nexpected=\"0000\"\n"
|
||||
"replace_from={option=\"value\",encoding=\"u8\"}\n"),
|
||||
"replace_from width must match the expected guard");
|
||||
"replace_from={option=\"value\",encoding=\"u8\",offset=2}\n"),
|
||||
"replace_from value must stay inside the expected guard");
|
||||
check(reject_parametric_manifest(
|
||||
"dynamic-overflow",
|
||||
"format_version=2\n" + dynamic_prelude +
|
||||
|
||||
Reference in New Issue
Block a user