paavohtl Claude Opus 4.6 commited on
Commit
e454eb7
Β·
0 Parent(s):

Initial openwa-types crate: enums, structs, and RE addresses

Browse files

Bootstrap the Rust type foundation for WA 3.8.1 reverse engineering:

- Cargo workspace with openwa-types crate
- Enums translated from wkJellyWorm: ClassType (34), TaskMessage (83+),
Weapon (71), SoundId (126), SpriteId (696)
- Fixed-point math newtype (16.16 format, 0x10000 = 1.0)
- CTask and CGameTask struct stubs with compile-time size assertions
- 80+ known addresses: vtables, constructors, gameplay, rendering, sound
- RE documentation: task hierarchy, vtable layouts, memory map

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

.gitattributes ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ testdata/** filter=lfs diff=lfs merge=lfs -text
2
+ crates/**/tests/fixtures/** filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.gif filter=lfs diff=lfs merge=lfs -text
5
+ *.wsc filter=lfs diff=lfs merge=lfs -text
6
+ *.pal filter=lfs diff=lfs merge=lfs -text
7
+ *.wagame filter=lfs diff=lfs merge=lfs -text
8
+ *.WAgame filter=lfs diff=lfs merge=lfs -text
9
+ crates/*/data/** filter=lfs diff=lfs merge=lfs -text
10
+ testdata/**/*.md !text -filter -merge -diff
11
+ testdata/**/.gitignore !text -filter -merge -diff
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ /target
2
+ .claude/
3
+ .mcp.json
4
+ start-mcp.ps1
Cargo.lock ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "openwa-types"
7
+ version = "0.1.0"
Cargo.toml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [workspace]
2
+ members = ["crates/*"]
3
+ resolver = "2"
crates/openwa-types/Cargo.toml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [package]
2
+ name = "openwa-types"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Type definitions for Worms Armageddon reverse engineering"
crates/openwa-types/src/address.rs ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Known addresses in WA.exe 3.8.1 (image base 0x00400000).
2
+ ///
3
+ /// These addresses are discovered through Ghidra analysis and
4
+ /// cross-referenced with wkJellyWorm/WormKit sources.
5
+ ///
6
+ /// All addresses are virtual addresses (VA) as loaded in memory.
7
+ pub mod va {
8
+ // Segment layout:
9
+ // .text: 0x00401000 - 0x00619FFF (code)
10
+ // .rdata: 0x0061A000 - 0x00693FFF (read-only data)
11
+ // .data: 0x00694000 - 0x008C4157 (read-write data)
12
+ // .rsrc: 0x008C5000 - 0x00954FFF (resources)
13
+ // .reloc: 0x00955000 - 0x00983FFF (relocations)
14
+
15
+ pub const IMAGE_BASE: u32 = 0x0040_0000;
16
+ pub const TEXT_START: u32 = 0x0040_1000;
17
+ pub const TEXT_END: u32 = 0x0061_9FFF;
18
+ pub const RDATA_START: u32 = 0x0061_A000;
19
+ pub const DATA_START: u32 = 0x0069_4000;
20
+
21
+ // === Vtables (in .rdata) ===
22
+
23
+ /// CTask vtable - 7 virtual method pointers
24
+ pub const CTASK_VTABLE: u32 = 0x0066_9F8C;
25
+ /// CGameTask vtable - extends CTask vtable with 12 more methods
26
+ pub const CGAMETASK_VTABLE: u32 = 0x0066_41F8;
27
+ /// CGameTask secondary vtable (at object offset 0xE8)
28
+ pub const CGAMETASK_VTABLE2: u32 = 0x0066_9CF8;
29
+
30
+ // === CTask vtable methods (at CTask__vtable) ===
31
+
32
+ /// CTask::vtable0 - initialization/unknown
33
+ pub const CTASK_VT0_INIT: u32 = 0x0056_2710;
34
+ /// CTask::Free - destructor/deallocation
35
+ pub const CTASK_VT1_FREE: u32 = 0x0056_2620;
36
+ /// CTask::HandleMessage - message dispatch
37
+ pub const CTASK_VT2_HANDLE_MESSAGE: u32 = 0x0056_2F30;
38
+ /// CTask::vtable3 - unknown
39
+ pub const CTASK_VT3: u32 = 0x0056_13D0;
40
+ /// CTask::vtable4 - unknown (same as vt3 in base)
41
+ pub const CTASK_VT4: u32 = 0x0056_13D0;
42
+ /// CTask::vtable5 - unknown
43
+ pub const CTASK_VT5: u32 = 0x0056_2FA0;
44
+ /// CTask::vtable6 - unknown
45
+ pub const CTASK_VT6: u32 = 0x0056_3000;
46
+ /// CTask::vtable7 - ProcessFrame
47
+ pub const CTASK_VT7_PROCESS_FRAME: u32 = 0x0056_3210;
48
+
49
+ // === CGameTask vtable methods (first 8 override CTask, then 12 new) ===
50
+
51
+ /// CGameTask::vtable0 override
52
+ pub const CGAMETASK_VT0: u32 = 0x004F_F1C0;
53
+ /// CGameTask::Free override
54
+ pub const CGAMETASK_VT1_FREE: u32 = 0x004F_EF10;
55
+ /// CGameTask::HandleMessage override
56
+ pub const CGAMETASK_VT2_HANDLE_MESSAGE: u32 = 0x004F_F280;
57
+
58
+ // === Constructors ===
59
+
60
+ /// CTask constructor - initializes base task fields and children list
61
+ pub const CTASK_CONSTRUCTOR: u32 = 0x0056_25A0;
62
+ /// CGameTask constructor - calls CTask ctor, sets physics defaults
63
+ pub const CGAMETASK_CONSTRUCTOR: u32 = 0x004F_ED50;
64
+ /// CTaskWorm constructor
65
+ pub const CTASK_WORM_CONSTRUCTOR: u32 = 0x0050_BFB0;
66
+
67
+ // === Game entity constructors (from wkJellyWorm) ===
68
+
69
+ pub const CTASK_AIRSTRIKE_CTOR: u32 = 0x0055_53C0;
70
+ pub const CTASK_ARROW_CTOR: u32 = 0x004F_E130;
71
+ pub const CTASK_CANISTER_CTOR: u32 = 0x0050_1A80;
72
+ pub const CTASK_CLOUD_CTOR: u32 = 0x0054_82E0;
73
+ pub const CTASK_CPU_CTOR: u32 = 0x0054_85D0;
74
+ pub const CTASK_CRATE_CTOR: u32 = 0x0050_2490;
75
+ pub const CTASK_CROSS_CTOR: u32 = 0x0050_45C0;
76
+ pub const CTASK_DIRT_CTOR: u32 = 0x0054_EDC0;
77
+ pub const CTASK_FILTER_CTOR: u32 = 0x0054_F3D0;
78
+ pub const CTASK_FIRE_CTOR: u32 = 0x0054_F4C0;
79
+ pub const CTASK_FIREBALL_CTOR: u32 = 0x0055_0890;
80
+ pub const CTASK_FLAME_CTOR: u32 = 0x0054_F0F0;
81
+ pub const CTASK_GAS_CTOR: u32 = 0x0055_4750;
82
+ pub const CTASK_LAND_CTOR: u32 = 0x0050_5440;
83
+ pub const CTASK_MINE_CTOR: u32 = 0x0050_6660;
84
+ pub const CTASK_MISSILE_CTOR: u32 = 0x0050_7D10;
85
+ pub const CTASK_OILDRUM_CTOR: u32 = 0x0050_4AF0;
86
+ pub const CTASK_OLDWORM_CTOR: u32 = 0x0051_FEB0;
87
+ pub const CTASK_SCOREBUBBLE_CTOR: u32 = 0x0055_4CA0;
88
+ pub const CTASK_SEABUBBLE_CTOR: u32 = 0x0055_4FE0;
89
+ pub const CTASK_SMOKE_CTOR: u32 = 0x0055_51D0;
90
+ pub const CTASK_SPRITE_ANIM_CTOR: u32 = 0x0054_66C0;
91
+ pub const CTASK_TEAM_CTOR: u32 = 0x0055_5BB0;
92
+ pub const CTASK_TURNGAME_CTOR: u32 = 0x0055_B280;
93
+
94
+ // === Gameplay functions ===
95
+
96
+ pub const CREATE_EXPLOSION: u32 = 0x0054_8080;
97
+ pub const SPECIAL_IMPACT: u32 = 0x0051_93D0;
98
+ pub const SPAWN_OBJECT: u32 = 0x0056_1CF0;
99
+ pub const WEAPON_RELEASE: u32 = 0x0051_C3D0;
100
+ pub const WORM_START_FIRING: u32 = 0x0051_B7F0;
101
+ pub const FIRE_WEAPON: u32 = 0x0051_EE60;
102
+ pub const CREATE_WEAPON_PROJECTILE: u32 = 0x0051_E0F0;
103
+
104
+ // === Weapon system ===
105
+
106
+ pub const INIT_WEAPON_TABLE: u32 = 0x0053_CAB0;
107
+ pub const GET_AMMO: u32 = 0x0052_25E0;
108
+ pub const ADD_AMMO: u32 = 0x0052_2640;
109
+ pub const SUBTRACT_AMMO: u32 = 0x0052_2680;
110
+
111
+ // === Graphics / rendering ===
112
+
113
+ pub const CONSTRUCT_DD_GAME: u32 = 0x0056_E220;
114
+ pub const CONSTRUCT_DD_GAME_WRAPPER: u32 = 0x0056_DEF0;
115
+ pub const CONSTRUCT_DD_DISPLAY: u32 = 0x0056_9D00;
116
+ pub const CONSTRUCT_FRAME_BUFFER: u32 = 0x005A_2430;
117
+ pub const BLIT_SCREEN: u32 = 0x005A_2020;
118
+ pub const RENDER_DRAWING_QUEUE: u32 = 0x0054_2350;
119
+ pub const DRAW_LANDSCAPE: u32 = 0x005A_2790;
120
+ pub const DRAW_SPRITE_GLOBAL: u32 = 0x0054_1FE0;
121
+ pub const DRAW_BITMAP_GLOBAL: u32 = 0x0054_2170;
122
+ pub const LOAD_SPRITE: u32 = 0x0052_3400;
123
+ pub const CONSTRUCT_OPENGL_CPU: u32 = 0x005A_0850;
124
+ pub const OPENGL_INIT: u32 = 0x0059_F000;
125
+
126
+ // === Landscape ===
127
+
128
+ pub const CONSTRUCT_PC_LANDSCAPE: u32 = 0x0057_ACB0;
129
+ pub const REDRAW_LAND_REGION: u32 = 0x0057_CC10;
130
+ pub const WRITE_LAND_RAW: u32 = 0x0057_C300;
131
+
132
+ // === Sound ===
133
+
134
+ pub const CONSTRUCT_DS_SOUND: u32 = 0x0057_3D50;
135
+ pub const PLAY_SOUND_LOCAL: u32 = 0x004F_DFE0;
136
+ pub const PLAY_SOUND_GLOBAL: u32 = 0x0054_6E20;
137
+
138
+ // === Chat / UI ===
139
+
140
+ pub const SHOW_CHAT_MESSAGE: u32 = 0x0052_ACB0;
141
+ pub const ON_CHAT_INPUT: u32 = 0x0052_B730;
142
+
143
+ // === Lobby / network ===
144
+
145
+ pub const LOBBY_HOST_COMMANDS: u32 = 0x004B_9B00;
146
+ pub const LOBBY_CLIENT_COMMANDS: u32 = 0x004A_ABB0;
147
+ pub const SEND_GAME_PACKET_WRAPPED: u32 = 0x0054_1130;
148
+ pub const FRONTEND_CHANGE_SCREEN: u32 = 0x0044_7A20;
149
+
150
+ // === Memory ===
151
+
152
+ pub const WA_MALLOC_MEMSET: u32 = 0x0053_E910;
153
+ pub const WA_FREE: u32 = 0x005D_0D2B;
154
+
155
+ // === Game context (DDGame struct offsets) ===
156
+ // These are offsets from the DDGame base pointer, not absolute addresses.
157
+ // DDGame pointer is obtained from hookConstructDDGameWrapper param.
158
+
159
+ pub mod ddgame_offsets {
160
+ /// Offset to TurnGame object pointer
161
+ pub const TURN_GAME: u32 = 0x08;
162
+ /// Offset to game global state pointer
163
+ pub const GAME_GLOBAL: u32 = 0x488;
164
+ /// Offset to PC_Landscape pointer
165
+ pub const PC_LANDSCAPE: u32 = 0x4CC;
166
+ /// Offset to weapon table pointer
167
+ pub const WEAPON_TABLE: u32 = 0x510;
168
+ /// Offset to weapon panel pointer
169
+ pub const WEAPON_PANEL: u32 = 0x548;
170
+ }
171
+ }
crates/openwa-types/src/class_type.rs ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Task/entity classification type.
2
+ ///
3
+ /// Stored at offset 0x20 in CTask. Used to identify the concrete type
4
+ /// of a task in the task hierarchy.
5
+ ///
6
+ /// Source: wkJellyWorm Constants.h
7
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8
+ #[repr(u32)]
9
+ pub enum ClassType {
10
+ None = 0,
11
+ Task = 1,
12
+ GameTask = 2,
13
+ GameCollisionTask = 3,
14
+ Control = 4,
15
+ Game = 5,
16
+ TurnGame = 6,
17
+ Filter = 7,
18
+ Mine = 8,
19
+ Canister = 9,
20
+ Team = 10,
21
+ Missile = 11,
22
+ Arrow = 12,
23
+ Animation = 13,
24
+ Dirt = 14,
25
+ Crate = 15,
26
+ Flame = 16,
27
+ AirStrike = 17,
28
+ Worm = 18,
29
+ OldWorm = 19,
30
+ Drill = 20,
31
+ Cross = 21,
32
+ Smoke = 22,
33
+ Cloud = 23,
34
+ Fire = 24,
35
+ Gas = 25,
36
+ FireBall = 26,
37
+ SeaBubble = 27,
38
+ Land = 28,
39
+ ScoreBubble = 29,
40
+ OilDrum = 30,
41
+ Cpu = 31,
42
+ SpriteAnimation = 32,
43
+ CollisionManager = 33,
44
+ }
45
+
46
+ impl ClassType {
47
+ pub const MAX: u32 = 34;
48
+ }
49
+
50
+ impl TryFrom<u32> for ClassType {
51
+ type Error = u32;
52
+
53
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
54
+ if value < Self::MAX {
55
+ // SAFETY: all values 0..34 are valid variants
56
+ Ok(unsafe { core::mem::transmute(value) })
57
+ } else {
58
+ Err(value)
59
+ }
60
+ }
61
+ }
crates/openwa-types/src/fixed.rs ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use core::ops::{Add, Sub, Mul, Neg};
2
+
3
+ /// 16.16 fixed-point number used throughout WA for coordinates and velocities.
4
+ ///
5
+ /// The game uses `0x10000` (65536) to represent `1.0`.
6
+ /// For example, a position of `0x30000` means 3.0 world units.
7
+ #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
8
+ #[repr(transparent)]
9
+ pub struct Fixed(pub i32);
10
+
11
+ impl Fixed {
12
+ pub const FRACTIONAL_BITS: u32 = 16;
13
+ pub const SCALE: i32 = 1 << Self::FRACTIONAL_BITS; // 0x10000 = 65536
14
+
15
+ pub const ZERO: Self = Self(0);
16
+ pub const ONE: Self = Self(Self::SCALE);
17
+
18
+ #[inline]
19
+ pub const fn from_raw(raw: i32) -> Self {
20
+ Self(raw)
21
+ }
22
+
23
+ #[inline]
24
+ pub const fn to_raw(self) -> i32 {
25
+ self.0
26
+ }
27
+
28
+ #[inline]
29
+ pub const fn from_int(n: i32) -> Self {
30
+ Self(n << Self::FRACTIONAL_BITS)
31
+ }
32
+
33
+ #[inline]
34
+ pub const fn to_int(self) -> i32 {
35
+ self.0 >> Self::FRACTIONAL_BITS
36
+ }
37
+
38
+ #[inline]
39
+ pub fn from_f32(f: f32) -> Self {
40
+ Self((f * Self::SCALE as f32) as i32)
41
+ }
42
+
43
+ #[inline]
44
+ pub fn to_f32(self) -> f32 {
45
+ self.0 as f32 / Self::SCALE as f32
46
+ }
47
+ }
48
+
49
+ impl Add for Fixed {
50
+ type Output = Self;
51
+ #[inline]
52
+ fn add(self, rhs: Self) -> Self {
53
+ Self(self.0 + rhs.0)
54
+ }
55
+ }
56
+
57
+ impl Sub for Fixed {
58
+ type Output = Self;
59
+ #[inline]
60
+ fn sub(self, rhs: Self) -> Self {
61
+ Self(self.0 - rhs.0)
62
+ }
63
+ }
64
+
65
+ impl Mul for Fixed {
66
+ type Output = Self;
67
+ #[inline]
68
+ fn mul(self, rhs: Self) -> Self {
69
+ // Use i64 to avoid overflow during multiplication
70
+ Self(((self.0 as i64 * rhs.0 as i64) >> Self::FRACTIONAL_BITS) as i32)
71
+ }
72
+ }
73
+
74
+ impl Neg for Fixed {
75
+ type Output = Self;
76
+ #[inline]
77
+ fn neg(self) -> Self {
78
+ Self(-self.0)
79
+ }
80
+ }
81
+
82
+ impl core::fmt::Debug for Fixed {
83
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84
+ write!(f, "Fixed({:.4} raw=0x{:08x})", self.to_f32(), self.0)
85
+ }
86
+ }
87
+
88
+ impl core::fmt::Display for Fixed {
89
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
90
+ write!(f, "{:.4}", self.to_f32())
91
+ }
92
+ }
crates/openwa-types/src/lib.rs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ pub mod class_type;
2
+ pub mod message;
3
+ pub mod weapon;
4
+ pub mod sound;
5
+ pub mod sprite;
6
+ pub mod fixed;
7
+ pub mod task;
8
+ pub mod address;
crates/openwa-types/src/message.rs ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Inter-task message types for the game's event/message passing system.
2
+ ///
3
+ /// Tasks communicate by sending these messages through the hierarchy.
4
+ /// Note: there are gaps in the numbering (10, 24-25, 65-66, 82-83, 87, 95).
5
+ ///
6
+ /// Source: wkJellyWorm Constants.h
7
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8
+ #[repr(u32)]
9
+ pub enum TaskMessage {
10
+ None = 0,
11
+ FrameStart = 1,
12
+ FrameFinish = 2,
13
+ RenderScene = 3,
14
+ ProcessInput = 4,
15
+ UpdateNonCritical = 5,
16
+ MachineFinished = 6,
17
+ CrateCollected = 7,
18
+ StateChecksum = 8,
19
+ MachineReady = 9,
20
+ // gap: 10 is unused
21
+ WormDrowned = 11,
22
+ FrameNumber = 12,
23
+ MachineQuit = 13,
24
+ EnableCheat = 14,
25
+ PlayerChat = 15,
26
+ CameraAuto = 16,
27
+ CursorMoved = 17,
28
+ GirderChanged = 18,
29
+ StrikeChanged = 19,
30
+ TeamVictory = 20,
31
+ GameOver = 21,
32
+ ExitMode = 22,
33
+ Hurry = 23,
34
+ // gap: 24-25 unused
35
+ ThinkingShow = 26,
36
+ ThinkingHide = 27,
37
+ Explosion = 28,
38
+ ExplosionReport = 29,
39
+ MoveLeft = 30,
40
+ MoveRight = 31,
41
+ MoveUp = 32,
42
+ MoveDown = 33,
43
+ FaceLeft = 34,
44
+ FaceRight = 35,
45
+ Jump = 36,
46
+ JumpUp = 37,
47
+ FireWeapon = 38,
48
+ ReleaseWeapon = 39,
49
+ SkipGo = 40,
50
+ Freeze = 41,
51
+ // gap: 42 unused
52
+ Surrender = 43,
53
+ DetonateWeapon = 44,
54
+ MoveWeaponLeft = 45,
55
+ MoveWeaponRight = 46,
56
+ SelectFuse = 47,
57
+ SelectHerd = 48,
58
+ SelectBounce = 49,
59
+ SelectCursor = 50,
60
+ SelectWeapon = 51,
61
+ StartTurn = 52,
62
+ PauseTurn = 53,
63
+ ResumeTurn = 54,
64
+ FinishTurn = 55,
65
+ TurnStarted = 56,
66
+ TurnFinished = 57,
67
+ SuddenDeath = 58,
68
+ DamageWorms = 59,
69
+ RetreatStarted = 60,
70
+ RetreatFinished = 61,
71
+ ApplyPoison = 62,
72
+ SetWorm = 63,
73
+ KillWorm = 64,
74
+ // gap: 65-66 unused
75
+ AdvanceWorm = 67,
76
+ ShowDamage = 68,
77
+ EnableWeapons = 69,
78
+ DisableWeapons = 70,
79
+ WormMoved = 71,
80
+ WormDamaged = 72,
81
+ WeaponReleased = 73,
82
+ WeaponFinished = 74,
83
+ SpecialImpact = 75,
84
+ WeaponCreated = 76,
85
+ WeaponHoming = 77,
86
+ WeaponDestroyed = 78,
87
+ WeaponClaimControl = 79,
88
+ WeaponReleaseControl = 80,
89
+ PoisonWorm = 81,
90
+ // gap: 82-83 unused
91
+ SetWind = 84,
92
+ GameText = 85,
93
+ CreateAnimation = 86,
94
+ // gap: 87 unused
95
+ BringForward = 88,
96
+ RaiseWater = 89,
97
+ NukeBlast = 90,
98
+ Armageddon = 91,
99
+ DetonateCrate = 92,
100
+ Earthquake = 93,
101
+ ScalesOfJustice = 94,
102
+ // gap: 95 unused
103
+ PauseTimer = 96,
104
+ ResumeTimer = 97,
105
+ MoveSpecial = 98,
106
+ StateWrongChecksum = 99,
107
+ UpdateTween = 100,
108
+ ProcessInputTween = 101,
109
+ TimeReceivedObsolete = 102,
110
+ Transferred = 103,
111
+ TransferTimeFreq = 104,
112
+ UpdatePanelTween = 105,
113
+ StateWrongInitChecksum = 106,
114
+ FrameFinishFiller = 107,
115
+ MachineEvent = 108,
116
+ InvalidDataCompressed = 109,
117
+ InvalidDataUncompressed = 110,
118
+ PacketIndexJump = 111,
119
+ CancelPending = 112,
120
+ BulletExplosion = 113,
121
+ FrameNumberWinsock = 114,
122
+ }
123
+
124
+ impl TryFrom<u32> for TaskMessage {
125
+ type Error = u32;
126
+
127
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
128
+ match value {
129
+ 0..=9 | 11..=23 | 26..=41 | 43..=64 | 67..=81 | 84..=86
130
+ | 88..=94 | 96..=114 => {
131
+ // SAFETY: all matched values correspond to valid variants
132
+ Ok(unsafe { core::mem::transmute(value) })
133
+ }
134
+ _ => Err(value),
135
+ }
136
+ }
137
+ }
crates/openwa-types/src/sound.rs ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Sound effect IDs. Range 1-126, contiguous.
2
+ ///
3
+ /// Source: wkJellyWorm Constants.h
4
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5
+ #[repr(u32)]
6
+ pub enum SoundId {
7
+ Morse = 1,
8
+ CrowdPart1 = 2,
9
+ CrowdPart2 = 3,
10
+ NukePart1 = 4,
11
+ NukePart2 = 5,
12
+ FrenchAnthem = 6,
13
+ IndianAnthem = 7,
14
+ Twang1 = 8,
15
+ Twang2 = 9,
16
+ Twang3 = 10,
17
+ Twang4 = 11,
18
+ Twang5 = 12,
19
+ Twang6 = 13,
20
+ Cough1 = 14,
21
+ Cough2 = 15,
22
+ Cough3 = 16,
23
+ Cough4 = 17,
24
+ Cough5 = 18,
25
+ Cough6 = 19,
26
+ DonorCardAppears = 20,
27
+ DonorCardCollect = 21,
28
+ FlameThrowerAttack = 22,
29
+ FlameThrowerLoop = 23,
30
+ Freeze = 24,
31
+ UnFreeze = 25,
32
+ JetPackStart = 26,
33
+ JetPackFinish = 27,
34
+ LongbowImpact = 28,
35
+ LongbowRelease = 29,
36
+ NukeFlash = 30,
37
+ ScalesOfJustice = 31,
38
+ UnderWaterLoop = 32,
39
+ VikingAxeImpact = 33,
40
+ VikingAxeRelease = 34,
41
+ WormLanding = 35,
42
+ JetPackLoop1 = 36,
43
+ JetPackLoop2 = 37,
44
+ MoleBombDiggingLoop = 38,
45
+ MoleBombWalkLoop = 39,
46
+ MoleBombSqueak = 40,
47
+ SkunkGasLoop = 41,
48
+ SkunkWalkLoop = 42,
49
+ SkunkSqueak = 43,
50
+ Armageddon = 44,
51
+ StartRound = 45,
52
+ CameraPan = 46,
53
+ WalkCompress = 47,
54
+ WalkExpand = 48,
55
+ CowMoo = 49,
56
+ SheepBaa = 50,
57
+ PigeonCoo = 51,
58
+ AirStrike = 52,
59
+ BlowTorch = 53,
60
+ WormPop = 54,
61
+ Sizzle = 55,
62
+ SnotPlop = 56,
63
+ Splash = 57,
64
+ Splish = 58,
65
+ Petrol = 59,
66
+ WormBurned = 60,
67
+ SalvationArmy = 61,
68
+ MagicBullet = 62,
69
+ WormSpring = 63,
70
+ VaseSmash = 64,
71
+ OldWoman = 65,
72
+ Fuse = 66,
73
+ Teleport = 67,
74
+ Communicator = 68,
75
+ Explosion1 = 69,
76
+ Explosion2 = 70,
77
+ Explosion3 = 71,
78
+ ThrowPowerup = 72,
79
+ ThrowRelease = 73,
80
+ RocketPowerup = 74,
81
+ RocketRelease = 75,
82
+ SuperSheepRelease = 76,
83
+ SuperSheepWhoosh = 77,
84
+ UziFire = 78,
85
+ MinigunFire = 79,
86
+ ShotgunFire = 80,
87
+ ShotgunReload = 81,
88
+ HandgunFire = 82,
89
+ Ricochet = 83,
90
+ Drill = 84,
91
+ DrillImpact = 85,
92
+ NinjaRopeFire = 86,
93
+ NinjaRopeImpact = 87,
94
+ MineArm = 88,
95
+ MineTick = 89,
96
+ MineDud = 90,
97
+ WeaponHoming = 91,
98
+ PauseTick = 92,
99
+ TimerTick = 93,
100
+ SuddenDeath = 94,
101
+ KamikazeRelease = 95,
102
+ BaseballBatRelease = 96,
103
+ BaseballBatImpact = 97,
104
+ BaseballBatJingle = 98,
105
+ DragonBallRelease = 99,
106
+ DragonBallImpact = 100,
107
+ FirePunchImpact = 101,
108
+ HolyDonkey = 102,
109
+ HolyDonkeyImpact = 103,
110
+ HolyGrenade = 104,
111
+ HolyGrenadeImpact = 105,
112
+ FrozenWormImpact = 106,
113
+ MineImpact = 107,
114
+ WormImpact = 108,
115
+ CrossImpact = 109,
116
+ CrateImpact = 110,
117
+ BananaImpact = 111,
118
+ GirderImpact = 112,
119
+ GrenadeImpact = 113,
120
+ OilDrumImpact = 114,
121
+ CratePop = 115,
122
+ KeyClick = 116,
123
+ KeyErase = 117,
124
+ WormSelect = 118,
125
+ CursorSelect = 119,
126
+ WarningBeep = 120,
127
+ LoadingTick = 121,
128
+ TeamDrop = 122,
129
+ TeamBounce = 123,
130
+ Collect = 124,
131
+ ThrowPowerdown = 125,
132
+ RocketPowerdown = 126,
133
+ }
134
+
135
+ impl SoundId {
136
+ pub const MIN: u32 = 1;
137
+ pub const MAX: u32 = 126;
138
+ }
139
+
140
+ impl TryFrom<u32> for SoundId {
141
+ type Error = u32;
142
+
143
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
144
+ if value >= Self::MIN && value <= Self::MAX {
145
+ Ok(unsafe { core::mem::transmute(value) })
146
+ } else {
147
+ Err(value)
148
+ }
149
+ }
150
+ }
crates/openwa-types/src/sprite.rs ADDED
@@ -0,0 +1,720 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Sprite/animation frame IDs. Range 1-696 with gaps at 620-621.
2
+ ///
3
+ /// Names preserved from the original sprite file names in the game data.
4
+ /// The original names are terse 8-character identifiers from the game's
5
+ /// sprite sheets.
6
+ ///
7
+ /// Source: wkJellyWorm Constants.h
8
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9
+ #[repr(u32)]
10
+ pub enum SpriteId {
11
+ Cdrom = 1,
12
+ CrshairR = 2,
13
+ CrshairB = 3,
14
+ CrshairG = 4,
15
+ CrshairY = 5,
16
+ CrshairP = 6,
17
+ CrshairC = 7,
18
+ SpangleR = 8,
19
+ SpangleB = 9,
20
+ SpangleG = 10,
21
+ SpangleY = 11,
22
+ SpangleP = 12,
23
+ SpangleC = 13,
24
+ Grave1 = 14,
25
+ Grave2 = 15,
26
+ Grave3 = 16,
27
+ Grave4 = 17,
28
+ Grave5 = 18,
29
+ Grave6 = 19,
30
+ ArrowDnR = 20,
31
+ ArrowDnB = 21,
32
+ ArrowDnG = 22,
33
+ ArrowDnY = 23,
34
+ ArrowDnP = 24,
35
+ ArrowDnC = 25,
36
+ ArrowSdR = 26,
37
+ ArrowSdB = 27,
38
+ ArrowSdG = 28,
39
+ ArrowSdY = 29,
40
+ ArrowSdP = 30,
41
+ ArrowSdC = 31,
42
+ CursorR = 32,
43
+ CursorB = 33,
44
+ CursorG = 34,
45
+ CursorY = 35,
46
+ CursorP = 36,
47
+ CursorC = 37,
48
+ MarkerR = 38,
49
+ MarkerB = 39,
50
+ MarkerG = 40,
51
+ MarkerY = 41,
52
+ MarkerP = 42,
53
+ MarkerC = 43,
54
+ CMarker = 44,
55
+ MineOn = 45,
56
+ Arrow = 46,
57
+ MineOff = 47,
58
+ Missile = 48,
59
+ Bullet = 49,
60
+ Grenade = 50,
61
+ Banana = 51,
62
+ Mortar = 52,
63
+ Cluster = 53,
64
+ Clustlet = 54,
65
+ PetrolBm = 55,
66
+ HGrenade = 56,
67
+ Tamborin = 57,
68
+ HMissil1 = 58,
69
+ HMissil2 = 59,
70
+ AirMisl = 60,
71
+ SheepFal = 61,
72
+ Carpet1 = 62,
73
+ Carpet2 = 63,
74
+ SheepBrn = 64,
75
+ SheepLau = 65,
76
+ Meteor = 66,
77
+ Drill = 67,
78
+ RopeTip = 68,
79
+ RopeCuff = 69,
80
+ Phone = 70,
81
+ MbBomb = 71,
82
+ Letter1 = 72,
83
+ Letter2 = 73,
84
+ Vase = 74,
85
+ VaseBit1 = 75,
86
+ VaseBit2 = 76,
87
+ VaseBit3 = 77,
88
+ Dynamite = 78,
89
+ FireHit = 79,
90
+ KamiSmk = 80,
91
+ BaseHit = 81,
92
+ Donkey = 82,
93
+ Fireball = 83,
94
+ Flame1 = 84,
95
+ Flame2 = 85,
96
+ AirJetB = 86,
97
+ AirJetG = 87,
98
+ AirJetO = 88,
99
+ PostBox = 89,
100
+ ShpTruck = 90,
101
+ CptTruck = 91,
102
+ Targets = 92,
103
+ TargetsV = 93,
104
+ TargetW = 94,
105
+ TargetWV = 95,
106
+ WCrate1 = 96,
107
+ MCrate1 = 97,
108
+ UCrate1 = 98,
109
+ WCrate2 = 99,
110
+ MCrate2 = 100,
111
+ UCrate2 = 101,
112
+ Donor = 102,
113
+ WCrateV = 103,
114
+ MCrateV = 104,
115
+ UCrateV = 105,
116
+ Fallout = 106,
117
+ WCrate0 = 107,
118
+ MCrate0 = 108,
119
+ UCrate0 = 109,
120
+ OilDrum1 = 110,
121
+ OilDrum2 = 111,
122
+ OilDrum3 = 112,
123
+ OilDrum4 = 113,
124
+ UziHit = 114,
125
+ UziCase = 115,
126
+ ShotHit = 116,
127
+ ShotCase = 117,
128
+ WormHit1 = 118,
129
+ WormHit2 = 119,
130
+ Petrol1_ = 120,
131
+ Petrol2_ = 121,
132
+ Petrol3_ = 122,
133
+ Petrol4_ = 123,
134
+ Petrol1 = 124,
135
+ Petrol2 = 125,
136
+ Petrol3 = 126,
137
+ Petrol4 = 127,
138
+ Petrol5 = 128,
139
+ Petrol6 = 129,
140
+ Exhaust = 130,
141
+ HExhaust = 131,
142
+ WExhaust = 132,
143
+ QExhaust = 133,
144
+ Feather = 134,
145
+ MExhaust = 135,
146
+ TCloud = 136,
147
+ WWalk = 137,
148
+ WWalkU = 138,
149
+ WWalkD = 139,
150
+ WBlowW = 140,
151
+ WBlowWU = 141,
152
+ WBlowWD = 142,
153
+ WBlowA = 143,
154
+ WBlowAU = 144,
155
+ WBlowAD = 145,
156
+ WBungee = 146,
157
+ WBatRope = 147,
158
+ WRoll = 148,
159
+ WFly1 = 149,
160
+ WFly2 = 150,
161
+ WFly3 = 151,
162
+ LShpWlk = 152,
163
+ SheepWlk = 153,
164
+ SpSheep1 = 154,
165
+ SpSheep2 = 155,
166
+ AquaShp1 = 156,
167
+ AquaShp2 = 157,
168
+ MoleDive = 158,
169
+ MoleDig1 = 159,
170
+ MoleDig2 = 160,
171
+ MoleDig3 = 161,
172
+ Woman = 162,
173
+ Sally = 163,
174
+ CowWalk = 164,
175
+ CowJump = 165,
176
+ MoleWalk = 166,
177
+ WJetFly1 = 167,
178
+ WJetFly2 = 168,
179
+ WJetFly3 = 169,
180
+ WJetFly4 = 170,
181
+ WJetFlmB = 171,
182
+ WJetFlmD = 172,
183
+ Skunk1 = 173,
184
+ Skunk2 = 174,
185
+ PigeonR0 = 175,
186
+ PigeonR1 = 176,
187
+ PigeonR2 = 177,
188
+ PigeonR3 = 178,
189
+ WpBrth = 179,
190
+ WpBrthU = 180,
191
+ WpBrthD = 181,
192
+ WCough = 182,
193
+ WCoughU = 183,
194
+ WCoughD = 184,
195
+ WBrth1 = 185,
196
+ WBrth1U = 186,
197
+ WBrth1D = 187,
198
+ WBrth2 = 188,
199
+ WBrth2U = 189,
200
+ WBrth2D = 190,
201
+ WBlink1 = 191,
202
+ WBlink1U = 192,
203
+ WBlink1D = 193,
204
+ WBlink2 = 194,
205
+ WBlink2U = 195,
206
+ WBlink2D = 196,
207
+ WGlnce1 = 197,
208
+ WGlnce1U = 198,
209
+ WGlnce1D = 199,
210
+ WGlnce2 = 200,
211
+ WGlnce2U = 201,
212
+ WGlnce2D = 202,
213
+ WBgHead = 203,
214
+ WBgHeadU = 204,
215
+ WBgHeadD = 205,
216
+ WFist = 206,
217
+ WFistU = 207,
218
+ WFistD = 208,
219
+ WPeek = 209,
220
+ WPeekU = 210,
221
+ WPeekD = 211,
222
+ WULook = 212,
223
+ WULookU = 213,
224
+ WULookD = 214,
225
+ WOLook = 215,
226
+ WOLookU = 216,
227
+ WOLookD = 217,
228
+ WScarey = 218,
229
+ WScareyU = 219,
230
+ WScareyD = 220,
231
+ WKami1 = 221,
232
+ WKami2 = 222,
233
+ WKami3 = 223,
234
+ WKami4 = 224,
235
+ WKami5 = 225,
236
+ WScrtch = 226,
237
+ WScrtchU = 227,
238
+ WScrtchD = 228,
239
+ WAccuse = 229,
240
+ WAccuseU = 230,
241
+ WAccuseD = 231,
242
+ WTache = 232,
243
+ WTacheU = 233,
244
+ WTacheD = 234,
245
+ WMunch = 235,
246
+ WMunchU = 236,
247
+ WMunchD = 237,
248
+ WLazy = 238,
249
+ WLazyU = 239,
250
+ WLazyD = 240,
251
+ WWinner = 241,
252
+ WSurndr = 242,
253
+ WSurndrU = 243,
254
+ WSurndrD = 244,
255
+ WSvR = 245,
256
+ WSvRU = 246,
257
+ WSvRD = 247,
258
+ WSvB = 248,
259
+ WSvBU = 249,
260
+ WSvBD = 250,
261
+ WSvG = 251,
262
+ WSvGU = 252,
263
+ WSvGD = 253,
264
+ WSvY = 254,
265
+ WSvYU = 255,
266
+ WSvYD = 256,
267
+ WSvP = 257,
268
+ WSvPU = 258,
269
+ WSvPD = 259,
270
+ WSvC = 260,
271
+ WSvCU = 261,
272
+ WSvCD = 262,
273
+ WSkipGo = 263,
274
+ WSkipGoU = 264,
275
+ WSkipGoD = 265,
276
+ WSelect = 266,
277
+ WSelectU = 267,
278
+ WSelectD = 268,
279
+ WJump = 269,
280
+ WJumpU = 270,
281
+ WJumpD = 271,
282
+ WLand1 = 272,
283
+ WLand1U = 273,
284
+ WLand1D = 274,
285
+ WLand2 = 275,
286
+ WLand2U = 276,
287
+ WLand2D = 277,
288
+ WFlyLnk = 278,
289
+ WBackFlp = 279,
290
+ WPncLnk = 280,
291
+ WPncLnkU = 281,
292
+ WPncLnkD = 282,
293
+ WPncTop = 283,
294
+ WPncBak = 284,
295
+ WPncBakU = 285,
296
+ WPncBakD = 286,
297
+ WParaLnk = 287,
298
+ WParaCht = 288,
299
+ WParBak = 289,
300
+ WParBakU = 290,
301
+ WParBakD = 291,
302
+ WTwang = 292,
303
+ WTwangU = 293,
304
+ WTwangD = 294,
305
+ WSldLk1 = 295,
306
+ WSldLk1U = 296,
307
+ WSldLk1D = 297,
308
+ WSldLk2 = 298,
309
+ WSldLk2U = 299,
310
+ WSldLk2D = 300,
311
+ WpBrtLk = 301,
312
+ WpBrtLkU = 302,
313
+ WpBrtLkD = 303,
314
+ WBlowLk = 304,
315
+ WBlowLkU = 305,
316
+ WBlowLkD = 306,
317
+ WHatLnk = 307,
318
+ WHatLnkU = 308,
319
+ WHatLnkD = 309,
320
+ WNukeLk = 310,
321
+ WNukeLkU = 311,
322
+ WNukeLkD = 312,
323
+ WGrnLnk = 313,
324
+ WGrnLnkU = 314,
325
+ WGrnLnkD = 315,
326
+ WClsLnk = 316,
327
+ WClsLnkU = 317,
328
+ WClsLnkD = 318,
329
+ WBanLnk = 319,
330
+ WBanLnkU = 320,
331
+ WBanLnkD = 321,
332
+ WPbmLnk = 322,
333
+ WPbmLnkU = 323,
334
+ WPbmLnkD = 324,
335
+ WHgrLnk = 325,
336
+ WHgrLnkU = 326,
337
+ WHgrLnkD = 327,
338
+ WUtlLnk = 328,
339
+ WUtlLnkU = 329,
340
+ WUtlLnkD = 330,
341
+ WJetLnk = 331,
342
+ WJetLnkU = 332,
343
+ WJetLnkD = 333,
344
+ WDynLnk = 334,
345
+ WDynLnkU = 335,
346
+ WDynLnkD = 336,
347
+ WVasLnk = 337,
348
+ WVasLnkU = 338,
349
+ WVasLnkD = 339,
350
+ WCowLnk = 340,
351
+ WCowLnkU = 341,
352
+ WCowLnkD = 342,
353
+ WShpLnk = 343,
354
+ WShpLnkU = 344,
355
+ WShpLnkD = 345,
356
+ WWmnLnk = 346,
357
+ WWmnLnkU = 347,
358
+ WWmnLnkD = 348,
359
+ WSknLnk = 349,
360
+ WSknLnkU = 350,
361
+ WSknLnkD = 351,
362
+ WMolLnk = 352,
363
+ WMolLnkU = 353,
364
+ WMolLnkD = 354,
365
+ WWmn2Lk = 355,
366
+ WWmn2LkU = 356,
367
+ WWmn2LkD = 357,
368
+ WDrlLnk = 358,
369
+ WDrlLnkU = 359,
370
+ WDrlLnkD = 360,
371
+ WDrill = 361,
372
+ WPrdLnk = 362,
373
+ WPrdLnkU = 363,
374
+ WPrdLnkD = 364,
375
+ WProd = 365,
376
+ WProdU = 366,
377
+ WProdD = 367,
378
+ WPrdBk2 = 368,
379
+ WPrdBk2U = 369,
380
+ WPrdBk2D = 370,
381
+ WKamLnk = 371,
382
+ WKamLnkU = 372,
383
+ WKamLnkD = 373,
384
+ WKamJmp = 374,
385
+ WKamBmr = 375,
386
+ WKamBmrU = 376,
387
+ WKamBmrD = 377,
388
+ WFirBl1 = 378,
389
+ WFirBl1U = 379,
390
+ WFirBl1D = 380,
391
+ WFirBl2 = 381,
392
+ WFirBl2U = 382,
393
+ WFirBl2D = 383,
394
+ WFirAir = 384,
395
+ WJapBak = 385,
396
+ WJapBakU = 386,
397
+ WJapBakD = 387,
398
+ WMinLnk = 388,
399
+ WMinLnkU = 389,
400
+ WMinLnkD = 390,
401
+ WSkpLnk = 391,
402
+ WSkpLnkU = 392,
403
+ WSkpLnkD = 393,
404
+ WSelLnk = 394,
405
+ WSelLnkU = 395,
406
+ WSelLnkD = 396,
407
+ WAxeLnk = 397,
408
+ WAxeLnkU = 398,
409
+ WAxeLnkD = 399,
410
+ WAxeLk2 = 400,
411
+ WAxeLk2U = 401,
412
+ WAxeLk2D = 402,
413
+ WAxeSwn = 403,
414
+ WAxeSwnU = 404,
415
+ WAxeSwnD = 405,
416
+ WAxeBak = 406,
417
+ WAxeBakU = 407,
418
+ WAxeBakD = 408,
419
+ WBndLnk = 409,
420
+ WBndLnkU = 410,
421
+ WBndLnkD = 411,
422
+ WAirLnk = 412,
423
+ WAirLnkU = 413,
424
+ WAirLnkD = 414,
425
+ WAirTlk = 415,
426
+ WAirTlkU = 416,
427
+ WAirTlkD = 417,
428
+ WTelLnk = 418,
429
+ WTelLnkU = 419,
430
+ WTelLnkD = 420,
431
+ WTelTlk = 421,
432
+ WTelTlkU = 422,
433
+ WTelTlkD = 423,
434
+ WTelDsv = 424,
435
+ WTelDsvU = 425,
436
+ WTelDsvD = 426,
437
+ WThrBak = 427,
438
+ WThrBakU = 428,
439
+ WThrBakD = 429,
440
+ WBsbLnk = 430,
441
+ WBsbLnkU = 431,
442
+ WBsbLnkD = 432,
443
+ WBsbBk2 = 433,
444
+ WBsbBk2U = 434,
445
+ WBsbBk2D = 435,
446
+ WBazLnk = 436,
447
+ WBazLnkU = 437,
448
+ WBazLnkD = 438,
449
+ WFlmLnk = 439,
450
+ WFlmLnkU = 440,
451
+ WFlmLnkD = 441,
452
+ WBz2Lnk = 442,
453
+ WBz2LnkU = 443,
454
+ WBz2LnkD = 444,
455
+ WBz3Lnk = 445,
456
+ WBz3LnkU = 446,
457
+ WBz3LnkD = 447,
458
+ WSurLnk = 448,
459
+ WSurLnkU = 449,
460
+ WSurLnkD = 450,
461
+ WSvrLnk = 451,
462
+ WSvrLnkU = 452,
463
+ WSvrLnkD = 453,
464
+ WSvbLnk = 454,
465
+ WSvbLnkU = 455,
466
+ WSvbLnkD = 456,
467
+ WSvgLnk = 457,
468
+ WSvgLnkU = 458,
469
+ WSvgLnkD = 459,
470
+ WSvyLnk = 460,
471
+ WSvyLnkU = 461,
472
+ WSvyLnkD = 462,
473
+ WSvpLnk = 463,
474
+ WSvpLnkU = 464,
475
+ WSvpLnkD = 465,
476
+ WSvcLnk = 466,
477
+ WSvcLnkU = 467,
478
+ WSvcLnkD = 468,
479
+ WIceLnk = 469,
480
+ WIceLnkU = 470,
481
+ WIceLnkD = 471,
482
+ WIceLk2 = 472,
483
+ WIceLk2U = 473,
484
+ WIceLk2D = 474,
485
+ WIceLk3 = 475,
486
+ WIceLk3U = 476,
487
+ WIceLk3D = 477,
488
+ WIceLook = 478,
489
+ WIceBlnk = 479,
490
+ WIceGlnt = 480,
491
+ WIceStnd = 481,
492
+ WBatLnk = 482,
493
+ WBatLnkU = 483,
494
+ WBatLnkD = 484,
495
+ WBatBk2 = 485,
496
+ WBatBk2U = 486,
497
+ WBatBk2D = 487,
498
+ WUziLnk = 488,
499
+ WUziLnkU = 489,
500
+ WUziLnkD = 490,
501
+ WShgLnk = 491,
502
+ WShgLnkU = 492,
503
+ WShgLnkD = 493,
504
+ WBowLnk = 494,
505
+ WBowLnkU = 495,
506
+ WBowLnkD = 496,
507
+ WBowBk2 = 497,
508
+ WBowBk2U = 498,
509
+ WBowBk2D = 499,
510
+ WHgnLnk = 500,
511
+ WHgnLnkU = 501,
512
+ WHgnLnkD = 502,
513
+ WMgnLnk = 503,
514
+ WMgnLnkU = 504,
515
+ WMgnLnkD = 505,
516
+ WDie = 506,
517
+ SkdSmoke = 507,
518
+ WFlyUp = 508,
519
+ WFlyDn = 509,
520
+ WFall = 510,
521
+ WFallDn = 511,
522
+ WPncUp = 512,
523
+ WSlide = 513,
524
+ WSlideU = 514,
525
+ WSlideD = 515,
526
+ WBow = 516,
527
+ WBowU = 517,
528
+ WBowD = 518,
529
+ WBsbAim = 519,
530
+ WBsbAimU = 520,
531
+ WBsbAimD = 521,
532
+ WBsbSwn = 522,
533
+ WBsbSwnU = 523,
534
+ WBsbSwnD = 524,
535
+ WBaz = 525,
536
+ WBazU = 526,
537
+ WBazD = 527,
538
+ WFlm = 528,
539
+ WFlmU = 529,
540
+ WFlmD = 530,
541
+ WBaz2 = 531,
542
+ WBaz2U = 532,
543
+ WBaz2D = 533,
544
+ WBaz3 = 534,
545
+ WBaz3U = 535,
546
+ WBaz3D = 536,
547
+ WBatAim = 537,
548
+ WBatAimU = 538,
549
+ WBatAimD = 539,
550
+ WBatFrd = 540,
551
+ WBatFrdU = 541,
552
+ WBatFrdD = 542,
553
+ WShotG = 543,
554
+ WShotGU = 544,
555
+ WShotGD = 545,
556
+ WShotP = 546,
557
+ WShotPU = 547,
558
+ WShotPD = 548,
559
+ WShotF = 549,
560
+ WShotFU = 550,
561
+ WShotFD = 551,
562
+ WBowP = 552,
563
+ WBowPU = 553,
564
+ WBowPD = 554,
565
+ WBowF = 555,
566
+ WBowFU = 556,
567
+ WBowFD = 557,
568
+ WHandG = 558,
569
+ WHandGU = 559,
570
+ WHandGD = 560,
571
+ WHandF = 561,
572
+ WHandFU = 562,
573
+ WHandFD = 563,
574
+ WUzi = 564,
575
+ WUziU = 565,
576
+ WUziD = 566,
577
+ WUziF = 567,
578
+ WUziFU = 568,
579
+ WUziFD = 569,
580
+ WMini = 570,
581
+ WMiniU = 571,
582
+ WMiniD = 572,
583
+ WMiniF = 573,
584
+ WMiniFU = 574,
585
+ WMiniFD = 575,
586
+ WThrGrn = 576,
587
+ WThrGrnU = 577,
588
+ WThrGrnD = 578,
589
+ WThrCls = 579,
590
+ WThrClsU = 580,
591
+ WThrClsD = 581,
592
+ WThrBan = 582,
593
+ WThrBanU = 583,
594
+ WThrBanD = 584,
595
+ WThrPbm = 585,
596
+ WThrPbmU = 586,
597
+ WThrPbmD = 587,
598
+ WThrHgr = 588,
599
+ WThrHgrU = 589,
600
+ WThrHgrD = 590,
601
+ WThrow = 591,
602
+ WThrowU = 592,
603
+ WThrowD = 593,
604
+ Replay = 594,
605
+ NetBreak = 595,
606
+ ExFoom = 596,
607
+ ExBiff = 597,
608
+ ExPoot = 598,
609
+ ExPow = 599,
610
+ Circle25 = 600,
611
+ Circle50 = 601,
612
+ Circle75 = 602,
613
+ Circl100 = 603,
614
+ Elipse25 = 604,
615
+ Elipse50 = 605,
616
+ Elipse75 = 606,
617
+ Elips100 = 607,
618
+ GassGrn = 608,
619
+ SmkLt25 = 609,
620
+ SmkLt50 = 610,
621
+ SmkLt75 = 611,
622
+ SmkLt100 = 612,
623
+ SmkDrk20 = 613,
624
+ SmkDrk30 = 614,
625
+ SmkDrk40 = 615,
626
+ Clouds = 616,
627
+ CloudM = 617,
628
+ CloudL = 618,
629
+ Blob = 619,
630
+ // gap: 620-621 unused
631
+ Debris = 622,
632
+ Bubble1 = 623,
633
+ Bubble2 = 624,
634
+ Bubble3 = 625,
635
+ Bubble4 = 626,
636
+ WfDrown = 627,
637
+ Grave1V2 = 628,
638
+ Grave2V2 = 629,
639
+ Grave3V2 = 630,
640
+ Grave4V2 = 631,
641
+ Grave5V2 = 632,
642
+ Grave6V2 = 633,
643
+ TargetsV2 = 634,
644
+ TargetWV2 = 635,
645
+ WCrate1V2 = 636,
646
+ MCrate1V2 = 637,
647
+ UCrate1V2 = 638,
648
+ DonorV2 = 639,
649
+ WDrown = 640,
650
+ OilDrum = 641,
651
+ MeteorV2 = 642,
652
+ MineOnV2 = 643,
653
+ MineOffV2 = 644,
654
+ MissileV2 = 645,
655
+ AirMislV2 = 646,
656
+ GrenadeV2 = 647,
657
+ MortarV2 = 648,
658
+ ClusterV2 = 649,
659
+ ClustletV2 = 650,
660
+ BulletV2 = 651,
661
+ ArrowV2 = 652,
662
+ Sheep = 653,
663
+ Carpet1V2 = 654,
664
+ Carpet2V2 = 655,
665
+ SheepFalV2 = 656,
666
+ SheepBrnV2 = 657,
667
+ MbBombV2 = 658,
668
+ DrillV2 = 659,
669
+ WomanV2 = 660,
670
+ SallyV2 = 661,
671
+ SkunkV2 = 662,
672
+ Mole = 663,
673
+ CowWalkV2 = 664,
674
+ PigeonR0V2 = 665,
675
+ PigeonR1V2 = 666,
676
+ PigeonR2V2 = 667,
677
+ PigeonR3V2 = 668,
678
+ BananaV2 = 669,
679
+ PetrolBmV2 = 670,
680
+ TamborinV2 = 671,
681
+ HGrenadeV2 = 672,
682
+ HMissil1V2 = 673,
683
+ HMissil2V2 = 674,
684
+ AquaShp1V2 = 675,
685
+ AquaShp2V2 = 676,
686
+ VaseV2 = 677,
687
+ VaseBit1V2 = 678,
688
+ VaseBit2V2 = 679,
689
+ VaseBit3V2 = 680,
690
+ DynamiteV2 = 681,
691
+ Letter1V2 = 682,
692
+ Letter2V2 = 683,
693
+ DonkeyV2 = 684,
694
+ WBaz4 = 685,
695
+ WBaz4U = 686,
696
+ WBaz4D = 687,
697
+ WBaz5 = 688,
698
+ WBaz5U = 689,
699
+ WBaz5D = 690,
700
+ WBz4Lnk = 691,
701
+ WBz4LnkU = 692,
702
+ WBz4LnkD = 693,
703
+ WBz5Lnk = 694,
704
+ WBz5LnkU = 695,
705
+ WBz5LnkD = 696,
706
+ }
707
+
708
+ impl TryFrom<u32> for SpriteId {
709
+ type Error = u32;
710
+
711
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
712
+ match value {
713
+ 1..=619 | 622..=696 => {
714
+ // SAFETY: all matched values correspond to valid variants
715
+ Ok(unsafe { core::mem::transmute(value) })
716
+ }
717
+ _ => Err(value),
718
+ }
719
+ }
720
+ }
crates/openwa-types/src/task.rs ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::class_type::ClassType;
2
+ use crate::fixed::Fixed;
3
+
4
+ /// A 32-bit pointer as it appears in WA's memory (the game is 32-bit x86).
5
+ pub type Ptr32 = u32;
6
+
7
+ /// Base task class in WA's entity hierarchy.
8
+ ///
9
+ /// All game objects inherit from CTask. Tasks form a tree via parent/children
10
+ /// pointers and communicate through the TaskMessage system.
11
+ ///
12
+ /// PARTIAL: Many fields between known offsets are unknown.
13
+ ///
14
+ /// Source: wkJellyWorm CTask.h
15
+ ///
16
+ /// Vtable at 0x669F8C (8 methods):
17
+ /// 0x00: 0x562710 vtable0 (init?)
18
+ /// 0x04: 0x562620 Free
19
+ /// 0x08: 0x562F30 HandleMessage
20
+ /// 0x0C: 0x5613D0 unknown
21
+ /// 0x10: 0x5613D0 unknown (same as 0x0C)
22
+ /// 0x14: 0x562FA0 unknown
23
+ /// 0x18: 0x563000 unknown
24
+ /// 0x1C: 0x563210 ProcessFrame
25
+ #[repr(C)]
26
+ pub struct CTask {
27
+ /// 0x00: Pointer to virtual method table
28
+ pub vtable: Ptr32,
29
+ /// 0x04: Parent task in the hierarchy
30
+ pub parent: Ptr32,
31
+ /// 0x08: Unknown (set to 0x10 in constructor β€” possibly capacity/flags)
32
+ pub _unknown_08: u32,
33
+ /// 0x0C-0x1F: Children task list (CList<CTask*>)
34
+ /// Internal structure: max_size(4), unk4(4), size(4), data_ptr(4), hash_list(4)
35
+ pub _children_raw: [u8; 20],
36
+ /// 0x20: Task classification type
37
+ pub class_type: ClassType,
38
+ /// 0x24-0x2F: Unknown padding to 0x30
39
+ pub _unknown_24: [u8; 12],
40
+ }
41
+
42
+ const _: () = assert!(core::mem::size_of::<CTask>() == 0x30);
43
+
44
+ /// Game task - extends CTask with physics and gameplay data.
45
+ ///
46
+ /// PARTIAL: Most fields between 0x30-0x83 and 0x98-0xEC are unknown.
47
+ /// Only position and velocity fields have been verified.
48
+ ///
49
+ /// Source: wkJellyWorm CGameTask.h
50
+ ///
51
+ /// Additional vtable (12 methods at offsets 0x1C-0x48 in vtable)
52
+ #[repr(C)]
53
+ pub struct CGameTask {
54
+ /// 0x00-0x2F: Base CTask fields
55
+ pub base: CTask,
56
+ /// 0x30-0x83: Unknown gameplay fields (84 bytes)
57
+ pub _unknown_30: [u8; 0x54],
58
+ /// 0x84: X position in fixed-point
59
+ pub pos_x: Fixed,
60
+ /// 0x88: Y position in fixed-point
61
+ pub pos_y: Fixed,
62
+ /// 0x8C-0x8F: Unknown (4 bytes between pos and speed)
63
+ pub _unknown_8c: [u8; 4],
64
+ /// 0x90: X velocity in fixed-point
65
+ pub speed_x: Fixed,
66
+ /// 0x94: Y velocity in fixed-point
67
+ pub speed_y: Fixed,
68
+ /// 0x98-0xE7: Unknown gameplay fields
69
+ pub _unknown_98: [u8; 0x50],
70
+ /// 0xE8: Secondary vtable pointer (-> 0x669CF8 for CGameTask)
71
+ pub vtable2: Ptr32,
72
+ }
73
+
74
+ const _: () = assert!(core::mem::size_of::<CGameTask>() == 0xEC);
crates/openwa-types/src/weapon.rs ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Weapon types. Contiguous range 0-70.
2
+ ///
3
+ /// Source: wkJellyWorm Constants.h
4
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5
+ #[repr(u32)]
6
+ pub enum Weapon {
7
+ None = 0,
8
+ Bazooka = 1,
9
+ HomingMissile = 2,
10
+ Mortar = 3,
11
+ HomingPigeon = 4,
12
+ SheepLauncher = 5,
13
+ Grenade = 6,
14
+ ClusterBomb = 7,
15
+ BananaBomb = 8,
16
+ BattleAxe = 9,
17
+ Earthquake = 10,
18
+ Shotgun = 11,
19
+ Handgun = 12,
20
+ Uzi = 13,
21
+ Minigun = 14,
22
+ Longbow = 15,
23
+ FirePunch = 16,
24
+ DragonBall = 17,
25
+ Kamikaze = 18,
26
+ SuicideBomber = 19,
27
+ Prod = 20,
28
+ Dynamite = 21,
29
+ Mine = 22,
30
+ Sheep = 23,
31
+ SuperSheep = 24,
32
+ AquaSheep = 25,
33
+ MoleBomb = 26,
34
+ AirStrike = 27,
35
+ NapalmStrike = 28,
36
+ MailStrike = 29,
37
+ MineStrike = 30,
38
+ MoleSquadron = 31,
39
+ BlowTorch = 32,
40
+ PneumaticDrill = 33,
41
+ Girder = 34,
42
+ BaseballBat = 35,
43
+ GirderPack = 36,
44
+ NinjaRope = 37,
45
+ Bungee = 38,
46
+ Parachute = 39,
47
+ Teleport = 40,
48
+ ScalesOfJustice = 41,
49
+ SuperBanana = 42,
50
+ HolyGrenade = 43,
51
+ FlameThrower = 44,
52
+ SalvationArmy = 45,
53
+ MbBomb = 46,
54
+ PetrolBomb = 47,
55
+ Skunk = 48,
56
+ MingVase = 49,
57
+ SheepStrike = 50,
58
+ CarpetBomb = 51,
59
+ MadCow = 52,
60
+ OldWoman = 53,
61
+ Donkey = 54,
62
+ NuclearTest = 55,
63
+ Armageddon = 56,
64
+ SkipGo = 57,
65
+ Surrender = 58,
66
+ SelectWorm = 59,
67
+ Freeze = 60,
68
+ MagicBullet = 61,
69
+ JetPack = 62,
70
+ LowGravity = 63,
71
+ FastWalk = 64,
72
+ LaserSight = 65,
73
+ Invisibility = 66,
74
+ DamageX2 = 67,
75
+ CrateSpy = 68,
76
+ DoubleTurnTime = 69,
77
+ CrateShower = 70,
78
+ }
79
+
80
+ impl Weapon {
81
+ pub const COUNT: u32 = 71;
82
+ }
83
+
84
+ impl TryFrom<u32> for Weapon {
85
+ type Error = u32;
86
+
87
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
88
+ if value < Self::COUNT {
89
+ Ok(unsafe { core::mem::transmute(value) })
90
+ } else {
91
+ Err(value)
92
+ }
93
+ }
94
+ }
docs/plans/2026-03-05-openwa-foundation-design.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenWA Foundation Design
2
+
3
+ ## Goal
4
+
5
+ Build the type foundation for an incremental Rust reimplementation of Worms Armageddon 3.8.1 (Steam). Start with Ghidra-assisted reverse engineering and Rust type definitions, later expand to a WormKit module that replaces subsystems.
6
+
7
+ ## Strategy: Hybrid Bootstrap
8
+
9
+ - **Constants/enums**: Translate immediately from wkJellyWorm's well-documented Constants.h (weapons, messages, sprites, sounds, class types)
10
+ - **Struct layouts**: Explore in Ghidra first, write Rust types only for verified fields, pad unknowns
11
+
12
+ ## Architecture
13
+
14
+ ```
15
+ OpenWA/ (Cargo workspace)
16
+ crates/
17
+ openwa-types/ (no_std-compatible type definitions)
18
+ - Enums: ClassType, TaskMessage, Weapon, SoundId, SpriteId
19
+ - Structs: CTask, CGameTask (partial, Ghidra-verified)
20
+ - Primitives: FixedPoint (16.16 fixed-point math)
21
+ - Addresses: Known function/global addresses
22
+ openwa-wormkit/ (future: WormKit DLL module)
23
+ openwa-render/ (future: modern rendering backend)
24
+ ```
25
+
26
+ ## Key Decisions
27
+
28
+ - `#[repr(C)]` and `#[repr(u32)]` for FFI compatibility from day one
29
+ - Unknown struct fields as `_unknown_XX: [u8; N]` padding β€” honest about what we don't know
30
+ - Fixed-point newtype rather than raw i32 β€” prevents unit confusion
31
+ - No external dependencies in openwa-types (pure Rust, no_std compatible)
32
+
33
+ ## Sources
34
+
35
+ - wkJellyWorm: Task hierarchy, enums, vtable layouts, offset documentation
36
+ - WormKit: Game state pointers, network protocol, fixed-point conventions
37
+ - Ghidra: WA.exe analysis (6,859 functions, image base 0x400000)
docs/re-notes/memory-map.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Memory Map - WA.exe 3.8.1
2
+
3
+ ## Segments
4
+
5
+ | Segment | Start | End | Size | Description |
6
+ |---------|------------|------------|----------|----------------------|
7
+ | Headers | 0x00400000 | 0x00400FFF | 4 KB | PE headers |
8
+ | .text | 0x00401000 | 0x00619FFF | ~2.1 MB | Code |
9
+ | .rdata | 0x0061A000 | 0x00693FFF | ~484 KB | Read-only data, vtables |
10
+ | .data | 0x00694000 | 0x008C4157 | ~2.2 MB | Read-write globals |
11
+ | .rsrc | 0x008C5000 | 0x00954FFF | ~576 KB | Resources |
12
+ | .reloc | 0x00955000 | 0x00983FFF | ~188 KB | Relocations |
13
+
14
+ ## Key Statistics
15
+
16
+ - Total functions identified: 6,859
17
+ - Image base: 0x00400000
18
+ - 32-bit x86 PE, compiled with MSVC
19
+ - Entry point: 0x005D8B6C (CRT startup)
20
+ - Export: SetHostingProxyAddressAndPort @ 0x0058E380
21
+
22
+ ## DDGame structure access
23
+
24
+ The main game state is accessed through the DDGame pointer:
25
+ - DDGame obtained via `ConstructDDGameWrapper` (0x56DEF0) parameter
26
+ - DDGame+0x08 β†’ TurnGame object
27
+ - DDGame+0x488 β†’ Game global state
28
+ - DDGame+0x4CC β†’ PC_Landscape
29
+ - DDGame+0x510 β†’ Weapon table
30
+ - DDGame+0x548 β†’ Weapon panel
31
+
32
+ ## Fixed-point convention
33
+
34
+ All coordinates, velocities, and physics values use 16.16 fixed-point:
35
+ - `0x10000` = 1.0
36
+ - `0x20000` = 2.0
37
+ - `0xCCCC` β‰ˆ 0.8
38
+ - Positions stored as Fixed at CGameTask offsets 0x84 (X) and 0x88 (Y)
39
+ - Velocities stored as Fixed at CGameTask offsets 0x90 (X) and 0x94 (Y)
docs/re-notes/task-hierarchy.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Task Hierarchy - Worms Armageddon 3.8.1
2
+
3
+ ## Overview
4
+
5
+ WA uses a hierarchical task/entity system. All game objects inherit from `CTask`,
6
+ which forms a tree via parent/children pointers. Tasks communicate through a
7
+ message passing system (see `TaskMessage` enum).
8
+
9
+ ## Class Hierarchy
10
+
11
+ ```
12
+ CTask (base) vtable: 0x669F8C ctor: 0x5625A0
13
+ β”œβ”€β”€ CGameTask vtable: 0x6641F8 ctor: 0x4FED50
14
+ β”‚ β”œβ”€β”€ CTaskWorm ctor: 0x50BFB0
15
+ β”‚ β”œβ”€β”€ CTaskOldWorm ctor: 0x51FEB0
16
+ β”‚ β”œβ”€β”€ CTaskMissile ctor: 0x507D10
17
+ β”‚ β”œβ”€β”€ CTaskArrow ctor: 0x4FE130
18
+ β”‚ β”œβ”€β”€ CTaskMine ctor: 0x506660
19
+ β”‚ β”œβ”€β”€ CTaskCanister ctor: 0x501A80
20
+ β”‚ β”œβ”€β”€ CTaskCrate ctor: 0x502490
21
+ β”‚ β”œβ”€β”€ CTaskOilDrum ctor: 0x504AF0
22
+ β”‚ β”œβ”€β”€ CTaskCross ctor: 0x5045C0
23
+ β”‚ └── CTaskLand ctor: 0x505440
24
+ β”œβ”€β”€ CTaskFilter ctor: 0x54F3D0
25
+ β”œβ”€β”€ CTaskTeam ctor: 0x555BB0
26
+ β”œβ”€β”€ CTaskTurnGame ctor: 0x55B280
27
+ β”œβ”€β”€ CTaskAirStrike ctor: 0x5553C0
28
+ β”œβ”€β”€ CTaskDirt ctor: 0x54EDC0
29
+ β”œβ”€β”€ CTaskFlame ctor: 0x54F0F0
30
+ β”œβ”€β”€ CTaskFire ctor: 0x54F4C0
31
+ β”œβ”€β”€ CTaskFireBall ctor: 0x550890
32
+ β”œβ”€β”€ CTaskGas ctor: 0x554750
33
+ β”œβ”€β”€ CTaskSmoke ctor: 0x5551D0
34
+ β”œβ”€β”€ CTaskCloud ctor: 0x5482E0
35
+ β”œβ”€β”€ CTaskSeaBubble ctor: 0x554FE0
36
+ β”œβ”€β”€ CTaskScoreBubble ctor: 0x554CA0
37
+ β”œβ”€β”€ CTaskCPU ctor: 0x5485D0
38
+ └── CTaskSpriteAnimation ctor: 0x5466C0
39
+ ```
40
+
41
+ Note: Not all tasks are confirmed to inherit from CGameTask. The hierarchy
42
+ above needs further verification via Ghidra analysis of each constructor.
43
+
44
+ ## CTask vtable (0x669F8C)
45
+
46
+ | Index | Offset | Address | Name (from wkJellyWorm) |
47
+ |-------|--------|------------|-------------------------|
48
+ | 0 | 0x00 | 0x562710 | vtable0 (init?) |
49
+ | 1 | 0x04 | 0x562620 | Free |
50
+ | 2 | 0x08 | 0x562F30 | HandleMessage |
51
+ | 3 | 0x0C | 0x5613D0 | unknown |
52
+ | 4 | 0x10 | 0x5613D0 | unknown (=vt3) |
53
+ | 5 | 0x14 | 0x562FA0 | unknown |
54
+ | 6 | 0x18 | 0x563000 | unknown |
55
+ | 7 | 0x1C | 0x563210 | ProcessFrame |
56
+
57
+ Note: wkJellyWorm says 7 methods but we see 8 entries. The 8th may be
58
+ the start of CGameTask's extended vtable region, or the count was off by one.
59
+
60
+ ## CGameTask vtable (0x6641F8)
61
+
62
+ First 3 entries override CTask:
63
+
64
+ | Index | Offset | Address | Name |
65
+ |-------|--------|------------|-------------------------|
66
+ | 0 | 0x00 | 0x4FF1C0 | vtable0 override |
67
+ | 1 | 0x04 | 0x4FEF10 | Free override |
68
+ | 2 | 0x08 | 0x4FF280 | HandleMessage override |
69
+ | 3 | 0x0C | 0x5613D0 | (inherited from CTask) |
70
+ | 4 | 0x10 | 0x5613D0 | (inherited from CTask) |
71
+ | 5 | 0x14 | 0x562FA0 | (inherited from CTask) |
72
+ | 6 | 0x18 | 0x563000 | (inherited from CTask) |
73
+ | 7 | 0x1C | 0x4FF720 | CGameTask-specific |
74
+ | 8 | 0x20 | 0x4FFED0 | CGameTask-specific |
75
+ | 9 | 0x24 | 0x500070 | CGameTask-specific |
76
+ | 10 | 0x28 | 0x500080 | CGameTask-specific |
77
+ | 11 | 0x2C | 0x5000E0 | CGameTask-specific |
78
+ | 12 | 0x30 | 0x500380 | CGameTask-specific |
79
+ | 13 | 0x34 | 0x500360 | CGameTask-specific |
80
+ | 14 | 0x38 | 0x4FE060 | CGameTask-specific |
81
+ | 15 | 0x3C | 0x500CC0 | CGameTask-specific |
82
+ | 16 | 0x40 | 0x592A50 | CGameTask-specific |
83
+ | 17 | 0x44 | 0x500090 | CGameTask-specific |
84
+ | 18 | 0x48 | 0x545780 | CGameTask-specific |
85
+ | 19 | 0x4C | 0x4AA060 | CGameTask-specific |
86
+
87
+ CGameTask also has a secondary vtable at object offset 0xE8, pointing to 0x669CF8.
88
+
89
+ ## CTask memory layout (0x30 bytes)
90
+
91
+ ```
92
+ Offset Size Field
93
+ 0x00 4 vtable pointer
94
+ 0x04 4 parent (CTask*)
95
+ 0x08 4 unknown (set to 0x10 in ctor)
96
+ 0x0C 4 children.max_size (init 0)
97
+ 0x10 4 children.unk4 (init 0)
98
+ 0x14 4 children.size
99
+ 0x18 4 children.data_ptr (allocated 0x60 bytes, zeroed 0x40)
100
+ 0x1C 4 children.hash_list
101
+ 0x20 4 class_type (ClassType enum)
102
+ 0x24 12 unknown padding
103
+ ```
104
+
105
+ ## CGameTask memory layout (0xEC bytes)
106
+
107
+ Extends CTask at offset 0x00. Key fields discovered from constructor:
108
+
109
+ ```
110
+ Offset Size Field
111
+ 0x00 0x30 CTask base
112
+ 0x20 4 class_type (set to 3 = GameCollisionTask in ctor)
113
+ 0x2C 4 param_3 from ctor
114
+ 0x30 4 param_3 (weapon id?)
115
+ 0x38 4 param_4
116
+ 0x3C 4 unknown
117
+ ...
118
+ 0x4C 4 = 0x10000 (Fixed 1.0)
119
+ 0x50 4 = 0x100000 (Fixed 16.0)
120
+ 0x54 4 = 0xCCCC (Fixed ~0.8)
121
+ 0x58 4 = 0x10000 (Fixed 1.0)
122
+ 0x5C 4 = 0
123
+ 0x60 4 = 0x10000 (Fixed 1.0)
124
+ 0x64 4 = 0x20000 (Fixed 2.0)
125
+ 0x68 4 = 0xF333 (Fixed ~0.95)
126
+ ...
127
+ 0x84 4 pos_x (Fixed)
128
+ 0x88 4 pos_y (Fixed)
129
+ 0x8C 4 = 0x10000 (Fixed 1.0, scale?)
130
+ 0x90 4 speed_x (Fixed)
131
+ 0x94 4 speed_y (Fixed)
132
+ ...
133
+ 0xCC 4 = 0x10000 (Fixed 1.0)
134
+ 0xD8 1 byte flag (init 0)
135
+ 0xD9 1 byte flag (init 0)
136
+ 0xE0 4 unknown (init 0)
137
+ 0xE4 4 unknown (init 0)
138
+ 0xDC 4 unknown (init 0)
139
+ 0xE8 4 secondary vtable pointer -> 0x669CF8
140
+ ```
141
+
142
+ ## Physics defaults from CGameTask constructor
143
+
144
+ Several fixed-point constants are set in the constructor, likely physics defaults:
145
+ - `0x10000` = 1.0 (appears at many offsets β€” scale factors, gravity?)
146
+ - `0x100000` = 16.0 (mass? terminal velocity?)
147
+ - `0xCCCC` β‰ˆ 0.8 (friction? damping?)
148
+ - `0x20000` = 2.0
149
+ - `0xF333` β‰ˆ 0.95 (air resistance?)
150
+ - `0x3333` β‰ˆ 0.2
151
+ - `0x4CCC` β‰ˆ 0.3
152
+ - `0xFEB8` β‰ˆ 0.995
153
+ - `0x9999` β‰ˆ 0.6
154
+ - `0x3AE1` β‰ˆ 0.23
155
+ - `0xFC000000` = -67108864 (large negative, possibly a boundary?)