paavohtl Claude Opus 4.6 (1M context) commited on
Commit
934d6af
·
1 Parent(s): 5724790

fix: replace all inline asm push+call with naked bridges

Browse files

Root cause of GfxDir crashes: inline asm `push {param}; call {fn}`
with `clobber_abi("C")` breaks the compiler's stack tracking when
the callee does `RET N` (cleans the pushed param). The compiler
doesn't know the stack was adjusted, corrupting subsequent accesses.

Fixed by creating naked bridge functions:
- call_gfx_find_entry: usercall(EAX=name) + 1 stack, RET 0x4
- call_gfx_load_image: usercall(ESI=handler) + 1 stack, RET 0x4
- Rewrote call_gfx_load_and_wrap and call_gfx_find_and_load to use
these bridges instead of inline asm

Also fixed GfxDir__LoadImage convention: ESI=gfx_handler (not EAX).

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -374,6 +374,8 @@ pub fn init_constructor_addrs() {
374
  FUN_570E20_ADDR = rb(va::FUN_570E20);
375
  FUN_570A90_ADDR = rb(va::FUN_570A90);
376
  GFX_RESOURCE_CREATE_ADDR = rb(va::GFX_RESOURCE_CREATE);
 
 
377
  FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
378
  LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
379
  LOAD_WEAPON_SPRITES_ADDR = rb(va::DDGAME_LOAD_WEAPON_SPRITES);
@@ -861,10 +863,8 @@ unsafe fn init_graphics_and_resources(
861
  release(gfx_resource);
862
  }
863
 
864
- // Resolved addresses for GfxDir operations (used by arrows, gradients, fill)
865
- let gfx_find_entry = rb(va::GFX_DIR_FIND_ENTRY) as u32;
866
- let gfx_load_image = rb(va::GFX_DIR_LOAD_IMAGE) as u32;
867
- let fun_4f5f80 = rb(0x4F5F80) as u32;
868
 
869
  // ── Arrow sprites + collision regions (32 iterations) ──
870
  {
@@ -884,18 +884,8 @@ unsafe fn init_graphics_and_resources(
884
  set_layer((*ddgame).display, 1);
885
  }
886
 
887
- // GfxDir__FindEntry: usercall(EAX=name) + stdcall(gfx_handler), RET 0x4
888
- let entry: *mut u8;
889
- core::arch::asm!(
890
- "push {handler}",
891
- "call {find}",
892
- handler = in(reg) gfx_handler,
893
- find = in(reg) gfx_find_entry,
894
- inlateout("eax") name_buf.as_ptr() => entry,
895
- out("ecx") _,
896
- out("edx") _,
897
- clobber_abi("C"),
898
- );
899
 
900
  let sprite: *mut u8;
901
  if !entry.is_null() {
@@ -907,14 +897,13 @@ unsafe fn init_graphics_and_resources(
907
  if !cached.is_null() {
908
  sprite = cached;
909
  } else {
910
- // Fallback to GfxDir__LoadImage
911
  sprite = call_gfx_load_and_wrap(
912
- name_buf.as_ptr(), gfx_load_image, fun_4f5f80,
913
  );
914
  }
915
  } else {
916
  sprite = call_gfx_load_and_wrap(
917
- name_buf.as_ptr(), gfx_load_image, fun_4f5f80,
918
  );
919
  }
920
 
@@ -1062,9 +1051,7 @@ unsafe fn init_graphics_and_resources(
1062
  if s_var1 < 0x61 && level_height == 0x2B8 {
1063
  // Simple gradient: load gradient.img directly
1064
  let gradient = call_gfx_find_and_load(
1065
- land_layer, b"gradient.img\0".as_ptr(),
1066
- gfx_find_entry, gfx_load_image, fun_4f5f80,
1067
- layer3_ctx,
1068
  );
1069
  (*ddgame).gradient_image = gradient;
1070
  } else {
@@ -1078,9 +1065,7 @@ unsafe fn init_graphics_and_resources(
1078
  {
1079
  let layer2_ctx = set_layer((*ddgame).display, 2);
1080
  let fill_sprite = call_gfx_find_and_load(
1081
- land_layer, b"fill.img\0".as_ptr(),
1082
- gfx_find_entry, gfx_load_image, fun_4f5f80,
1083
- layer2_ctx,
1084
  );
1085
  if !fill_sprite.is_null() {
1086
  // Get pixel value: fill_sprite->vtable[4](0, 0)
@@ -1144,23 +1129,9 @@ unsafe fn init_graphics_and_resources(
1144
  unsafe fn call_gfx_find_and_load(
1145
  gfx_dir: *mut u8,
1146
  name: *const u8,
1147
- find_entry_addr: u32,
1148
- load_image_addr: u32,
1149
- wrap_addr: u32,
1150
  display_ctx: *mut u8,
1151
  ) -> *mut u8 {
1152
- // GfxDir__FindEntry: usercall(EAX=name) + stdcall(gfx_dir), RET 0x4
1153
- let entry: *mut u8;
1154
- core::arch::asm!(
1155
- "push {dir}",
1156
- "call {find}",
1157
- dir = in(reg) gfx_dir,
1158
- find = in(reg) find_entry_addr,
1159
- inlateout("eax") name => entry,
1160
- out("ecx") _,
1161
- out("edx") _,
1162
- clobber_abi("C"),
1163
- );
1164
 
1165
  if !entry.is_null() {
1166
  // Try cached load: gfx_dir->vtable[2](entry->field_4)
@@ -1169,24 +1140,16 @@ unsafe fn call_gfx_find_and_load(
1169
  core::mem::transmute(*dir_vt.add(2));
1170
  let cached = load_cached(gfx_dir, *(entry.add(4) as *const u32));
1171
  if !cached.is_null() {
1172
- // Wrap with DisplayGfx constructor
1173
- let result: *mut u8;
1174
- core::arch::asm!(
1175
- "push {cached}",
1176
- "call {ctor}",
1177
- cached = in(reg) cached,
1178
- ctor = in(reg) rb(0x4F5E80) as u32, // DisplayGfx__Constructor_Maybe
1179
- out("eax") result,
1180
- out("ecx") _,
1181
- out("edx") _,
1182
- clobber_abi("C"),
1183
- );
1184
- return result;
1185
  }
1186
  }
1187
 
1188
  // Fallback: load image directly
1189
- call_gfx_load_and_wrap(name, load_image_addr, wrap_addr)
1190
  }
1191
 
1192
  // Statics for usercall bridge addresses
@@ -1253,6 +1216,25 @@ unsafe extern "C" fn call_gfx_resource_create(
1253
  );
1254
  }
1255
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1256
  /// Bridge to GfxHandler__LoadDir (0x5663E0).
1257
  /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
1258
  #[cfg(target_arch = "x86")]
@@ -1269,45 +1251,41 @@ unsafe fn call_gfx_load_dir(handler: *mut u8, addr: u32) -> i32 {
1269
  result
1270
  }
1271
 
1272
- /// Helper: load image via GfxDir__LoadImage + wrap with FUN_004F5F80.
1273
- /// Used by arrow sprite loop when GfxDir__FindEntry fails.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1274
  #[cfg(target_arch = "x86")]
1275
  unsafe fn call_gfx_load_and_wrap(
 
1276
  name: *const u8,
1277
- gfx_load_image_addr: u32,
1278
- fun_4f5f80: u32,
1279
  ) -> *mut u8 {
1280
- // GfxDir__LoadImage: usercall(EAX=name?), stdcall(1 stack param), RET 0x4
1281
- let image: *mut u8;
1282
- core::arch::asm!(
1283
- "push {name}",
1284
- "call {load}",
1285
- name = in(reg) name,
1286
- load = in(reg) gfx_load_image_addr,
1287
- out("eax") image,
1288
- out("ecx") _,
1289
- out("edx") _,
1290
- clobber_abi("C"),
1291
- );
1292
  if image.is_null() {
1293
  return core::ptr::null_mut();
1294
  }
1295
- // FUN_004F5F80(display_gfx_context, image, 1)
1296
- let result: *mut u8;
1297
- core::arch::asm!(
1298
- "push 1",
1299
- "push {img}",
1300
- "push {ctx}",
1301
- "call {wrap}",
1302
- "add esp, 12",
1303
- img = in(reg) image,
1304
- ctx = in(reg) 0u32, // display_gfx context, often 0
1305
- wrap = in(reg) fun_4f5f80,
1306
- out("eax") result,
1307
- out("ecx") _,
1308
- out("edx") _,
1309
- clobber_abi("C"),
1310
- );
1311
  // Release the raw image
1312
  let image_vt = *(image as *const *const u32);
1313
  let destroy: unsafe extern "thiscall" fn(*mut u8, u32) =
 
374
  FUN_570E20_ADDR = rb(va::FUN_570E20);
375
  FUN_570A90_ADDR = rb(va::FUN_570A90);
376
  GFX_RESOURCE_CREATE_ADDR = rb(va::GFX_RESOURCE_CREATE);
377
+ GFX_FIND_ENTRY_ADDR = rb(va::GFX_DIR_FIND_ENTRY);
378
+ GFX_LOAD_IMAGE_ADDR = rb(va::GFX_DIR_LOAD_IMAGE);
379
  FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
380
  LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
381
  LOAD_WEAPON_SPRITES_ADDR = rb(va::DDGAME_LOAD_WEAPON_SPRITES);
 
863
  release(gfx_resource);
864
  }
865
 
866
+ // GfxDir addresses are resolved via statics (GFX_FIND_ENTRY_ADDR, GFX_LOAD_IMAGE_ADDR)
867
+ // and called through naked bridges (call_gfx_find_entry, call_gfx_load_image).
 
 
868
 
869
  // ── Arrow sprites + collision regions (32 iterations) ──
870
  {
 
884
  set_layer((*ddgame).display, 1);
885
  }
886
 
887
+ // GfxDir__FindEntry: usercall(EAX=name) + 1 stack(gfx_handler), RET 0x4
888
+ let entry = call_gfx_find_entry(name_buf.as_ptr(), gfx_handler);
 
 
 
 
 
 
 
 
 
 
889
 
890
  let sprite: *mut u8;
891
  if !entry.is_null() {
 
897
  if !cached.is_null() {
898
  sprite = cached;
899
  } else {
 
900
  sprite = call_gfx_load_and_wrap(
901
+ gfx_handler, name_buf.as_ptr(), core::ptr::null_mut(),
902
  );
903
  }
904
  } else {
905
  sprite = call_gfx_load_and_wrap(
906
+ gfx_handler, name_buf.as_ptr(), core::ptr::null_mut(),
907
  );
908
  }
909
 
 
1051
  if s_var1 < 0x61 && level_height == 0x2B8 {
1052
  // Simple gradient: load gradient.img directly
1053
  let gradient = call_gfx_find_and_load(
1054
+ land_layer, b"gradient.img\0".as_ptr(), layer3_ctx,
 
 
1055
  );
1056
  (*ddgame).gradient_image = gradient;
1057
  } else {
 
1065
  {
1066
  let layer2_ctx = set_layer((*ddgame).display, 2);
1067
  let fill_sprite = call_gfx_find_and_load(
1068
+ land_layer, b"fill.img\0".as_ptr(), layer2_ctx,
 
 
1069
  );
1070
  if !fill_sprite.is_null() {
1071
  // Get pixel value: fill_sprite->vtable[4](0, 0)
 
1129
  unsafe fn call_gfx_find_and_load(
1130
  gfx_dir: *mut u8,
1131
  name: *const u8,
 
 
 
1132
  display_ctx: *mut u8,
1133
  ) -> *mut u8 {
1134
+ let entry = call_gfx_find_entry(name, gfx_dir);
 
 
 
 
 
 
 
 
 
 
 
1135
 
1136
  if !entry.is_null() {
1137
  // Try cached load: gfx_dir->vtable[2](entry->field_4)
 
1140
  core::mem::transmute(*dir_vt.add(2));
1141
  let cached = load_cached(gfx_dir, *(entry.add(4) as *const u32));
1142
  if !cached.is_null() {
1143
+ // Wrap with DisplayGfx__Constructor_Maybe (0x4F5E80)
1144
+ // This is stdcall(1 param), RET 0x4
1145
+ let ctor: unsafe extern "stdcall" fn(*mut u8) -> *mut u8 =
1146
+ core::mem::transmute(rb(0x4F5E80) as usize);
1147
+ return ctor(cached);
 
 
 
 
 
 
 
 
1148
  }
1149
  }
1150
 
1151
  // Fallback: load image directly
1152
+ call_gfx_load_and_wrap(gfx_dir, name, display_ctx)
1153
  }
1154
 
1155
  // Statics for usercall bridge addresses
 
1216
  );
1217
  }
1218
 
1219
+ /// Runtime address of GfxDir__FindEntry (set by init_constructor_addrs()).
1220
+ static mut GFX_FIND_ENTRY_ADDR: u32 = 0;
1221
+
1222
+ /// Bridge to GfxDir__FindEntry (0x566520).
1223
+ /// Convention: usercall(EAX=name) + 1 stack(gfx_handler), RET 0x4.
1224
+ /// Returns entry pointer or null.
1225
+ #[cfg(target_arch = "x86")]
1226
+ #[unsafe(naked)]
1227
+ unsafe extern "C" fn call_gfx_find_entry(_name: *const u8, _gfx_handler: *mut u8) -> *mut u8 {
1228
+ core::arch::naked_asm!(
1229
+ "movl 4(%esp), %eax", // EAX = name
1230
+ "pushl 8(%esp)", // push gfx_handler (callee cleans via RET 0x4)
1231
+ "calll *({addr})",
1232
+ "retl", // caller cleans our 2 cdecl params
1233
+ addr = sym GFX_FIND_ENTRY_ADDR,
1234
+ options(att_syntax),
1235
+ );
1236
+ }
1237
+
1238
  /// Bridge to GfxHandler__LoadDir (0x5663E0).
1239
  /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
1240
  #[cfg(target_arch = "x86")]
 
1251
  result
1252
  }
1253
 
1254
+ // GfxDir__LoadImage is usercall(ESI=gfx_handler) + 1 stack(name), RET 0x4.
1255
+ // Returns raw image pointer or null.
1256
+ static mut GFX_LOAD_IMAGE_ADDR: u32 = 0;
1257
+
1258
+ #[cfg(target_arch = "x86")]
1259
+ #[unsafe(naked)]
1260
+ unsafe extern "C" fn call_gfx_load_image(_gfx_handler: *mut u8, _name: *const u8) -> *mut u8 {
1261
+ core::arch::naked_asm!(
1262
+ "pushl %esi",
1263
+ "movl 8(%esp), %esi", // ESI = gfx_handler
1264
+ "pushl 12(%esp)", // push name (callee cleans via RET 0x4)
1265
+ "calll *({addr})",
1266
+ "popl %esi",
1267
+ "retl",
1268
+ addr = sym GFX_LOAD_IMAGE_ADDR,
1269
+ options(att_syntax),
1270
+ );
1271
+ }
1272
+
1273
+ /// Helper: load image via GfxDir__LoadImage + wrap as DisplayGfx.
1274
+ /// Used by arrow sprite loop when GfxDir__FindEntry returns null.
1275
  #[cfg(target_arch = "x86")]
1276
  unsafe fn call_gfx_load_and_wrap(
1277
+ gfx_handler: *mut u8,
1278
  name: *const u8,
1279
+ display_ctx: *mut u8,
 
1280
  ) -> *mut u8 {
1281
+ let image = call_gfx_load_image(gfx_handler, name);
 
 
 
 
 
 
 
 
 
 
 
1282
  if image.is_null() {
1283
  return core::ptr::null_mut();
1284
  }
1285
+ // FUN_004F5F80(display_ctx, image, 1) — cdecl, 3 params
1286
+ let f: unsafe extern "cdecl" fn(*mut u8, *mut u8, u32) -> *mut u8 =
1287
+ core::mem::transmute(rb(0x4F5F80) as usize);
1288
+ let result = f(display_ctx, image, 1);
 
 
 
 
 
 
 
 
 
 
 
 
1289
  // Release the raw image
1290
  let image_vt = *(image as *const *const u32);
1291
  let destroy: unsafe extern "thiscall" fn(*mut u8, u32) =