fix(runtime): preserve native mission AI selection word
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
This commit is contained in:
3 files changed
+66
-2
No files matched your search
@@ -86,6 +86,9 @@ pub struct MissionObjectDraft {
|
||||
pub original_id: Option<OriginalObjectId>,
|
||||
/// Raw mission resource reference.
|
||||
pub resource_name_raw: Vec<u8>,
|
||||
/// Raw word immediately after the mission resource reference. The native
|
||||
/// runtime uses it as an AI-selection index.
|
||||
pub raw_after_resource: u32,
|
||||
/// Raw identity/clan word from the mission.
|
||||
pub identity_or_clan_raw: u32,
|
||||
/// Raw word after the mission object's instance name. Native BAS
|
||||
@@ -895,6 +898,7 @@ fn load_mission_with_options(
|
||||
.map(|(index, object)| MissionObjectDraft {
|
||||
original_id: u32::try_from(index).ok().map(OriginalObjectId),
|
||||
resource_name_raw: object.resource_raw.clone(),
|
||||
raw_after_resource: object.raw_after_resource,
|
||||
identity_or_clan_raw: object.identity_or_clan_raw,
|
||||
raw_after_name: object.raw_after_name,
|
||||
unit_dat_header_flags: prototype_graph
|
||||
@@ -1613,6 +1617,7 @@ mod tests {
|
||||
MissionObjectDraft {
|
||||
original_id: Some(OriginalObjectId(7)),
|
||||
resource_name_raw: b"BUILDS/example.dat".to_vec(),
|
||||
raw_after_resource: 0,
|
||||
identity_or_clan_raw: 0,
|
||||
raw_after_name,
|
||||
unit_dat_header_flags: header_flags,
|
||||
@@ -2243,9 +2248,20 @@ mod tests {
|
||||
)
|
||||
.expect("load licensed AutoDemo");
|
||||
|
||||
let mission = loaded_mission_document(&engine).expect("mission document");
|
||||
let drafts = loaded_mission_object_drafts(&engine).expect("object drafts");
|
||||
assert_eq!(drafts.len(), mission.objects.len());
|
||||
assert!(mission
|
||||
.objects
|
||||
.iter()
|
||||
.any(|object| object.raw_after_resource != object.identity_or_clan_raw));
|
||||
for (object, draft) in mission.objects.iter().zip(drafts) {
|
||||
assert_eq!(draft.raw_after_resource, object.raw_after_resource);
|
||||
assert_eq!(draft.identity_or_clan_raw, object.identity_or_clan_raw);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
loaded_mission_document(&engine)
|
||||
.expect("mission document")
|
||||
mission
|
||||
.clans
|
||||
.iter()
|
||||
.map(|clan| clan.anchor)
|
||||
|
||||
@@ -60,5 +60,33 @@ u32 property_count
|
||||
Property properties[property_count]
|
||||
```
|
||||
|
||||
Эти два слова после `resource_name` -- разные входные значения. В оригинале
|
||||
`MisLoad.dll!FUN_10003900` (RVA `0x3900`) читает первое в смещение `0x54`
|
||||
нативной записи, если версия секции объектов выше 2; следующее слово попадает
|
||||
в смещение `0x5c` только при версии выше 3. Getter
|
||||
`MisLoad.dll!FUN_10001440` (RVA `0x1440`) копирует первое слово в смещение
|
||||
`0x50` строки, передаваемой хосту. `iron3d.dll!FUN_100a1f30` (RVA `0xA1F30`)
|
||||
загружает миссию и вызывает `FUN_100a3e40` (RVA `0xA3E40`); тот запрашивает
|
||||
строку через vtable slot `+0x28` (callsite RVA `0xA3F1A`). Хост передаёт это
|
||||
значение в создание схемы объекта в ArealMap, откуда оно становится первым
|
||||
DWORD дескриптора создания World3D. `World3D.dll!CreateObject` (RVA `0x7CB0`)
|
||||
читает DWORD по RVA `0x7E58`, а callsite RVA `0x7E62` передаёт его через vtable
|
||||
slot `+0x40` в путь выбора AI в Behavior. Поэтому Rust сохраняет первое слово
|
||||
как `raw_after_resource: u32`: оригинал использует его как индекс выбора AI,
|
||||
но соответствие значения типу `OwnerId` в `fparkan-world` не доказано.
|
||||
Соседнее `identity_or_clan_raw` остаётся отдельным raw-полем; его имя не
|
||||
доказывает, кому объект принадлежит.
|
||||
|
||||
Цепочка проверена на этих сборках (SHA-256): `MisLoad.dll`
|
||||
`c607762bb67db14ca530927c8e1347339b62b0a163ac90c708ee83142292e2bf`,
|
||||
`iron3d.dll`
|
||||
`d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa`,
|
||||
`ArealMap.dll`
|
||||
`a25f9254ebb339d70e2e07c8121950191e9584ca0858d8e9c2e2b6c671edf6ea`,
|
||||
`World3D.dll`
|
||||
`17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e` и
|
||||
`Behavior.dll`
|
||||
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`.
|
||||
|
||||
`Property` состоит из четырёх raw `u32` и имени. Typed views разрешены только
|
||||
для доказанных property names и consumers.
|
||||
@@ -195,6 +195,26 @@ Property properties[property_count]
|
||||
большинстве записей равен `(1,1,1)`. `instance_name` может быть пустым у
|
||||
unit-ссылки или содержать stem размещённого прототипа.
|
||||
|
||||
Сразу за строкой ресурса лежат два DWORD, и их нельзя склеивать в одно
|
||||
«владение» или менять местами. Первый, `raw_after_resource`, оригинал использует
|
||||
как индекс выбора AI. Второй сохраняется отдельным полем
|
||||
`identity_or_clan_raw`, но одно его имя ещё не говорит, кому объект принадлежит.
|
||||
В runtime draft оба значения остаются раздельными:
|
||||
|
||||
```rust
|
||||
MissionObjectDraft {
|
||||
raw_after_resource: object.raw_after_resource,
|
||||
identity_or_clan_raw: object.identity_or_clan_raw,
|
||||
..
|
||||
}
|
||||
```
|
||||
|
||||
Мы знаем это по пути создания: загрузчик копирует первое слово из TMA-записи в
|
||||
строку схемы объекта; затем хост передаёт его ArealMap и World3D, а World3D
|
||||
направляет его в ветку выбора AI. Соответствие этого индекса типу `OwnerId` в
|
||||
`fparkan-world` не подтверждено. Смещения, условия версий и контрольные суммы исследованных
|
||||
сборок приведены в [справочнике TMA](../reference/tma.md).
|
||||
|
||||
Свойства хранятся как ordered property bag:
|
||||
|
||||
```text
|
||||
|
||||
Reference in new issue
Block a user