paavohtl Claude Opus 4.6 (1M context) commited on
Commit
79f78c4
Β·
1 Parent(s): 8fe7bff

refactor: add DDDisplayVtable, replace inline vtable dispatch

Browse files

Create DDDisplayVtable (38 slots) with typed methods for the 5 slots
used in the DDGame constructor:
- set_layer_color (slot 4)
- set_active_layer (slot 5)
- set_layer_visibility (slot 23)
- load_sprite (slot 31)
- load_sprite_by_layer (slot 37)

Methods take *mut DDDisplay (not &self) to avoid UB from creating
mutable pointers from shared references.

Replaces ~15 inline transmute + vtable[N] patterns with typed calls
like DDDisplay::set_layer_color(disp, 1, 0xFE).

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

crates/openwa-core/src/display/dd_display.rs CHANGED
@@ -1,16 +1,151 @@
1
  /// DDDisplay β€” display/rendering subsystem.
2
  ///
3
  /// Constructor: DDDisplay__Init (0x569D00).
4
- /// Vtable: 0x66A218.
5
  /// Destructor: FUN_00569CE0.
6
  ///
7
  /// Manages display mode, dimensions, palette, and HWND.
8
  /// Contains DrawTextOnBitmap (thiscall) and ConstructTextbox methods.
9
  /// The OpenGL context (HDC, HGLRC) is also stored here when GL mode is active.
10
  ///
11
- /// OPAQUE: Size not yet determined. Only vtable pointer defined.
12
  #[repr(C)]
13
  pub struct DDDisplay {
14
  /// 0x000: Vtable pointer (0x66A218)
15
- pub vtable: *mut u8,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  }
 
1
  /// DDDisplay β€” display/rendering subsystem.
2
  ///
3
  /// Constructor: DDDisplay__Init (0x569D00).
4
+ /// Vtable: 0x66A218 (38 known slots).
5
  /// Destructor: FUN_00569CE0.
6
  ///
7
  /// Manages display mode, dimensions, palette, and HWND.
8
  /// Contains DrawTextOnBitmap (thiscall) and ConstructTextbox methods.
9
  /// The OpenGL context (HDC, HGLRC) is also stored here when GL mode is active.
10
  ///
11
+ /// OPAQUE: Full struct size not yet determined. Only vtable pointer defined.
12
  #[repr(C)]
13
  pub struct DDDisplay {
14
  /// 0x000: Vtable pointer (0x66A218)
15
+ pub vtable: *const DDDisplayVtable,
16
+ }
17
+
18
+ /// DDDisplay vtable (0x66A218, 38 slots).
19
+ ///
20
+ /// Only actively-used slots have typed signatures. Unknown slots are `usize`.
21
+ /// Follows the DSSoundVtable pattern: all slots defined, unknowns as stubs.
22
+ #[repr(C)]
23
+ pub struct DDDisplayVtable {
24
+ /// Slot 0: destructor
25
+ pub _slot_00: usize,
26
+ /// Slot 1
27
+ pub _slot_01: usize,
28
+ /// Slot 2
29
+ pub _slot_02: usize,
30
+ /// Slot 3
31
+ pub _slot_03: usize,
32
+ /// Slot 4: set layer color β€” thiscall(this, layer, color)
33
+ pub set_layer_color: unsafe extern "thiscall" fn(*mut DDDisplay, i32, i32),
34
+ /// Slot 5: set active layer β€” thiscall(this, layer) -> layer context ptr
35
+ pub set_active_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8,
36
+ /// Slot 6
37
+ pub _slot_06: usize,
38
+ /// Slot 7
39
+ pub _slot_07: usize,
40
+ /// Slot 8
41
+ pub _slot_08: usize,
42
+ /// Slot 9
43
+ pub _slot_09: usize,
44
+ /// Slot 10
45
+ pub _slot_10: usize,
46
+ /// Slot 11
47
+ pub _slot_11: usize,
48
+ /// Slot 12
49
+ pub _slot_12: usize,
50
+ /// Slot 13
51
+ pub _slot_13: usize,
52
+ /// Slot 14
53
+ pub _slot_14: usize,
54
+ /// Slot 15
55
+ pub _slot_15: usize,
56
+ /// Slot 16
57
+ pub _slot_16: usize,
58
+ /// Slot 17
59
+ pub _slot_17: usize,
60
+ /// Slot 18
61
+ pub _slot_18: usize,
62
+ /// Slot 19
63
+ pub _slot_19: usize,
64
+ /// Slot 20
65
+ pub _slot_20: usize,
66
+ /// Slot 21
67
+ pub _slot_21: usize,
68
+ /// Slot 22
69
+ pub _slot_22: usize,
70
+ /// Slot 23 (0x5C): set layer visibility β€” thiscall(this, layer, value)
71
+ pub set_layer_visibility: unsafe extern "thiscall" fn(*mut DDDisplay, i32, i32),
72
+ /// Slot 24
73
+ pub _slot_24: usize,
74
+ /// Slot 25
75
+ pub _slot_25: usize,
76
+ /// Slot 26
77
+ pub _slot_26: usize,
78
+ /// Slot 27
79
+ pub _slot_27: usize,
80
+ /// Slot 28
81
+ pub _slot_28: usize,
82
+ /// Slot 29
83
+ pub _slot_29: usize,
84
+ /// Slot 30
85
+ pub _slot_30: usize,
86
+ /// Slot 31 (0x7C): load sprite with flag β€” thiscall(this, layer, id, flag, gfx, name), RET 0x14
87
+ pub load_sprite:
88
+ unsafe extern "thiscall" fn(*mut DDDisplay, u32, u32, u32, *mut u8, *const u8) -> i32,
89
+ /// Slot 32
90
+ pub _slot_32: usize,
91
+ /// Slot 33
92
+ pub _slot_33: usize,
93
+ /// Slot 34
94
+ pub _slot_34: usize,
95
+ /// Slot 35
96
+ pub _slot_35: usize,
97
+ /// Slot 36
98
+ pub _slot_36: usize,
99
+ /// Slot 37 (0x94): load sprite by layer β€” thiscall(this, layer, id, gfx, name), RET 0x10
100
+ pub load_sprite_by_layer:
101
+ unsafe extern "thiscall" fn(*mut DDDisplay, u32, u32, *mut u8, *const u8) -> i32,
102
+ }
103
+
104
+ const _: () = assert!(core::mem::size_of::<DDDisplayVtable>() == 38 * 4);
105
+
106
+ /// Typed wrappers for DDDisplay vtable calls.
107
+ /// All take `*mut DDDisplay` since thiscall passes `this` as mutable.
108
+ impl DDDisplay {
109
+ /// Vtable slot 5: set active display layer, returns layer context pointer.
110
+ #[inline]
111
+ pub unsafe fn set_active_layer(this: *mut Self, layer: i32) -> *mut u8 {
112
+ ((*(*this).vtable).set_active_layer)(this, layer)
113
+ }
114
+
115
+ /// Vtable slot 4: set color for a display layer.
116
+ #[inline]
117
+ pub unsafe fn set_layer_color(this: *mut Self, layer: i32, color: i32) {
118
+ ((*(*this).vtable).set_layer_color)(this, layer, color)
119
+ }
120
+
121
+ /// Vtable slot 23: set layer visibility.
122
+ #[inline]
123
+ pub unsafe fn set_layer_visibility(this: *mut Self, layer: i32, value: i32) {
124
+ ((*(*this).vtable).set_layer_visibility)(this, layer, value)
125
+ }
126
+
127
+ /// Vtable slot 37: load sprite into a layer by name.
128
+ #[inline]
129
+ pub unsafe fn load_sprite_by_layer(
130
+ this: *mut Self,
131
+ layer: u32,
132
+ id: u32,
133
+ gfx: *mut u8,
134
+ name: *const u8,
135
+ ) -> i32 {
136
+ ((*(*this).vtable).load_sprite_by_layer)(this, layer, id, gfx, name)
137
+ }
138
+
139
+ /// Vtable slot 31: load sprite with flag.
140
+ #[inline]
141
+ pub unsafe fn load_sprite(
142
+ this: *mut Self,
143
+ layer: u32,
144
+ id: u32,
145
+ flag: u32,
146
+ gfx: *mut u8,
147
+ name: *const u8,
148
+ ) -> i32 {
149
+ ((*(*this).vtable).load_sprite)(this, layer, id, flag, gfx, name)
150
+ }
151
  }
crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -526,15 +526,10 @@ pub unsafe fn display_layer_color_init(wrapper: *mut DDGameWrapper) {
526
  5 + 0x69 // = 0x6E
527
  };
528
 
529
- let display = (*wrapper).display as *mut u8;
530
- let vt = *(display as *const *const u32);
531
- // vtable[4]: set layer color
532
- let set_color: unsafe extern "thiscall" fn(*mut u8, i32, i32) =
533
- core::mem::transmute(*vt.add(4));
534
-
535
- set_color(display, 1, layer1_color);
536
- set_color(display, 2, 0x20);
537
- set_color(display, 3, 0x70);
538
  }
539
 
540
  /// Initialize runtime addresses for the constructor bridges.
@@ -844,22 +839,18 @@ unsafe fn init_graphics_and_resources(
844
  if *(rb(va::G_DISPLAY_MODE_FLAG) as *const u8) == 0 {
845
  call_usercall_eax(wrapper, FUN_570A90_ADDR);
846
  }
847
- // Read display (may be modified by FUN_00570A90).
848
- let disp = (*wrapper).display as *mut u8;
849
  let gfx_handler = (*wrapper)._field_4c0;
850
- let vt = *(disp as *const *const u32);
851
- // vtable[4]: set palette range
852
- let vt_10: unsafe extern "thiscall" fn(*mut u8, i32, i32) =
853
- core::mem::transmute(*vt.add(4));
854
- vt_10(disp, 1, 0xFE);
855
- // vtable[0x1F]: init palette layer
856
- let vt_7c: unsafe extern "thiscall" fn(*mut u8, i32, i32, i32, *mut u8, u32) =
857
- core::mem::transmute(*vt.add(0x1F));
858
- vt_7c(disp, 1, 1, 0, gfx_handler, rb(va::STR_CDROM_SPR));
859
- // vtable[0x17]: set layer visibility
860
- let vt_5c: unsafe extern "thiscall" fn(*mut u8, i32, i32) =
861
- core::mem::transmute(*vt.add(0x17));
862
- vt_5c(disp, 1, -100);
863
 
864
  // Palette slot range init
865
  let palette_range_ptr = *(disp.add(0x3120) as *const *mut i16);
@@ -883,12 +874,7 @@ unsafe fn init_graphics_and_resources(
883
  // ── Display vtable slot 5 (offset 0x14) ──
884
  // Original: CALL EAX (vtable[5]), saves return value in ESI for use as
885
  // the `output` parameter in the color-entries GfxResource__Create call below.
886
- let layer_ctx = {
887
- let vt = *((*ddgame).display as *const *const u32);
888
- let f: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
889
- core::mem::transmute(*vt.add(5));
890
- f((*ddgame).display, 1)
891
- };
892
 
893
  // ── GfxDir color entries DDGame+0x730C..0x732C ──
894
  if (*wrapper).gfx_mode != 0 {
@@ -1076,11 +1062,7 @@ unsafe fn init_graphics_and_resources(
1076
  name_buf[5] = b'0' + (i / 10) as u8;
1077
  name_buf[6] = b'0' + (i % 10) as u8;
1078
 
1079
- // Display vtable[5](1) β€” set active layer, returns context ptr
1080
- let disp_vt = *((*ddgame).display as *const *const u32);
1081
- let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
1082
- core::mem::transmute(*disp_vt.add(5));
1083
- let layer_ctx = set_layer((*ddgame).display, 1);
1084
 
1085
  let entry = gfx_dir_find_entry(name_buf.as_ptr(), gfx_handler);
1086
 
@@ -1267,37 +1249,16 @@ unsafe fn init_graphics_and_resources(
1267
  0x2F4,
1268
  );
1269
 
1270
- // Display vtable[5](3) β€” set active layer 3, returns context ptr
1271
- let disp_vt = *((*ddgame).display as *const *const u32);
1272
- let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) -> *mut u8 =
1273
- core::mem::transmute(*disp_vt.add(5));
1274
- let _layer3_ctx = set_layer((*ddgame).display, 3);
1275
 
1276
- // Load back.spr and debris.spr (conditional)
1277
- let disp_obj = (*wrapper).display as *mut u8;
1278
- let disp_obj_vt = *(disp_obj as *const *const u32);
1279
- // Check if gfx_mode high byte is set (original: uStack_123c._3_1_ != '\0')
1280
  if (*wrapper).gfx_mode != 0 {
1281
- let load_spr_94: unsafe extern "thiscall" fn(*mut u8, u32, u32, *mut u8, *const u8) =
1282
- core::mem::transmute(*disp_obj_vt.add(0x94 / 4));
1283
- load_spr_94(disp_obj, 3, 0x26D, land_layer, b"back.spr\0".as_ptr());
1284
-
1285
- let load_spr_7c: unsafe extern "thiscall" fn(
1286
- *mut u8,
1287
- u32,
1288
- u32,
1289
- u32,
1290
- *mut u8,
1291
- *const u8,
1292
- ) = core::mem::transmute(*disp_obj_vt.add(0x7C / 4));
1293
- load_spr_7c(disp_obj, 3, 0x26E, 0, land_layer, b"debris.spr\0".as_ptr());
1294
  }
1295
 
1296
- // Load layer.spr into layer 2
1297
- let load_spr_94: unsafe extern "thiscall" fn(*mut u8, u32, u32, *mut u8, *const u8) =
1298
- core::mem::transmute(*disp_obj_vt.add(0x94 / 4));
1299
- load_spr_94(
1300
- disp_obj,
1301
  2,
1302
  0x26C,
1303
  water_layer,
@@ -1307,11 +1268,8 @@ unsafe fn init_graphics_and_resources(
1307
  (*ddgame).gradient_image_2 = core::ptr::null_mut();
1308
 
1309
  // ── Gradient image (0x030) ──
1310
- // The gradient is loaded from "gradient.img" via GfxDir.
1311
- // Simple path: height <= 0x60 AND level_height == 0x2B8
1312
  let level_height = (*ddgame).level_height as i32;
1313
- // Read sVar1 from display layer 3 context
1314
- let layer3_ctx = set_layer((*ddgame).display, 3);
1315
  let s_var1 = *(layer3_ctx.add(0x606) as *const i16);
1316
 
1317
  if s_var1 < 0x61 && level_height == 0x2B8 {
@@ -1325,7 +1283,7 @@ unsafe fn init_graphics_and_resources(
1325
 
1326
  // ── Fill image β†’ fill_pixel (0x7338) ──
1327
  {
1328
- let layer2_ctx = set_layer((*ddgame).display, 2);
1329
  // In the original, fill.img uses piStack_126c which the decompiler
1330
  // shows was set from piVar3 (water_layer from landscape+0xB38).
1331
  let fill_sprite =
@@ -1380,15 +1338,12 @@ unsafe fn init_graphics_and_resources(
1380
  call_usercall_eax(wrapper, FUN_570A90_ADDR);
1381
  }
1382
 
1383
- // ── Final display layer visibility (vtable[0x17], offset 0x5C) ──
1384
  {
1385
- let disp = (*wrapper).display as *mut u8;
1386
- let vt = *(disp as *const *const u32);
1387
- let set_vis: unsafe extern "thiscall" fn(*mut u8, i32, i32) =
1388
- core::mem::transmute(*vt.add(0x17));
1389
- set_vis(disp, 1, 0);
1390
- set_vis(disp, 2, 0);
1391
- set_vis(disp, 3, 1);
1392
  }
1393
 
1394
  let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");
 
526
  5 + 0x69 // = 0x6E
527
  };
528
 
529
+ let display = (*wrapper).display;
530
+ DDDisplay::set_layer_color(display, 1, layer1_color);
531
+ DDDisplay::set_layer_color(display, 2, 0x20);
532
+ DDDisplay::set_layer_color(display, 3, 0x70);
 
 
 
 
 
533
  }
534
 
535
  /// Initialize runtime addresses for the constructor bridges.
 
839
  if *(rb(va::G_DISPLAY_MODE_FLAG) as *const u8) == 0 {
840
  call_usercall_eax(wrapper, FUN_570A90_ADDR);
841
  }
842
+ let disp = (*wrapper).display;
 
843
  let gfx_handler = (*wrapper)._field_4c0;
844
+ DDDisplay::set_layer_color(disp, 1, 0xFE);
845
+ DDDisplay::load_sprite(
846
+ disp,
847
+ 1,
848
+ 1,
849
+ 0,
850
+ gfx_handler,
851
+ rb(va::STR_CDROM_SPR) as *const u8,
852
+ );
853
+ DDDisplay::set_layer_visibility(disp, 1, -100);
 
 
 
854
 
855
  // Palette slot range init
856
  let palette_range_ptr = *(disp.add(0x3120) as *const *mut i16);
 
874
  // ── Display vtable slot 5 (offset 0x14) ──
875
  // Original: CALL EAX (vtable[5]), saves return value in ESI for use as
876
  // the `output` parameter in the color-entries GfxResource__Create call below.
877
+ let layer_ctx = DDDisplay::set_active_layer((*ddgame).display, 1);
 
 
 
 
 
878
 
879
  // ── GfxDir color entries DDGame+0x730C..0x732C ──
880
  if (*wrapper).gfx_mode != 0 {
 
1062
  name_buf[5] = b'0' + (i / 10) as u8;
1063
  name_buf[6] = b'0' + (i % 10) as u8;
1064
 
1065
+ let layer_ctx = DDDisplay::set_active_layer((*ddgame).display, 1);
 
 
 
 
1066
 
1067
  let entry = gfx_dir_find_entry(name_buf.as_ptr(), gfx_handler);
1068
 
 
1249
  0x2F4,
1250
  );
1251
 
1252
+ let disp = (*wrapper).display;
1253
+ DDDisplay::set_active_layer(disp, 3);
 
 
 
1254
 
 
 
 
 
1255
  if (*wrapper).gfx_mode != 0 {
1256
+ DDDisplay::load_sprite_by_layer(disp, 3, 0x26D, land_layer, b"back.spr\0".as_ptr());
1257
+ DDDisplay::load_sprite(disp, 3, 0x26E, 0, land_layer, b"debris.spr\0".as_ptr());
 
 
 
 
 
 
 
 
 
 
 
1258
  }
1259
 
1260
+ DDDisplay::load_sprite_by_layer(
1261
+ disp,
 
 
 
1262
  2,
1263
  0x26C,
1264
  water_layer,
 
1268
  (*ddgame).gradient_image_2 = core::ptr::null_mut();
1269
 
1270
  // ── Gradient image (0x030) ──
 
 
1271
  let level_height = (*ddgame).level_height as i32;
1272
+ let layer3_ctx = DDDisplay::set_active_layer(disp, 3);
 
1273
  let s_var1 = *(layer3_ctx.add(0x606) as *const i16);
1274
 
1275
  if s_var1 < 0x61 && level_height == 0x2B8 {
 
1283
 
1284
  // ── Fill image β†’ fill_pixel (0x7338) ──
1285
  {
1286
+ let layer2_ctx = DDDisplay::set_active_layer((*ddgame).display, 2);
1287
  // In the original, fill.img uses piStack_126c which the decompiler
1288
  // shows was set from piVar3 (water_layer from landscape+0xB38).
1289
  let fill_sprite =
 
1338
  call_usercall_eax(wrapper, FUN_570A90_ADDR);
1339
  }
1340
 
1341
+ // ── Final display layer visibility ──
1342
  {
1343
+ let disp = (*wrapper).display;
1344
+ DDDisplay::set_layer_visibility(disp, 1, 0);
1345
+ DDDisplay::set_layer_visibility(disp, 2, 0);
1346
+ DDDisplay::set_layer_visibility(disp, 3, 1);
 
 
 
1347
  }
1348
 
1349
  let _ = crate::log::log_line("[DDGame] init_graphics_and_resources DONE");