diff --git a/crates/sylpheed-formats/examples/unit_signatures.rs b/crates/sylpheed-formats/examples/unit_signatures.rs new file mode 100644 index 0000000..8fba351 --- /dev/null +++ b/crates/sylpheed-formats/examples/unit_signatures.rs @@ -0,0 +1,33 @@ +//! (HP, Size_X, Size_Y, Size_Z) for every craft unit and vessel on the disc. +//! +//! A live definition object in guest RAM carries HP at `+0x54` and the sizes at +//! `+0x30/34/38` but no name in its first words, so identity has to come from the +//! values themselves: this is the lookup table for that match. +//! Run: unit_signatures +use sylpheed_formats::game_data::{load_units, load_vessels}; +use sylpheed_formats::PakArchive; +fn main() { + let disc = std::env::args().nth(1).expect("disc root"); + let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).expect("pak"); + println!("{:<34} {:>9} {:>8} {:>8} {:>8} kind", "id", "hp", "size_x", "size_y", "size_z"); + for u in load_units(&pak) { + println!( + "{:<34} {:>9} {:>8} {:>8} {:>8} unit", + u.id.unwrap_or_default(), + u.hp.map(|v| v.to_string()).unwrap_or("—".into()), + u.size_x.map(|v| v.to_string()).unwrap_or("—".into()), + u.size_y.map(|v| v.to_string()).unwrap_or("—".into()), + u.size_z.map(|v| v.to_string()).unwrap_or("—".into()), + ); + } + for v in load_vessels(&pak) { + println!( + "{:<34} {:>9} {:>8} {:>8} {:>8} vessel", + v.id.unwrap_or_default(), + v.hp.map(|x| x.to_string()).unwrap_or("—".into()), + v.size_x.map(|x| x.to_string()).unwrap_or("—".into()), + v.size_y.map(|x| x.to_string()).unwrap_or("—".into()), + v.size_z.map(|x| x.to_string()).unwrap_or("—".into()), + ); + } +} diff --git a/docs/re/captures/disc-unit-signatures.txt b/docs/re/captures/disc-unit-signatures.txt new file mode 100644 index 0000000..b7041cd --- /dev/null +++ b/docs/re/captures/disc-unit-signatures.txt @@ -0,0 +1,115 @@ + + +id hp size_x size_y size_z kind +UN_e006_ADAN_Vindicator_MargrasF 6400 12 5 30 unit +UN_be005_ADAN_SpaceFortress 10000 2500 3100 2600 unit +UN_f001_TCAF_DeltaSaber_T 1000 10 7 29 unit +UN_f002_TCAF_DeltaSaber_W 1000 10 7 29 unit +UN_e009_ADAN_Phantom 1000 18 16 24 unit +UN_e001_ADAN_Elan_EX4 500 25 10 30 unit +UN_e011_ADAN_Attacker_B 1500 200 50 100 unit +UN_f001_TCAF_DeltaSaber_T_Ttrl 1000 10 7 29 unit +UN_f202_TCAF_Cargo 4000 1400 600 2000 unit +UN_e007_ADAN_Turret_EX4 100 20 9 22 unit +UN_e010_ADAN_Attacker_S 500 100 40 50 unit +UN_be001_ADAN_TerrafoamingUnit 10000 2600 2200 — unit +UN_e005_ADAN_ElanTypeQ_Margras 3200 24 15 38 unit +UN_e001_ADAN_Elan_GR_Violeta 1200 25 10 30 unit +UN_e004_ADAN_ElanPlus_N 800 24 10 30 unit +UN_e201_ADAN_ISCMissile 3000 300 — 2800 unit +UN_f001_TCAF_DeltaSaber_T_Player_Ttrl1 1500 10 7 29 unit +UN_f001_TCAF_DeltaSaber_T_Player_Ttrl2 1500 10 7 29 unit +UN_f003_TCAF_ArrowHead_EX4 300 10 5 20 unit +UN_f003_TCAF_ArrowHead_EX5 300 10 5 20 unit +UN_e011_ADAN_Attacker_B_HF 2250 200 50 100 unit +UN_e015_ADAN_Puppy — 7 5.5 10 unit +Ship_Test 100 20 14 29 unit +UN_e010_ADAN_Attacker_S_HF 750 100 40 50 unit +Test_ADAN_PrometheusDriver 10000 1 — — unit +UN_f001_TCAF_DeltaSaber_T_EX5 1000 10 7 29 unit +UN_e002_ADAN_Elan_N 800 20 10 30 unit +UN_e008_ADAN_TurretPlus 300 24 9 35 unit +UN_mn040_Asteroid_Big 10000 2000 3500 — unit +UN_mn500_ADAN_FloatingMine 1000 200 — — unit +UN_S04_Asteroid_cmesh_01 10000 46900 55500 13600 unit +UN_S05_Asteroid_cmesh_01 10000 10400 13000 34400 unit +UN_S04_Asteroid_cmesh_03 10000 42800 37700 47400 unit +UN_S04_Asteroid_cmesh_04 10000 59000 56900 21800 unit +UN_S05_Asteroid_cmesh_03 10000 8300 13000 19800 unit +UN_S04_Asteroid_cmesh_05 — 26000 21800 72400 unit +UN_S05_Asteroid_cmesh_04 10000 7400 15800 30100 unit +UN_e011_ADAN_Attacker_B_EX4 1500 200 50 100 unit +UN_e910_core_ADAN_GeneratorCore 10000 2000 — — unit +UN_n001_TTRL_Box 100 0.1 — — unit +UN_e006_ADAN_Vindicator_Margras 6400 12 5 30 unit +UN_e010_ADAN_Attacker_S_EX4 500 100 40 50 unit +UN_bf001_TCAF_SchlosBase 10000 2500 3100 2600 unit +UN_f001_TCAF_DeltaSaber_T_EX5_el 1000 10 7 29 unit +UN_n001_TTRL_Box_move 100 0.1 — — unit +UN_e004_ADAN_ElanPlus_NF 800 24 10 30 unit +Test_ADAN_PrometheusDriver_InsideP2_01 10000 1 — — unit +UN_e001_ADAN_Elan 500 25 10 30 unit +UN_e007_ADAN_Turret 100 20 9 22 unit +UN_e003_ADAN_ElanPlus_Margras 1600 24 10 30 unit +UN_e901_ADAN_Boss 10000 500 1700 — unit +UN_e013_ADAN_ElanPlus_Taskent 1300 24 10 30 unit +UN_e011_ADAN_Attacker_B_HF_Wayne 3000 200 50 100 unit +UN_f003_TCAF_ArrowHead 300 10 5 20 unit +UN_f004_TCAF_DeltaSaber_A_Player 1500 10 7 29 unit +UN_e010_ADAN_Attacker_S_HF_Wayne 1000 100 40 50 unit +UN_e015_ADAN_Puppy_2 — 7 5.5 10 unit +UN_S01_Asteroid_cmesh_01a — 23000 21700 13400 unit +UN_S01_Asteroid_cmesh_02a 10000 20900 24000 23800 unit +UN_S01_Asteroid_cmesh_01b — 18000 20900 14200 unit +UN_S01_Asteroid_cmesh_02b — 20400 21800 26700 unit +UN_S01_Asteroid_cmesh_03a 10000 23600 11700 30300 unit +UN_bf002_TCAF_TransitPlatform 10000 2500 3100 4600 unit +UN_e001_ADAN_Elan_GR 800 25 10 30 unit +UN_S13_Asteroid_cmesh_01a 10000 14200 10200 14700 unit +UN_S01_Asteroid_cmesh_03b — 21800 18600 37200 unit +UN_S01_Asteroid_cmesh_04a — 32800 19900 23300 unit +UN_S13_Asteroid_cmesh_01b 10000 8000 5400 16400 unit +UN_S13_Asteroid_cmesh_02a 10000 6200 5400 11300 unit +UN_S01_Asteroid_cmesh_04b — 26400 23100 19600 unit +UN_S04_Asteroid_cmesh_02a — 25100 20800 61400 unit +UN_S13_Asteroid_cmesh_02b 10000 5100 6900 10500 unit +UN_S05_Asteroid_cmesh_02a 10000 10100 13200 9100 unit +UN_S04_Asteroid_cmesh_02b 10000 25200 19700 67000 unit +UN_S05_Asteroid_cmesh_02b 10000 8700 14900 12300 unit +UN_S08_Asteroid_cmesh_01a 10000 14700 14900 21300 unit +UN_S08_Asteroid_cmesh_01b 10000 14900 14600 15800 unit +UN_S08_Asteroid_cmesh_02a 10000 17400 13400 15200 unit +UN_S04_Asteroid_cmesh_06a — 14400 20800 45900 unit +UN_S08_Asteroid_cmesh_02b 10000 16800 10700 14200 unit +UN_S08_Asteroid_cmesh_03a — 14000 10000 13700 unit +UN_S04_Asteroid_cmesh_06b 10000 14100 16200 21500 unit +UN_S04_Asteroid_cmesh_07a — 23200 14000 67100 unit +UN_S08_Asteroid_cmesh_03b 10000 13900 15100 15700 unit +UN_S04_Asteroid_cmesh_07b 10000 25300 4900 40600 unit +UN_e008_ADAN_TurretPlus_EX4 300 24 9 35 unit +UN_f001_TCAF_DeltaSaber_T_Player 1500 10 7 29 unit +UN_f003_TCAF_ArrowHead_Yoji 400 10 5 20 unit +UN_f002_TCAF_DeltaSaber_W_Player 1500 10 7 29 unit +UN_e104_ADAN_Carrier 20000 2300 1000 3300 vessel +UN_f106_TCAF_Destroyer 10000 200 — 2000 vessel +UN_e105_ADAN_Cruiser 30000 600 — 3800 vessel +UN_e101_ADAN_SDBattleship 100000 1900 2100 12300 vessel +UN_f105_TCAF_Cruiser 30000 700 — 3800 vessel +UN_f102_TCAF_LightCarrier 20000 500 — 1600 vessel +UN_e102_ADAN_BattleshipEX 50000 1900 2300 6500 vessel +UN_e107_ADAN_AAFrigate_EX4 4000 100 200 — vessel +UN_e102_ADAN_Battleship 50000 1900 2300 6500 vessel +UN_f104_TCAF_Battleship 30000 2000 — 6000 vessel +UN_f105_TCAF_Cruiser_EX5 30000 700 — 3800 vessel +UN_e108_ADAN_ASFrigateEX 4000 80 — 350 vessel +UN_f106_TCAF_Destroyer_Inv 10000 200 — 2000 vessel +UN_e107_ADAN_AAFrigate 4000 100 200 — vessel +UN_f104_TCAF_Battleship_EX5 30000 2000 — 6000 vessel +UN_f105_TCAF_Cruiser_Inv 30000 700 — 3800 vessel +UN_f102_TCAF_LightCarrier_Inv 20000 500 — 1600 vessel +UN_e108_ADAN_ASFrigate 4000 80 — 350 vessel +UN_e106_ADAN_DestroyerEX 10000 300 — 2100 vessel +UN_f101_TCAF_Acropolis 25000 400 — 1400 vessel +UN_e105_ADAN_CruiserEX 30000 600 — 3800 vessel +UN_e101_ADAN_SDBattleshipEX 100000 1900 2100 12300 vessel +UN_e106_ADAN_Destroyer 10000 300 — 2100 vessel diff --git a/docs/re/live-unit-definitions.md b/docs/re/live-unit-definitions.md index 2f00d32..a1e36d0 100644 --- a/docs/re/live-unit-definitions.md +++ b/docs/re/live-unit-definitions.md @@ -66,8 +66,51 @@ with them. It also gives independent support to the **`Size_Y` inherits `Size_X` default rule — five of the fourteen have `+0x30 == +0x34` exactly (300/300, 400/400, 200/200, 600/600, 700/700) while the rest differ. -❔ **Not yet resolved: which unit each object is.** Nothing in the first 96 words -is an obvious name pointer, so these cannot be matched to their disc records yet -— and that match is what turns the snapshot into defaulted-field *values*. The -next step is to dump further into the object (and the entity that points at it) -looking for the id or name field, which is one more single-call run. +## Identity without a name: match on the values + +Nothing in the first 96 words is a name pointer, so identity comes from the +numbers themselves. `examples/unit_signatures.rs` prints `(HP, Size_X, Size_Y, +Size_Z)` for every craft unit and vessel on the disc; a live object is then the +record whose *known* fields it reproduces exactly. + +Seven of the fourteen match a disc record on all four values — the player's +Delta Saber (`10, 7, 29`, HP 1500 in its player form and 1000 otherwise), the +ArrowHead, an ADAN turret, an Attacker. **The other seven match nothing, and +that is the point:** their disc records leave a field defaulted, so there is +nothing to match against. **18 of the 23 vessel records on disc are missing at +least one of these four fields**, nearly always `Size_Y`. + +## ✅ Route B: 13 defaulted fields read out of the running game + +Matching each disc record that defaults exactly one field against the live object +that reproduces its remaining fields resolves the missing value: + +| unit | field | runtime value | live object (sx, sy, sz, hp) | +|---|---|---|---| +| `UN_e201_ADAN_ISCMissile` | `Size_Y` | **300** | `0xbd3e0680` (300, 300, 2800, 3000) | +| `UN_f106_TCAF_Destroyer` | `Size_Y` | **200** | `0xbd3ee300` (200, 200, 2000, 10000) | +| `UN_f106_TCAF_Destroyer_Inv` | `Size_Y` | **200** | `0xbd3ee300` | +| `UN_e105_ADAN_Cruiser` | `Size_Y` | **600** | `0xbd3fd800` (600, 600, 3800, 30000) | +| `UN_e105_ADAN_CruiserEX` | `Size_Y` | **600** | `0xbd3fd800` | +| `UN_f105_TCAF_Cruiser` | `Size_Y` | **700** | `0xbd40e200` (700, 700, 3800, 30000) | +| `UN_f105_TCAF_Cruiser_EX5` | `Size_Y` | **700** | `0xbd40e200` | +| `UN_f105_TCAF_Cruiser_Inv` | `Size_Y` | **700** | `0xbd40e200` | +| `UN_e108_ADAN_ASFrigate` | `Size_Y` | **80** | `0xbd3d2d80` (80, 80, 350, 4000) | +| `UN_e108_ADAN_ASFrigateEX` | `Size_Y` | **80** | `0xbd3d2d80` | +| `UN_e106_ADAN_Destroyer` | `Size_Y` | **300** | `0xbd3e6f80` (300, 300, 2100, 10000) | +| `UN_e106_ADAN_DestroyerEX` | `Size_Y` | **300** | `0xbd3e6f80` | +| `UN_f101_TCAF_Acropolis` | `Size_Y` | **400** | `0xbd3e1f00` (400, 400, 1400, 25000) | + +**Every resolved value equals that unit's `Size_X`** — so the running game +confirms the `Size_Y` inherits `Size_X` rule that was derived statically from +sibling records, this time from the engine's own parsed definitions. + +Two honest limits. Variants share a live object (`_Inv`, `_EX5`, `EX` map to the +same definition as their base), so the snapshot cannot distinguish them — the +value is right for the class, and per-variant differences in *other* fields are +not addressed. And this run only reaches the units Stage 02 instantiates: the +`…Ratio` / `…Count` family (`HQRatio`, `PowerRatio`, `ShieldRatio`, `HatchCount`, +`NodeCount`, `SequencingCount`, …) is defaulted on 5–23 records each and is +**not** resolved here, because those fields' offsets in the live object are not +yet known. Finding them is the next run: dump deeper than 96 words and look for +the constants the disc *does* set on the records that set them.