paavohtl Claude Opus 4.6 (1M context) commited on
Commit
106f839
·
1 Parent(s): 2559813

feat: map WeaponEntry and WeaponFireParams fields from live memory

Browse files

Dumped full WeaponTable (71 entries) via debug CLI and cross-referenced
with CTaskMissile constructor decompilation. Key findings:

- fire_params[0..94] copied verbatim into CTaskMissile.weapon_data
- Fields are polymorphic: different meanings for missile vs hitscan weapons
- Named 28+ fields: gravity_pct, bounce_pct, friction_pct, missile_type,
blast_radius, sprite_id, collision_radius (Fixed), render_size (Fixed)
- Hitscan damage at fp[8] (Shotgun=25, confirmed by gameplay)
- WeaponEntry header: requires_aiming, shot_count, retreat_time, creates_projectile
- MissileType corrected: 1=Homing, 2=Standard, 3=Sheep (was 2/3/4)

Includes tools/analyze_weapon_table.py for offline dump analysis.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/game/weapon.rs CHANGED
@@ -1,5 +1,7 @@
1
  use core::ffi::c_char;
2
 
 
 
3
  /// Weapon types. Contiguous range 0-70.
4
  ///
5
  /// Source: wkJellyWorm Constants.h
@@ -114,13 +116,22 @@ pub struct WeaponEntry {
114
  pub name2: *const c_char,
115
  /// +0x08: Panel state (init: 0xFFFFFFFF). wkJellyWorm calls this `panelRow`.
116
  pub panel_state: i32,
117
- /// +0x0C: Unknown.
118
- pub _unknown_0c: i32,
 
119
  /// +0x10: Weapon defined flag. Nonzero = weapon exists in table.
120
  /// Checked by DDGame__CheckWeaponAvail to determine if weapon is valid.
121
  pub defined: i32,
122
- /// +0x14-0x23: Unknown.
123
- pub _unknown_14: [u8; 0x24 - 0x14],
 
 
 
 
 
 
 
 
124
  /// +0x24: Availability flag. Init: 0xFFFFFFFF, then set to 0 (unavailable)
125
  /// or 1 (available) per weapon. Weapon::None, SkipGo, Surrender default to 0.
126
  pub availability: i32,
@@ -141,14 +152,105 @@ pub struct WeaponEntry {
141
  }
142
  const _: () = assert!(core::mem::size_of::<WeaponEntry>() == 0x1D0);
143
 
144
- /// Weapon fire parameters — embedded at WeaponEntry+0x3C.
145
  ///
146
  /// Pointer to this struct is passed to all fire dispatch sub-functions.
147
- /// Internal layout is mostly unknown (0x194 bytes).
 
 
 
 
 
 
 
148
  #[repr(C)]
149
  pub struct WeaponFireParams {
150
- /// +0x00 (WeaponEntry+0x3C): First param DWORD.
151
- pub _data: [u8; 0x1D0 - 0x3C],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
  const _: () = assert!(core::mem::size_of::<WeaponFireParams>() == 0x1D0 - 0x3C);
154
 
 
1
  use core::ffi::c_char;
2
 
3
+ use crate::fixed::Fixed;
4
+
5
  /// Weapon types. Contiguous range 0-70.
6
  ///
7
  /// Source: wkJellyWorm Constants.h
 
116
  pub name2: *const c_char,
117
  /// +0x08: Panel state (init: 0xFFFFFFFF). wkJellyWorm calls this `panelRow`.
118
  pub panel_state: i32,
119
+ /// +0x0C: Requires aiming (1 for aimed weapons like Bazooka, 0 for non-aimed like Earthquake).
120
+ /// Runtime-observed: 1 for Bazooka/Grenade/Shotgun, 0 for AirStrike/HomingMissile/Teleport.
121
+ pub requires_aiming: i32,
122
  /// +0x10: Weapon defined flag. Nonzero = weapon exists in table.
123
  /// Checked by DDGame__CheckWeaponAvail to determine if weapon is valid.
124
  pub defined: i32,
125
+ /// +0x14: Shot count per use (1 for most, 2 for Shotgun/Longbow, 5 for GirderPack).
126
+ pub shot_count: i32,
127
+ /// +0x18: Unknown flag (0 or 1). 0 for NinjaRope/Bungee/Parachute/SelectWorm/JetPack/powerups.
128
+ pub _unknown_18: i32,
129
+ /// +0x1C: Retreat timer in ms (0xBB8=3000 for most, 0x1388=5000 for Dynamite/Mine/MingVase,
130
+ /// -1 for non-retreating, -1000 for PneumaticDrill/Teleport, 0 for utility/powerups).
131
+ pub retreat_time: i32,
132
+ /// +0x20: Creates projectile flag (1 for weapons that fire a physical object, 0 for utility).
133
+ /// 0 for Prod/Girder/NinjaRope/Parachute/Teleport/utility weapons.
134
+ pub creates_projectile: i32,
135
  /// +0x24: Availability flag. Init: 0xFFFFFFFF, then set to 0 (unavailable)
136
  /// or 1 (available) per weapon. Weapon::None, SkipGo, Surrender default to 0.
137
  pub availability: i32,
 
152
  }
153
  const _: () = assert!(core::mem::size_of::<WeaponEntry>() == 0x1D0);
154
 
155
+ /// Weapon fire parameters — embedded at WeaponEntry+0x3C (0x194 = 404 bytes, 101 DWORDs).
156
  ///
157
  /// Pointer to this struct is passed to all fire dispatch sub-functions.
158
+ /// The first 94 DWORDs (0x178 bytes) are copied verbatim into CTaskMissile.weapon_data
159
+ /// by CTaskMissile__Constructor. The remaining 7 DWORDs are WeaponEntry-only metadata.
160
+ ///
161
+ /// For single-shot projectiles, CTaskMissile.render_data[N] = weapon_data[N+3].
162
+ /// For cluster sub-pellets, render_data[N] = weapon_data[N+52].
163
+ ///
164
+ /// Field names confirmed by cross-referencing live memory dumps (debug CLI),
165
+ /// CTaskMissile constructor decompilation, and render_data physics usage.
166
  #[repr(C)]
167
  pub struct WeaponFireParams {
168
+ /// [0] +0x3C: Polymorphic.
169
+ /// Missile: pellet count (Bazooka=2, Mortar=2).
170
+ /// Hitscan: shots per trigger (Shotgun=1, Handgun=6, Uzi=10, Minigun=20).
171
+ /// Airstrike: number of projectiles (AirStrike=5).
172
+ pub shot_count: i32,
173
+ /// [1] +0x40: Polymorphic.
174
+ /// Missile: spread angle for multi-pellet (Mortar=100).
175
+ /// Hitscan: spread cone (Shotgun=500, Handgun=500).
176
+ /// Airstrike: spacing (NapalmStrike=48).
177
+ pub spread: i32,
178
+ /// [2] +0x44: Polymorphic.
179
+ /// Missile: cluster flag (Grenade=1, ClusterBomb=1, BananaBomb=1).
180
+ /// Hitscan: fire delay between shots (Handgun=10, Uzi=15, Minigun=25).
181
+ pub _fp_02: i32,
182
+ /// [3] +0x48: Polymorphic.
183
+ /// Missile: collision radius (Bazooka=2.10, HomingPigeon=66.1).
184
+ /// Hitscan: always Fixed(1) — flag.
185
+ pub collision_radius: Fixed,
186
+ /// [4] +0x4C: Missile only: unknown (HomingMissile=5, HomingPigeon=25, Grenade=10).
187
+ pub _fp_04: i32,
188
+ /// [5] +0x50: Polymorphic.
189
+ /// Missile: explosion damage (Bazooka=100, Grenade=100, Mortar=0).
190
+ /// Hitscan: max range (all=66.1 as Fixed16.16).
191
+ pub _fp_05: i32,
192
+ /// [6] +0x54: Polymorphic.
193
+ /// Missile: blast radius (Bazooka=50, Mortar=15, Grenade=50).
194
+ /// Hitscan: impact radius (Shotgun=5, Minigun=20).
195
+ pub _fp_06: i32,
196
+ /// [7] +0x58: Hitscan only: unknown (Shotgun=100, Handgun=50, Uzi/Minigun=100).
197
+ pub _fp_07: i32,
198
+ /// [8] +0x5C: Hitscan only: damage per hit (Shotgun=25, Handgun/Uzi/Minigun=5).
199
+ pub _fp_08: i32,
200
+ /// [9] +0x60: Missile only: sprite/animation ID (Bazooka=48, Grenade=50, HomingPigeon=175).
201
+ pub sprite_id: i32,
202
+ /// [10] +0x64: Shot type/impact type (Missile: Bazooka=2, Grenade=1. Hitscan: Shotgun=2, Uzi=5).
203
+ pub impact_type: i32,
204
+ /// [11] +0x68: Polymorphic.
205
+ /// Missile: unknown (Bazooka=131).
206
+ /// Hitscan: max range in pixels (all=32767).
207
+ pub _fp_11: i32,
208
+ /// [12] +0x6C: Trail effect (Bazooka=50, Mortar=50, most weapons=0).
209
+ pub trail_effect: i32,
210
+ /// [13] +0x70: Gravity percentage (100=normal, 0=no gravity).
211
+ /// → render_data[0x0C] → CGameTask.gravity_factor.
212
+ pub gravity_pct: i32,
213
+ /// [14] +0x74: Wind influence (Bazooka=50, AquaSheep=200).
214
+ pub wind_influence: i32,
215
+ /// [15] +0x78: Bounce percentage (100=normal elastic, 0=no bounce).
216
+ /// → render_data[0x0D] → CGameTask.bounce_factor.
217
+ pub bounce_pct: i32,
218
+ /// [16] +0x7C: Unknown (Bazooka=100, most=0).
219
+ pub _fp_16: i32,
220
+ /// [17] +0x80: Unknown (AirStrike=10, MoleSquadron=50).
221
+ pub _fp_17: i32,
222
+ /// [18] +0x84: Friction percentage (100=normal, 0=no friction).
223
+ /// → render_data[0x0F] → CGameTask.friction_factor.
224
+ pub friction_pct: i32,
225
+ /// [19] +0x88: Explosion delay/fuse timer (Grenade=5000, Dynamite=5000).
226
+ pub explosion_delay: i32,
227
+ /// [20] +0x8C: Fuse timer (Bazooka=9000, HomingMissile=10000, SheepLauncher=20000).
228
+ pub fuse_timer: i32,
229
+ /// [21-25] +0x90-0xA0: Various parameters. Remaining primary fields.
230
+ pub _fp_21_25: [i32; 5],
231
+ /// [26] +0xA4: Missile type discriminator.
232
+ /// 0=None, 1=Homing, 2=Standard, 3=Sheep, 5=SheepLauncher/Cluster.
233
+ /// → render_data[0x17] → CTaskMissile behavior dispatch.
234
+ pub missile_type: i32,
235
+ /// [27] +0xA8: Render size. Bazooka=64.0, Grenade≈66.1.
236
+ pub render_size: Fixed,
237
+ /// [28] +0xAC: Render timer/fuse (Bazooka=1, HomingMissile=58, HomingPigeon=175).
238
+ pub render_timer: i32,
239
+ /// [29-33] +0xB0-0xC0: Homing parameters (only nonzero for homing weapons).
240
+ /// [29]=homing_strength, [30]=homing_sprite, [31]=homing_turn_rate,
241
+ /// [32]=homing_accel, [33]=homing_speed.
242
+ pub homing_params: [i32; 5],
243
+ /// [34-36] +0xC4-0xCC: Additional parameters.
244
+ pub _fp_34_36: [i32; 3],
245
+ /// [37-51] +0xD0-0x10C: Reserved / weapon-specific extended params.
246
+ pub _fp_37_51: [i32; 15],
247
+ /// [52-93] +0x10C-0x1B0: Cluster sub-pellet parameters (mirrors primary [0-41]).
248
+ /// When pellet_index > 0, render_data copies from here instead.
249
+ pub cluster_params: [i32; 42],
250
+ /// [94-100] +0x1B0-0x1CC: WeaponEntry-only metadata (NOT copied to CTaskMissile).
251
+ /// [94]+0x1C8: Power percentage (Bazooka=100, Shotgun=10, NinjaRope=100).
252
+ /// [95]+0x1CC: Unknown (Bazooka=100, Grenade=70, Shotgun=20).
253
+ pub entry_metadata: [i32; 7],
254
  }
255
  const _: () = assert!(core::mem::size_of::<WeaponFireParams>() == 0x1D0 - 0x3C);
256
 
tools/analyze_weapon_table.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Analyze a raw weapon table dump from the debug CLI.
3
+
4
+ Usage:
5
+ python tools/analyze_weapon_table.py /tmp/weapon_table.bin
6
+
7
+ Reads 71 x 0x1D0 byte entries and prints field analysis.
8
+ """
9
+
10
+ import struct
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ ENTRY_SIZE = 0x1D0
15
+ ENTRY_COUNT = 71
16
+ FIRE_PARAMS_OFFSET = 0x3C
17
+
18
+ WEAPON_NAMES = [
19
+ "None", "Bazooka", "HomingMissile", "Mortar", "HomingPigeon",
20
+ "SheepLauncher", "Grenade", "ClusterBomb", "BananaBomb", "BattleAxe",
21
+ "Earthquake", "Shotgun", "Handgun", "Uzi", "Minigun",
22
+ "Longbow", "FirePunch", "DragonBall", "Kamikaze", "SuicideBomber",
23
+ "Prod", "Dynamite", "Mine", "Sheep", "SuperSheep",
24
+ "AquaSheep", "MoleBomb", "AirStrike", "NapalmStrike", "MailStrike",
25
+ "MineStrike", "MoleSquadron", "BlowTorch", "PneumaticDrill", "Girder",
26
+ "BaseballBat", "GirderPack", "NinjaRope", "Bungee", "Parachute",
27
+ "Teleport", "ScalesOfJustice", "SuperBanana", "HolyGrenade", "FlameThrower",
28
+ "SalvationArmy", "MbBomb", "PetrolBomb", "Skunk", "MingVase",
29
+ "SheepStrike", "CarpetBomb", "MadCow", "OldWoman", "Donkey",
30
+ "NuclearTest", "Armageddon", "SkipGo", "Surrender", "SelectWorm",
31
+ "Freeze", "MagicBullet", "JetPack", "LowGravity", "FastWalk",
32
+ "LaserSight", "Invisibility", "DamageX2", "CrateSpy", "DoubleTurnTime",
33
+ "CrateShower",
34
+ ]
35
+
36
+ FIRE_TYPE_NAMES = {0: "none", 1: "projectile", 2: "rope", 3: "grenade", 4: "special"}
37
+
38
+ # Known WeaponEntry fields (offset -> name)
39
+ KNOWN_FIELDS = {
40
+ 0x00: "name1_ptr",
41
+ 0x04: "name2_ptr",
42
+ 0x08: "panel_state",
43
+ 0x0C: "_unknown_0c",
44
+ 0x10: "defined",
45
+ 0x24: "availability",
46
+ 0x28: "enabled",
47
+ 0x30: "fire_type",
48
+ 0x34: "fire_subtype_34",
49
+ 0x38: "fire_subtype_38",
50
+ }
51
+
52
+
53
+ def read_entries(data: bytes) -> list[bytes]:
54
+ assert len(data) == ENTRY_SIZE * ENTRY_COUNT, f"Expected {ENTRY_SIZE * ENTRY_COUNT} bytes, got {len(data)}"
55
+ return [data[i * ENTRY_SIZE:(i + 1) * ENTRY_SIZE] for i in range(ENTRY_COUNT)]
56
+
57
+
58
+ def dword_at(entry: bytes, offset: int) -> int:
59
+ return struct.unpack_from("<I", entry, offset)[0]
60
+
61
+
62
+ def sdword_at(entry: bytes, offset: int) -> int:
63
+ return struct.unpack_from("<i", entry, offset)[0]
64
+
65
+
66
+ def print_header_fields(entries: list[bytes]):
67
+ """Print known header fields (0x00-0x3B) for all weapons."""
68
+ print("=" * 80)
69
+ print("WEAPON ENTRY HEADER (0x00-0x3B)")
70
+ print("=" * 80)
71
+
72
+ fmt = "{:<4} {:<16} {:>4} {:>4} {:>4} {:>12} {:>4} {:>4} {:>4} {:>4}"
73
+ print(fmt.format("ID", "Name", "panl", "unk0C", "def", "avail", "enab", "ftyp", "sub34", "sub38"))
74
+ print("-" * 80)
75
+
76
+ for i, entry in enumerate(entries):
77
+ name = WEAPON_NAMES[i]
78
+ panel = sdword_at(entry, 0x08)
79
+ unk0c = sdword_at(entry, 0x0C)
80
+ defined = sdword_at(entry, 0x10)
81
+ avail = sdword_at(entry, 0x24)
82
+ enabled = sdword_at(entry, 0x28)
83
+ ftype = sdword_at(entry, 0x30)
84
+ sub34 = sdword_at(entry, 0x34)
85
+ sub38 = sdword_at(entry, 0x38)
86
+
87
+ type_name = FIRE_TYPE_NAMES.get(ftype, f"?{ftype}")
88
+ print(f"{i:<4} {name:<16} {panel:>4} {unk0c:>4} {defined:>4} {avail:>12} {enabled:>4} {type_name:<11} {sub34:>4} {sub38:>4}")
89
+
90
+
91
+ def print_unknown_header_region(entries: list[bytes]):
92
+ """Print the unknown 0x14-0x23 region."""
93
+ print("\n" + "=" * 80)
94
+ print("UNKNOWN HEADER REGION (0x14-0x23)")
95
+ print("=" * 80)
96
+
97
+ for i, entry in enumerate(entries):
98
+ vals = [dword_at(entry, off) for off in range(0x14, 0x24, 4)]
99
+ if any(v != 0 for v in vals):
100
+ print(f"{i:<4} {WEAPON_NAMES[i]:<16} " + " ".join(f"{v:08X}" for v in vals))
101
+ print("(only non-zero entries shown)")
102
+
103
+
104
+ def analyze_fire_params(entries: list[bytes]):
105
+ """Analyze fire_params region (0x3C-0x1CF) across all weapons."""
106
+ print("\n" + "=" * 80)
107
+ print("FIRE PARAMS ANALYSIS (0x3C-0x1CF, 0x194 bytes = 101 DWORDs)")
108
+ print("=" * 80)
109
+
110
+ num_dwords = (ENTRY_SIZE - FIRE_PARAMS_OFFSET) // 4
111
+
112
+ # For each DWORD offset, collect all values
113
+ interesting = []
114
+ all_zero = []
115
+
116
+ for di in range(num_dwords):
117
+ byte_off = FIRE_PARAMS_OFFSET + di * 4
118
+ values = [dword_at(entry, byte_off) for entry in entries]
119
+ unique = set(values)
120
+ nonzero = [v for v in values if v != 0]
121
+
122
+ if len(unique) == 1 and values[0] == 0:
123
+ all_zero.append(di)
124
+ elif len(unique) <= 3:
125
+ interesting.append((di, byte_off, values, "few_values"))
126
+ else:
127
+ interesting.append((di, byte_off, values, "varied"))
128
+
129
+ print(f"\nAll-zero DWORDs ({len(all_zero)} of {num_dwords}): indices {all_zero[:20]}{'...' if len(all_zero) > 20 else ''}")
130
+ print(f"Non-trivial DWORDs: {len(interesting)}")
131
+
132
+ # Print interesting fields grouped by pattern
133
+ print("\n--- Fields with few distinct values (likely flags/enums) ---")
134
+ for di, byte_off, values, kind in interesting:
135
+ if kind != "few_values":
136
+ continue
137
+ unique = sorted(set(values))
138
+ # Which weapons have each value?
139
+ val_weapons = {}
140
+ for i, v in enumerate(values):
141
+ val_weapons.setdefault(v, []).append(i)
142
+
143
+ print(f"\n +0x{byte_off:03X} (fire_params[{di - FIRE_PARAMS_OFFSET // 4}]):")
144
+ for v in unique:
145
+ weapons = val_weapons[v]
146
+ names = [WEAPON_NAMES[w] for w in weapons[:8]]
147
+ suffix = f" +{len(weapons)-8} more" if len(weapons) > 8 else ""
148
+ print(f" {v:>10} ({v:#010x}): {', '.join(names)}{suffix}")
149
+
150
+ print("\n--- Fields with many distinct values (likely parameters) ---")
151
+ print(f"{'Offset':<10} {'Min':>10} {'Max':>10} {'Unique':>6} Sample values (Bazooka, Grenade, Shotgun, NinjaRope)")
152
+ print("-" * 90)
153
+
154
+ for di, byte_off, values, kind in interesting:
155
+ if kind != "varied":
156
+ continue
157
+ unique_count = len(set(values))
158
+ min_v = min(values)
159
+ max_v = max(values)
160
+ samples = [values[1], values[6], values[11], values[37]] # Bazooka, Grenade, Shotgun, NinjaRope
161
+
162
+ sample_str = ", ".join(f"{v:>8}" for v in samples)
163
+ print(f"+0x{byte_off:03X} {min_v:>10} {max_v:>10} {unique_count:>6} [{sample_str}]")
164
+
165
+
166
+ def print_fire_params_by_type(entries: list[bytes]):
167
+ """Group weapons by fire_type and print fire_params for each group."""
168
+ groups: dict[int, list[int]] = {}
169
+ for i, entry in enumerate(entries):
170
+ ftype = sdword_at(entry, 0x30)
171
+ groups.setdefault(ftype, []).append(i)
172
+
173
+ print("\n" + "=" * 80)
174
+ print("FIRE PARAMS BY WEAPON TYPE")
175
+ print("=" * 80)
176
+
177
+ for ftype in sorted(groups.keys()):
178
+ if ftype == 0:
179
+ continue
180
+ type_name = FIRE_TYPE_NAMES.get(ftype, f"type_{ftype}")
181
+ weapon_ids = groups[ftype]
182
+ print(f"\n--- Type {ftype} ({type_name}): {len(weapon_ids)} weapons ---")
183
+ print(f"Weapons: {', '.join(WEAPON_NAMES[w] for w in weapon_ids)}")
184
+
185
+ # Find fields that vary within this type group
186
+ num_dwords = (ENTRY_SIZE - FIRE_PARAMS_OFFSET) // 4
187
+ for di in range(min(num_dwords, 40)): # First 40 DWORDs for readability
188
+ byte_off = FIRE_PARAMS_OFFSET + di * 4
189
+ values = {w: dword_at(entries[w], byte_off) for w in weapon_ids}
190
+ unique = set(values.values())
191
+ if len(unique) > 1 or (len(unique) == 1 and list(unique)[0] != 0):
192
+ val_str = ", ".join(f"{WEAPON_NAMES[w][:8]}={values[w]}" for w in weapon_ids[:6])
193
+ print(f" +0x{byte_off:03X} fp[{di}]: {val_str}")
194
+
195
+
196
+ def main():
197
+ if len(sys.argv) < 2:
198
+ print(f"Usage: {sys.argv[0]} <weapon_table.bin>")
199
+ sys.exit(1)
200
+
201
+ data = Path(sys.argv[1]).read_bytes()
202
+ entries = read_entries(data)
203
+
204
+ print_header_fields(entries)
205
+ print_unknown_header_region(entries)
206
+ analyze_fire_params(entries)
207
+ print_fire_params_by_type(entries)
208
+
209
+
210
+ if __name__ == "__main__":
211
+ main()