diff options
| author | Valentin Popov <valentin@popov.link> | 2026-10-20 07:11:45 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-10-20 07:11:45 +0300 |
| commit | ecc46ed17772816d6f0de565a0a1605d0299a1dd (patch) | |
| tree | efe00bb6c5c04005fd5e0ebaf125e3dc8aa4f190 | |
| parent | e4645b1d6278a2f766dac99815d3938c42f61640 (diff) | |
| download | fparkan-ecc46ed17772816d6f0de565a0a1605d0299a1dd.tar.xz fparkan-ecc46ed17772816d6f0de565a0a1605d0299a1dd.zip | |
fix(runtime): preserve native mission AI selection word
| -rw-r--r-- | crates/fparkan-runtime/src/lib.rs | 20 | ||||
| -rw-r--r-- | docs/reference/tma.md | 28 | ||||
| -rw-r--r-- | docs/tomes/04-world.md | 20 |
3 files changed, 66 insertions, 2 deletions
diff --git a/crates/fparkan-runtime/src/lib.rs b/crates/fparkan-runtime/src/lib.rs index 80ce024..8d32778 100644 --- a/crates/fparkan-runtime/src/lib.rs +++ b/crates/fparkan-runtime/src/lib.rs @@ -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) diff --git a/docs/reference/tma.md b/docs/reference/tma.md index 30ee495..dff0796 100644 --- a/docs/reference/tma.md +++ b/docs/reference/tma.md @@ -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. diff --git a/docs/tomes/04-world.md b/docs/tomes/04-world.md index e486e18..dcd53b2 100644 --- a/docs/tomes/04-world.md +++ b/docs/tomes/04-world.md @@ -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 |
