fix(script): read Init guards in native disk order
Docs Deploy / Build and Deploy MkDocs (push) Successful in 39s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 39s
This commit is contained in:
4 files changed
+99
-47
No files matched your search
@@ -12283,7 +12283,7 @@ mod tests {
|
|||||||
name_raw: b"Init\0".to_vec(),
|
name_raw: b"Init\0".to_vec(),
|
||||||
event_word: 0x1234_5678,
|
event_word: 0x1234_5678,
|
||||||
instructions: vec![fparkan_script::ScriptInstruction {
|
instructions: vec![fparkan_script::ScriptInstruction {
|
||||||
header_words: [19, u32::MAX, u32::MAX, 0, 0, 3, 0],
|
header_words: [19, u32::MAX, u32::MAX, u32::MAX, 6, 3, u32::MAX],
|
||||||
references: vec![0, 1, 2],
|
references: vec![0, 1, 2],
|
||||||
}],
|
}],
|
||||||
}],
|
}],
|
||||||
@@ -12370,7 +12370,7 @@ mod tests {
|
|||||||
event_word: 0x1234,
|
event_word: 0x1234,
|
||||||
instructions: vec![
|
instructions: vec![
|
||||||
fparkan_script::ScriptInstruction {
|
fparkan_script::ScriptInstruction {
|
||||||
header_words: [19, u32::MAX, u32::MAX, 0, 0, 3, 0],
|
header_words: [19, u32::MAX, u32::MAX, u32::MAX, 6, 3, u32::MAX],
|
||||||
references: vec![0, 1, 2],
|
references: vec![0, 1, 2],
|
||||||
},
|
},
|
||||||
fparkan_script::ScriptInstruction {
|
fparkan_script::ScriptInstruction {
|
||||||
@@ -12784,11 +12784,24 @@ mod tests {
|
|||||||
assert_eq!(loaded.script_bundle_count, 2);
|
assert_eq!(loaded.script_bundle_count, 2);
|
||||||
assert_eq!(loaded.script_init_state_count, 2);
|
assert_eq!(loaded.script_init_state_count, 2);
|
||||||
assert_eq!(loaded.script_varset_state_count, 2);
|
assert_eq!(loaded.script_varset_state_count, 2);
|
||||||
assert!(loaded_mission_script_bundles(&engine)
|
let script_bundles = loaded_mission_script_bundles(&engine).expect("script bundles");
|
||||||
.expect("script bundles")
|
assert_eq!(script_bundles.len(), 2);
|
||||||
.iter()
|
assert!(script_bundles.iter().all(|bundle| bundle.init_execution
|
||||||
.all(|bundle| bundle.init_execution
|
== MissionScriptInitExecution::Executed { event_index: 0 }));
|
||||||
== MissionScriptInitExecution::Executed { event_index: 0 }));
|
for bundle in script_bundles {
|
||||||
|
let init = bundle
|
||||||
|
.package
|
||||||
|
.events
|
||||||
|
.iter()
|
||||||
|
.find(|event| event.name_raw.split(|byte| *byte == 0).next() == Some(&b"Init"[..]))
|
||||||
|
.expect("first Init event");
|
||||||
|
let instruction = init.instructions.first().expect("Handler(19) action");
|
||||||
|
assert_eq!(
|
||||||
|
instruction.header_words,
|
||||||
|
[19, u32::MAX, u32::MAX, u32::MAX, 6, 3, u32::MAX]
|
||||||
|
);
|
||||||
|
assert_eq!(instruction.references.as_slice(), &[224, 225, 226]);
|
||||||
|
}
|
||||||
assert_eq!(engine.behavior_rng_state, 0xc823_f683);
|
assert_eq!(engine.behavior_rng_state, 0xc823_f683);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
loaded_mission_script_bundles(&engine)
|
loaded_mission_script_bundles(&engine)
|
||||||
@@ -13755,6 +13768,16 @@ mod tests {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.expect("campaign package with an unsupported Init instruction");
|
.expect("campaign package with an unsupported Init instruction");
|
||||||
|
assert_eq!(
|
||||||
|
&unsupported.init_execution,
|
||||||
|
&MissionScriptInitExecution::Unsupported {
|
||||||
|
event_index: 0,
|
||||||
|
instruction_index: 1,
|
||||||
|
error: Handler19ResolveError::UnsupportedSelector {
|
||||||
|
found: fparkan_script::ScriptDispatchSelector::Sentinel,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
let clan_index = unsupported.clan_index;
|
let clan_index = unsupported.clan_index;
|
||||||
let states = loaded_mission_script_init_states(&engine).expect("Init writes");
|
let states = loaded_mission_script_init_states(&engine).expect("Init writes");
|
||||||
assert!(states.iter().all(|state| state.clan_index != clan_index));
|
assert!(states.iter().all(|state| state.clan_index != clan_index));
|
||||||
|
|||||||
@@ -205,12 +205,12 @@ pub enum Handler19ResolveError {
|
|||||||
},
|
},
|
||||||
/// Only action instructions with control word `0xffff_ffff` are supported.
|
/// Only action instructions with control word `0xffff_ffff` are supported.
|
||||||
UnsupportedControlWord {
|
UnsupportedControlWord {
|
||||||
/// Opaque control word from instruction header word one.
|
/// Opaque control word from disk-order header index 3.
|
||||||
found: u32,
|
found: u32,
|
||||||
},
|
},
|
||||||
/// Only instructions with no result-save reference are supported.
|
/// Only instructions with no result-save reference are supported.
|
||||||
UnsupportedResultSave {
|
UnsupportedResultSave {
|
||||||
/// Opaque variable reference from instruction header word two.
|
/// Opaque variable reference from disk-order header index 1.
|
||||||
found: u32,
|
found: u32,
|
||||||
},
|
},
|
||||||
/// One of the three required references was absent.
|
/// One of the three required references was absent.
|
||||||
@@ -637,14 +637,14 @@ impl VarSet {
|
|||||||
if selector != ScriptDispatchSelector::Handler(19) {
|
if selector != ScriptDispatchSelector::Handler(19) {
|
||||||
return Err(Handler19ResolveError::UnsupportedSelector { found: selector });
|
return Err(Handler19ResolveError::UnsupportedSelector { found: selector });
|
||||||
}
|
}
|
||||||
if instruction.header_words[1] != u32::MAX {
|
if instruction.header_words[3] != u32::MAX {
|
||||||
return Err(Handler19ResolveError::UnsupportedControlWord {
|
return Err(Handler19ResolveError::UnsupportedControlWord {
|
||||||
found: instruction.header_words[1],
|
found: instruction.header_words[3],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if instruction.header_words[2] != u32::MAX {
|
if instruction.header_words[1] != u32::MAX {
|
||||||
return Err(Handler19ResolveError::UnsupportedResultSave {
|
return Err(Handler19ResolveError::UnsupportedResultSave {
|
||||||
found: instruction.header_words[2],
|
found: instruction.header_words[1],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.resolve_handler19(instruction, input)
|
self.resolve_handler19(instruction, input)
|
||||||
@@ -1688,7 +1688,7 @@ STRING( 8, ignored, ignored, ignored)\r\n";
|
|||||||
let varset = parse_varset(b"VAR( DWORD, x, 0)\nVAR( DWORD, y, 0)\nVAR( DWORD, id, 0)\n")
|
let varset = parse_varset(b"VAR( DWORD, x, 0)\nVAR( DWORD, y, 0)\nVAR( DWORD, id, 0)\n")
|
||||||
.expect("DWORD targets");
|
.expect("DWORD targets");
|
||||||
let action = |references: Vec<u32>| ScriptInstruction {
|
let action = |references: Vec<u32>| ScriptInstruction {
|
||||||
header_words: [19, u32::MAX, u32::MAX, 0, 0, 3, 0],
|
header_words: [19, u32::MAX, u32::MAX, u32::MAX, 6, 3, u32::MAX],
|
||||||
references,
|
references,
|
||||||
};
|
};
|
||||||
let package = ScriptPackage {
|
let package = ScriptPackage {
|
||||||
@@ -1832,7 +1832,7 @@ STRING( 8, ignored, ignored, ignored)\r\n";
|
|||||||
event_word: 0,
|
event_word: 0,
|
||||||
instructions: vec![
|
instructions: vec![
|
||||||
ScriptInstruction {
|
ScriptInstruction {
|
||||||
header_words: [19, u32::MAX, u32::MAX, 0, 0, 3, 0],
|
header_words: [19, u32::MAX, u32::MAX, u32::MAX, 6, 3, u32::MAX],
|
||||||
references: vec![0, 1, 2],
|
references: vec![0, 1, 2],
|
||||||
},
|
},
|
||||||
ScriptInstruction {
|
ScriptInstruction {
|
||||||
@@ -1840,7 +1840,7 @@ STRING( 8, ignored, ignored, ignored)\r\n";
|
|||||||
references: Vec::new(),
|
references: Vec::new(),
|
||||||
},
|
},
|
||||||
ScriptInstruction {
|
ScriptInstruction {
|
||||||
header_words: [19, u32::MAX, u32::MAX, 0, 0, 3, 0],
|
header_words: [19, u32::MAX, u32::MAX, u32::MAX, 6, 3, u32::MAX],
|
||||||
references: vec![0, 1, 2],
|
references: vec![0, 1, 2],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1884,7 +1884,7 @@ STRING( 8, ignored, ignored, ignored)\r\n";
|
|||||||
references: Vec::new(),
|
references: Vec::new(),
|
||||||
},
|
},
|
||||||
ScriptInstruction {
|
ScriptInstruction {
|
||||||
header_words: [19, u32::MAX, u32::MAX, 0, 0, 3, 0],
|
header_words: [19, u32::MAX, u32::MAX, u32::MAX, 6, 3, u32::MAX],
|
||||||
references: vec![0, 1, 2],
|
references: vec![0, 1, 2],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1913,7 +1913,7 @@ STRING( 8, ignored, ignored, ignored)\r\n";
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn handler_nineteen_init_types_unsupported_control_and_result_save() {
|
fn handler_nineteen_init_uses_disk_control_and_result_fields() {
|
||||||
let varset = parse_varset(b"VAR( DWORD, x, 0)\nVAR( DWORD, y, 0)\nVAR( DWORD, id, 0)\n")
|
let varset = parse_varset(b"VAR( DWORD, x, 0)\nVAR( DWORD, y, 0)\nVAR( DWORD, id, 0)\n")
|
||||||
.expect("DWORD targets");
|
.expect("DWORD targets");
|
||||||
let input = Handler19InitInput {
|
let input = Handler19InitInput {
|
||||||
@@ -1921,18 +1921,17 @@ STRING( 8, ignored, ignored, ignored)\r\n";
|
|||||||
second_x87_word: 8,
|
second_x87_word: 8,
|
||||||
third_word: 9,
|
third_word: 9,
|
||||||
};
|
};
|
||||||
let instruction = |control_word, result_word| ScriptInstruction {
|
let resolve = |header_words| {
|
||||||
header_words: [19, control_word, result_word, 0, 0, 3, 0],
|
|
||||||
references: vec![0, 1, 2],
|
|
||||||
};
|
|
||||||
let resolve = |instruction| {
|
|
||||||
let package = ScriptPackage {
|
let package = ScriptPackage {
|
||||||
opcode_handler_count: 73,
|
opcode_handler_count: 73,
|
||||||
events: vec![ScriptEvent {
|
events: vec![ScriptEvent {
|
||||||
name_len: 4,
|
name_len: 4,
|
||||||
name_raw: b"Init\0".to_vec(),
|
name_raw: b"Init\0".to_vec(),
|
||||||
event_word: 0,
|
event_word: 0,
|
||||||
instructions: vec![instruction],
|
instructions: vec![ScriptInstruction {
|
||||||
|
header_words,
|
||||||
|
references: vec![0, 1, 2],
|
||||||
|
}],
|
||||||
}],
|
}],
|
||||||
trailing_bytes: Vec::new(),
|
trailing_bytes: Vec::new(),
|
||||||
raw: std::sync::Arc::from(&b""[..]),
|
raw: std::sync::Arc::from(&b""[..]),
|
||||||
@@ -1940,21 +1939,34 @@ STRING( 8, ignored, ignored, ignored)\r\n";
|
|||||||
varset.resolve_handler19_init(&package, input)
|
varset.resolve_handler19_init(&package, input)
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
let actual = (
|
||||||
resolve(instruction(0, u32::MAX)),
|
resolve([19, u32::MAX, u32::MAX, 2, 6, 3, u32::MAX]),
|
||||||
Ok(Handler19InitResolution::Unsupported {
|
resolve([19, 0x1234, u32::MAX, u32::MAX, 6, 3, u32::MAX]),
|
||||||
event_index: 0,
|
resolve([19, u32::MAX, 0xdecafbad, u32::MAX, 6, 3, u32::MAX]),
|
||||||
instruction_index: 0,
|
|
||||||
error: Handler19ResolveError::UnsupportedControlWord { found: 0 },
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve(instruction(u32::MAX, 2)),
|
actual,
|
||||||
Ok(Handler19InitResolution::Unsupported {
|
(
|
||||||
event_index: 0,
|
Ok(Handler19InitResolution::Unsupported {
|
||||||
instruction_index: 0,
|
event_index: 0,
|
||||||
error: Handler19ResolveError::UnsupportedResultSave { found: 2 },
|
instruction_index: 0,
|
||||||
})
|
error: Handler19ResolveError::UnsupportedControlWord { found: 2 },
|
||||||
|
}),
|
||||||
|
Ok(Handler19InitResolution::Unsupported {
|
||||||
|
event_index: 0,
|
||||||
|
instruction_index: 0,
|
||||||
|
error: Handler19ResolveError::UnsupportedResultSave { found: 0x1234 },
|
||||||
|
}),
|
||||||
|
Ok(Handler19InitResolution::Executed {
|
||||||
|
event_index: 0,
|
||||||
|
event_word: 0,
|
||||||
|
writes: vec![
|
||||||
|
Handler19DwordWrite { index: 0, value: 7 },
|
||||||
|
Handler19DwordWrite { index: 1, value: 8 },
|
||||||
|
Handler19DwordWrite { index: 2, value: 9 },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,11 +141,16 @@ Rust.
|
|||||||
продолжает работу. Rust моделирует числовую запись, а не это UI-предупреждение;
|
продолжает работу. Rust моделирует числовую запись, а не это UI-предупреждение;
|
||||||
на первом `FISTP qword` он явно завершает загрузку ошибкой для NaN, infinity и
|
на первом `FISTP qword` он явно завершает загрузку ошибкой для NaN, infinity и
|
||||||
значений вне signed-qword диапазона. В первом событии `Init` Rust поддерживает
|
значений вне signed-qword диапазона. В первом событии `Init` Rust поддерживает
|
||||||
только action-форму `Handler(19)` (`control word` и `result reference` равны
|
только action-форму `Handler(19)`: в семи disk header words control находится в
|
||||||
`0xffffffff`). Неизвестная инструкция даёт `Unsupported` в preview, не применяя
|
`header_words[3]`, а result-save reference — в `header_words[1]`; для поддержанной
|
||||||
накопленные частичные записи; при полной загрузке это ошибка. Это ограничение
|
формы оба равны `0xffffffff`. Это подтверждается перестановкой записи в
|
||||||
Rust исполнителя, не правило оригинального dispatcher-а. Ошибочные ссылки и
|
`ai.dll!FUN_10011b20` (RVA `0x11b20`) и чтениями в исполнителе
|
||||||
типы остаются ошибками в обоих режимах.
|
`FUN_10012020` (RVA `0x12020`) той же GOG-сборки. `Handler(19)` (`FUN_1000aa38`,
|
||||||
|
RVA `0xaa38`) использует references `0..2`; `header_words[2]` его result-save
|
||||||
|
guard-ом не проверяется. Неизвестная инструкция даёт `Unsupported` в preview,
|
||||||
|
не применяя накопленные частичные записи; при полной загрузке это ошибка. Это
|
||||||
|
ограничение Rust исполнителя, не правило оригинального dispatcher-а. Ошибочные
|
||||||
|
ссылки и типы остаются ошибками в обоих режимах.
|
||||||
|
|
||||||
Для GOG-сборки также найден статический host call path в `Init`. В
|
Для GOG-сборки также найден статический host call path в `Init`. В
|
||||||
`iron3d.dll!FUN_1005ff40` (RVA `0x5ff40`) одна ветвь сначала проходит записи
|
`iron3d.dll!FUN_1005ff40` (RVA `0x5ff40`) одна ветвь сначала проходит записи
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ handlers, второй — числом event records. Каждый event хра
|
|||||||
`name_len + 1` raw bytes с обязательным NUL, opaque event word и count вложенных
|
`name_len + 1` raw bytes с обязательным NUL, opaque event word и count вложенных
|
||||||
records. Вложенный record сохраняет семь `u32` header words (в disk order),
|
records. Вложенный record сохраняет семь `u32` header words (в disk order),
|
||||||
список `u32` references после шестого header word и trailing seventh word.
|
список `u32` references после шестого header word и trailing seventh word.
|
||||||
Никакой из этих words ещё не получает semantic name. `.fml` — текстовый
|
Семантика остальных слов остаётся открытой; поля control и result-save описаны
|
||||||
|
в узком `Init` contract ниже. `.fml` — текстовый
|
||||||
symbol/formula oracle; `varset.var` задаёт `VAR(...)`/`STRING(...)` defaults.
|
symbol/formula oracle; `varset.var` задаёт `VAR(...)`/`STRING(...)` defaults.
|
||||||
`fparkan-script::parse_varset` уже читает подтверждённые numeric
|
`fparkan-script::parse_varset` уже читает подтверждённые numeric
|
||||||
`VAR(float|DWORD, name, default)` declarations byte-safe (comments остаются
|
`VAR(float|DWORD, name, default)` declarations byte-safe (comments остаются
|
||||||
@@ -302,10 +303,21 @@ image base `0x10000000`. В ней `FUN_10011f40` (RVA `0x11f40`) ищет им
|
|||||||
`FUN_10011f10` передаёт выбранный индекс внутреннему циклу `FUN_10011fb0`
|
`FUN_10011f10` передаёт выбранный индекс внутреннему циклу `FUN_10011fb0`
|
||||||
(RVA `0x11fb0`). Цикл начинает с первой вложенной инструкции, сбрасывает индекс
|
(RVA `0x11fb0`). Цикл начинает с первой вложенной инструкции, сбрасывает индекс
|
||||||
инструкции и глубину стека условий, затем идёт по записям в порядке файла.
|
инструкции и глубину стека условий, затем идёт по записям в порядке файла.
|
||||||
В исходном `default.scr` событие `Init` содержит одну инструкцию selector `19`,
|
Читатель `FUN_10011b20` (RVA `0x11b20`) переставляет семь disk header words в
|
||||||
control word `0xffffffff` (действие) и result reference `0xffffffff` (результат
|
runtime record: `[0, 1, 2, 3, 4, 5, 6]` переходят в offsets
|
||||||
не сохраняется). Это наблюдаемая форма пакета; поведение оригинала для остальных
|
`[+0, +8, +0x0c, +4, +0x10, +0x18, +0x1c]`; указатель references лежит в `+0x14`.
|
||||||
форм здесь не утверждается.
|
Исполнитель `FUN_10012020` (RVA `0x12020`) берёт control из runtime `+0x04`, то
|
||||||
|
есть из `header_words[3]`, а result-save reference — из runtime `+0x08`, то есть
|
||||||
|
из `header_words[1]`. Disk word `header_words[2]` попадает в `+0x0c` и участвует
|
||||||
|
в особой ветви selector `-1`, но не является result-save полем `Handler(19)`.
|
||||||
|
|
||||||
|
В исходном `default.scr` событие `Init` содержит действие selector `19` с
|
||||||
|
заголовком `[19, 0xffffffff, 0xffffffff, 0xffffffff, 6, 3, 0xffffffff]` и
|
||||||
|
references `224, 225, 226`. Для этого действия `header_words[3] = 0xffffffff`
|
||||||
|
(action control), а `header_words[1] = 0xffffffff` (результат не сохраняется).
|
||||||
|
Одинаковое значение в `header_words[2]` — лишь содержимое этой записи, оно не
|
||||||
|
проверяется guard-ом `Handler(19)`. Это наблюдаемая форма пакета; поведение
|
||||||
|
оригинала для остальных форм здесь не утверждается.
|
||||||
|
|
||||||
В GOG `default.scr` события `Init` в двух AutoDemo bundles имеют по одной такой
|
В GOG `default.scr` события `Init` в двух AutoDemo bundles имеют по одной такой
|
||||||
инструкции. Её ссылки `224`, `225`, `226` ведут к объявлениям `DWORD`
|
инструкции. Её ссылки `224`, `225`, `226` ведут к объявлениям `DWORD`
|
||||||
|
|||||||
Reference in new issue
Block a user