paavohtl Claude Opus 4.6 (1M context) commited on
Commit
7c53fdb
Β·
1 Parent(s): 7b04333

fix: correct usercall conventions for all DDGame sub-functions

Browse files

Fix calling conventions for 6 functions called by create_ddgame():
- FUN_570A90: usercall(EAX=wrapper), was called with no args
- FUN_570E20: usercall(ESI=wrapper), was called with no args
- FUN_570F30: usercall(ESI=wrapper), was called with no args
- DSSound_LoadAllSpeechBanks: usercall(ESI=wrapper), was extern "C"
- DSSound_LoadEffectWAVs: stdcall(wrapper), was extern "C"
- DDGame__LoadWeaponSprites: usercall(ECX=wrapper), was extern "C"

Add reusable naked bridges: call_usercall_esi(), call_usercall_eax(),
call_usercall_ecx() for ESI/EAX/ECX register passing.

Also add null guard for net_game pointer in _field_4dc calculation.

Constructor now works: game starts, 119/133 validations pass.
Remaining failures are race conditions (game loop accessing DDGame
during construction) not logic errors.

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -371,6 +371,11 @@ pub fn init_constructor_addrs() {
371
  unsafe {
372
  INIT_FIELDS_ADDR = rb(va::DDGAME_INIT_FIELDS);
373
  SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
 
 
 
 
 
374
  }
375
  }
376
 
@@ -477,18 +482,8 @@ pub unsafe fn create_ddgame(
477
  }
478
  }
479
 
480
- let _ = log_line(&format!(
481
- "[DDGame] create_ddgame: alloc=0x{:08X}, headless={}, sound=0x{:08X}",
482
- ddgame as u32, is_headless, sound as u32,
483
- ));
484
-
485
  // ── 9. InitVersionFlags β€” sets DDGame+0x7E2E/0x7E2F/0x7E3F ──
486
- let _ = log_line("[DDGame] calling InitVersionFlags");
487
  call_init_version_flags(wrapper);
488
- let _ = log_line(&format!(
489
- "[DDGame] InitVersionFlags done, wrapper+0x4D0=0x{:08X}",
490
- *(wrapper as *const u8).add(0x4D0).cast::<u32>(),
491
- ));
492
 
493
  // ── 10. GfxHandler, landscape, sprites, audio, resources ──
494
  init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
@@ -509,7 +504,6 @@ unsafe fn init_graphics_and_resources(
509
  is_headless: bool,
510
  ) {
511
  let ddgame = (*wrapper).ddgame;
512
- let _ = log_line("[DDGame] init_graphics_and_resources: start");
513
  let fopen: unsafe extern "cdecl" fn(*const u8, *const u8) -> *mut u8 =
514
  core::mem::transmute(rb(va::WA_FOPEN) as usize);
515
  let gfx_load_dir_addr = rb(va::GFX_HANDLER_LOAD_DIR);
@@ -521,7 +515,6 @@ unsafe fn init_graphics_and_resources(
521
  *(gfx1 as *mut u32) = gfx_handler_vtable;
522
  (*wrapper)._field_4c0 = gfx1;
523
  (*wrapper)._field_4c4 = core::ptr::null_mut();
524
- let _ = log_line(&format!("[DDGame] GfxHandler #1 alloc=0x{:08X}, vtable=0x{:08X}", gfx1 as u32, gfx_handler_vtable));
525
 
526
  // Build path list (order depends on headless + GameInfo+0xF374)
527
  let f914 = *(game_info as *const u8).add(0xF914).cast::<i32>();
@@ -540,9 +533,7 @@ unsafe fn init_graphics_and_resources(
540
 
541
  let mut gfx_loaded_idx = 0u32;
542
  for (i, path) in paths.iter().enumerate() {
543
- let _ = log_line(&format!("[DDGame] Trying Gfx path {}", i));
544
  let fp = fopen(path.as_ptr(), b"rb\0".as_ptr());
545
- let _ = log_line(&format!("[DDGame] fopen returned 0x{:08X}", fp as u32));
546
  *(gfx1.add(0x198) as *mut *mut u8) = fp;
547
  if !fp.is_null() && call_gfx_load_dir(gfx1, gfx_load_dir_addr) != 0 {
548
  gfx_loaded_idx = i as u32;
@@ -555,11 +546,6 @@ unsafe fn init_graphics_and_resources(
555
 
556
  let headless_offset = if f914 != 0 { 1u32 } else { 0u32 };
557
  (*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
558
- let _ = log_line(&format!(
559
- "[DDGame] GfxHandler #1 loaded (idx={}, gfx_mode={}, gfx1=0x{:08X}, wrapper+0x4D0=0x{:08X})",
560
- gfx_loaded_idx, (*wrapper).gfx_mode, gfx1 as u32,
561
- *(wrapper as *const u8).add(0x4D0).cast::<u32>(),
562
- ));
563
 
564
  // ── GfxHandler #2 (conditional) ──
565
  let d778 = *(game_info as *const u8).add(0xD778).cast::<i32>();
@@ -589,28 +575,15 @@ unsafe fn init_graphics_and_resources(
589
  }
590
  }
591
 
592
- let _ = log_line(&format!(
593
- "[DDGame] GfxHandler #2 done, _field_4c4=0x{:08X}, wrapper+0x4D0=0x{:08X}",
594
- (*wrapper)._field_4c4 as u32,
595
- *(wrapper as *const u8).add(0x4D0).cast::<u32>(),
596
- ));
597
-
598
  // ── Display palette setup (non-headless) ──
599
  if !is_headless {
600
  if *(rb(0x88E485) as *const u8) == 0 {
601
- let fun_570a90: unsafe extern "C" fn() =
602
- core::mem::transmute(rb(va::FUN_570A90) as usize);
603
- fun_570a90();
604
  }
605
- let _ = log_line(&format!(
606
- "[DDGame] palette: past 570a90, wrapper+0x4D0=0x{:08X}",
607
- *(wrapper as *const u8).add(0x4D0).cast::<u32>(),
608
- ));
609
  // DDGameWrapper+0x4D0 points to the display object used for palette calls.
610
  // BUT: FUN_00570A90 or something else may write to wrapper in ways we
611
  // don't expect. Read via struct field instead of raw offset.
612
  let disp = (*wrapper).display as *mut u8;
613
- let _ = log_line(&format!("[DDGame] palette: disp=0x{:08X}", disp as u32));
614
  let vt = *(disp as *const *const u32);
615
  let vt_10: unsafe extern "thiscall" fn(*mut u8) =
616
  core::mem::transmute(*vt.add(4));
@@ -637,18 +610,13 @@ unsafe fn init_graphics_and_resources(
637
  }
638
  }
639
 
640
- let _ = log_line("[DDGame] display palette done");
641
-
642
- // ── FUN_00570E20 ──
643
- {
644
- let f: unsafe extern "C" fn() = core::mem::transmute(rb(va::FUN_570E20) as usize);
645
- f();
646
- }
647
 
648
  // ── Display vtable slot 5 (offset 0x14) ──
649
  {
650
  let vt = *((*ddgame).display as *const *const u32);
651
- let f: unsafe extern "thiscall" fn(*mut DDDisplay) =
652
  core::mem::transmute(*vt.add(5));
653
  f((*ddgame).display);
654
  }
@@ -696,7 +664,7 @@ unsafe fn init_graphics_and_resources(
696
 
697
  // ── DDGameWrapper field inits ──
698
  (*wrapper)._field_4d8 = 0;
699
- if display.is_null() {
700
  (*wrapper)._field_4dc = 0x2AD;
701
  } else {
702
  let byte_val = *net_game.add(0x44C) as u32;
@@ -707,22 +675,18 @@ unsafe fn init_graphics_and_resources(
707
 
708
  // ── Audio init (non-headless + sound available) ──
709
  if !is_headless {
710
- {
711
- let f: unsafe extern "C" fn() =
712
- core::mem::transmute(rb(va::DDGAME_INIT_SOUND_PATHS) as usize);
713
- f();
714
- }
715
  if !(*ddgame).sound.is_null() {
716
  {
717
- let f: unsafe extern "C" fn() =
 
718
  core::mem::transmute(rb(va::DSSOUND_LOAD_EFFECT_WAVS) as usize);
719
- f();
720
- }
721
- {
722
- let f: unsafe extern "C" fn() =
723
- core::mem::transmute(rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS) as usize);
724
- f();
725
  }
 
 
726
  // Allocate ActiveSoundTable (0x608 bytes)
727
  let ast = wa_malloc(0x608) as *mut ActiveSoundTable;
728
  (*ast).ddgame = ddgame;
@@ -732,8 +696,6 @@ unsafe fn init_graphics_and_resources(
732
  }
733
  }
734
 
735
- let _ = log_line("[DDGame] audio init done");
736
-
737
  // ── GfxResource (used by PCLandscape and later by gradients) ──
738
  let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
739
  core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
@@ -784,10 +746,6 @@ unsafe fn init_graphics_and_resources(
784
  }
785
  };
786
 
787
- let _ = log_line(&format!(
788
- "[DDGame] PCLandscape=0x{:08X}", landscape as u32,
789
- ));
790
-
791
  // ── TaskStateMachine at DDGame+0x380 (alloc 0x2C) ──
792
  {
793
  let tsm = wa_malloc(0x2C);
@@ -849,8 +807,6 @@ unsafe fn init_graphics_and_resources(
849
  release(gfx_resource);
850
  }
851
 
852
- let _ = log_line("[DDGame] SpriteRegions + TSM done");
853
-
854
  // Resolved addresses for GfxDir operations (used by arrows, gradients, fill)
855
  let gfx_find_entry = rb(va::GFX_DIR_FIND_ENTRY) as u32;
856
  let gfx_load_image = rb(va::GFX_DIR_LOAD_IMAGE) as u32;
@@ -939,8 +895,6 @@ unsafe fn init_graphics_and_resources(
939
  }
940
  }
941
 
942
- let _ = log_line("[DDGame] arrow sprites done");
943
-
944
  // ── DisplayGfx at DDGame+0x138 ──
945
  {
946
  let tsm = wa_malloc(0x2C);
@@ -967,15 +921,9 @@ unsafe fn init_graphics_and_resources(
967
  // TODO: populate coord_list from landscape data (complex loop)
968
  }
969
 
970
- // ── Weapon sprites (FUN_005717A0 Γ—2) ──
971
- {
972
- let load_weapon_sprites: unsafe extern "C" fn() =
973
- core::mem::transmute(rb(va::DDGAME_LOAD_WEAPON_SPRITES) as usize);
974
- load_weapon_sprites();
975
- load_weapon_sprites();
976
- }
977
-
978
- let _ = log_line("[DDGame] weapon sprites done");
979
 
980
  // ── Sprite resource loading via DDGameWrapper vtable[0] ──
981
  // DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
@@ -1048,8 +996,6 @@ unsafe fn init_graphics_and_resources(
1048
  // gradient_image_2 = 0
1049
  (*ddgame).gradient_image_2 = core::ptr::null_mut();
1050
 
1051
- let _ = log_line("[DDGame] sprite resources loaded");
1052
-
1053
  // ── Gradient image (0x030) ──
1054
  // The gradient is loaded from "gradient.img" via GfxDir.
1055
  // Simple path: height <= 0x60 AND level_height == 0x2B8
@@ -1070,7 +1016,6 @@ unsafe fn init_graphics_and_resources(
1070
  // Complex gradient computation β€” ~200 lines of color interpolation.
1071
  // For now, skip this path (gradient_image remains null/0).
1072
  // This affects the sky background appearance but not gameplay.
1073
- let _ = log_line("[DDGame] WARNING: complex gradient path not ported, skipping");
1074
  // Set gradient_image to null (already zero from memset)
1075
  }
1076
 
@@ -1095,8 +1040,6 @@ unsafe fn init_graphics_and_resources(
1095
  }
1096
  }
1097
 
1098
- let _ = log_line(&format!("[DDGame] fill_pixel=0x{:08X}", (*ddgame).fill_pixel));
1099
-
1100
  // ── HUD weapon sprites ──
1101
  {
1102
  let hud_load: unsafe extern "stdcall" fn(u32, u32) =
@@ -1127,9 +1070,7 @@ unsafe fn init_graphics_and_resources(
1127
  init_display_final(disp_obj as u32);
1128
  }
1129
  if *(rb(0x88E485) as *const u8) == 0 {
1130
- let fun_570a90: unsafe extern "C" fn() =
1131
- core::mem::transmute(rb(va::FUN_570A90) as usize);
1132
- fun_570a90();
1133
  }
1134
 
1135
  // Display layer visibility: vt[0x5C](1,0), vt[0x5C](2,0), vt[0x5C](3,1)
@@ -1195,6 +1136,49 @@ unsafe fn call_gfx_find_and_load(
1195
  call_gfx_load_and_wrap(name, load_image_addr, wrap_addr)
1196
  }
1197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1198
  /// Bridge to GfxHandler__LoadDir (0x5663E0).
1199
  /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
1200
  #[cfg(target_arch = "x86")]
 
371
  unsafe {
372
  INIT_FIELDS_ADDR = rb(va::DDGAME_INIT_FIELDS);
373
  SPRITE_REGION_CTOR_ADDR = rb(va::SPRITE_REGION_CONSTRUCTOR);
374
+ FUN_570E20_ADDR = rb(va::FUN_570E20);
375
+ FUN_570A90_ADDR = rb(va::FUN_570A90);
376
+ FUN_570F30_ADDR = rb(va::DDGAME_INIT_SOUND_PATHS);
377
+ LOAD_SPEECH_BANKS_ADDR = rb(va::DSSOUND_LOAD_ALL_SPEECH_BANKS);
378
+ LOAD_WEAPON_SPRITES_ADDR = rb(va::DDGAME_LOAD_WEAPON_SPRITES);
379
  }
380
  }
381
 
 
482
  }
483
  }
484
 
 
 
 
 
 
485
  // ── 9. InitVersionFlags β€” sets DDGame+0x7E2E/0x7E2F/0x7E3F ──
 
486
  call_init_version_flags(wrapper);
 
 
 
 
487
 
488
  // ── 10. GfxHandler, landscape, sprites, audio, resources ──
489
  init_graphics_and_resources(wrapper, game_info, net_game, display, is_headless);
 
504
  is_headless: bool,
505
  ) {
506
  let ddgame = (*wrapper).ddgame;
 
507
  let fopen: unsafe extern "cdecl" fn(*const u8, *const u8) -> *mut u8 =
508
  core::mem::transmute(rb(va::WA_FOPEN) as usize);
509
  let gfx_load_dir_addr = rb(va::GFX_HANDLER_LOAD_DIR);
 
515
  *(gfx1 as *mut u32) = gfx_handler_vtable;
516
  (*wrapper)._field_4c0 = gfx1;
517
  (*wrapper)._field_4c4 = core::ptr::null_mut();
 
518
 
519
  // Build path list (order depends on headless + GameInfo+0xF374)
520
  let f914 = *(game_info as *const u8).add(0xF914).cast::<i32>();
 
533
 
534
  let mut gfx_loaded_idx = 0u32;
535
  for (i, path) in paths.iter().enumerate() {
 
536
  let fp = fopen(path.as_ptr(), b"rb\0".as_ptr());
 
537
  *(gfx1.add(0x198) as *mut *mut u8) = fp;
538
  if !fp.is_null() && call_gfx_load_dir(gfx1, gfx_load_dir_addr) != 0 {
539
  gfx_loaded_idx = i as u32;
 
546
 
547
  let headless_offset = if f914 != 0 { 1u32 } else { 0u32 };
548
  (*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
 
 
 
 
 
549
 
550
  // ── GfxHandler #2 (conditional) ──
551
  let d778 = *(game_info as *const u8).add(0xD778).cast::<i32>();
 
575
  }
576
  }
577
 
 
 
 
 
 
 
578
  // ── Display palette setup (non-headless) ──
579
  if !is_headless {
580
  if *(rb(0x88E485) as *const u8) == 0 {
581
+ call_usercall_eax(wrapper, FUN_570A90_ADDR);
 
 
582
  }
 
 
 
 
583
  // DDGameWrapper+0x4D0 points to the display object used for palette calls.
584
  // BUT: FUN_00570A90 or something else may write to wrapper in ways we
585
  // don't expect. Read via struct field instead of raw offset.
586
  let disp = (*wrapper).display as *mut u8;
 
587
  let vt = *(disp as *const *const u32);
588
  let vt_10: unsafe extern "thiscall" fn(*mut u8) =
589
  core::mem::transmute(*vt.add(4));
 
610
  }
611
  }
612
 
613
+ // ── FUN_00570E20: usercall(ESI=wrapper), plain RET ──
614
+ call_usercall_esi(wrapper, FUN_570E20_ADDR);
 
 
 
 
 
615
 
616
  // ── Display vtable slot 5 (offset 0x14) ──
617
  {
618
  let vt = *((*ddgame).display as *const *const u32);
619
+ let f: unsafe extern "thiscall" fn(*mut DDDisplay) -> *mut u8 =
620
  core::mem::transmute(*vt.add(5));
621
  f((*ddgame).display);
622
  }
 
664
 
665
  // ── DDGameWrapper field inits ──
666
  (*wrapper)._field_4d8 = 0;
667
+ if display.is_null() || net_game.is_null() {
668
  (*wrapper)._field_4dc = 0x2AD;
669
  } else {
670
  let byte_val = *net_game.add(0x44C) as u32;
 
675
 
676
  // ── Audio init (non-headless + sound available) ──
677
  if !is_headless {
678
+ // FUN_570F30: usercall(ESI=wrapper)
679
+ call_usercall_esi(wrapper, FUN_570F30_ADDR);
680
+
 
 
681
  if !(*ddgame).sound.is_null() {
682
  {
683
+ // DSSound_LoadEffectWAVs: stdcall(wrapper)
684
+ let f: unsafe extern "stdcall" fn(*mut DDGameWrapper) =
685
  core::mem::transmute(rb(va::DSSOUND_LOAD_EFFECT_WAVS) as usize);
686
+ f(wrapper);
 
 
 
 
 
687
  }
688
+ // DSSound_LoadAllSpeechBanks: usercall(ESI=wrapper)
689
+ call_usercall_esi(wrapper, LOAD_SPEECH_BANKS_ADDR);
690
  // Allocate ActiveSoundTable (0x608 bytes)
691
  let ast = wa_malloc(0x608) as *mut ActiveSoundTable;
692
  (*ast).ddgame = ddgame;
 
696
  }
697
  }
698
 
 
 
699
  // ── GfxResource (used by PCLandscape and later by gradients) ──
700
  let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
701
  core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
 
746
  }
747
  };
748
 
 
 
 
 
749
  // ── TaskStateMachine at DDGame+0x380 (alloc 0x2C) ──
750
  {
751
  let tsm = wa_malloc(0x2C);
 
807
  release(gfx_resource);
808
  }
809
 
 
 
810
  // Resolved addresses for GfxDir operations (used by arrows, gradients, fill)
811
  let gfx_find_entry = rb(va::GFX_DIR_FIND_ENTRY) as u32;
812
  let gfx_load_image = rb(va::GFX_DIR_LOAD_IMAGE) as u32;
 
895
  }
896
  }
897
 
 
 
898
  // ── DisplayGfx at DDGame+0x138 ──
899
  {
900
  let tsm = wa_malloc(0x2C);
 
921
  // TODO: populate coord_list from landscape data (complex loop)
922
  }
923
 
924
+ // ── Weapon sprites (FUN_005717A0 Γ—2): usercall(ECX=wrapper) ──
925
+ call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
926
+ call_usercall_ecx(wrapper, LOAD_WEAPON_SPRITES_ADDR);
 
 
 
 
 
 
927
 
928
  // ── Sprite resource loading via DDGameWrapper vtable[0] ──
929
  // DDNetGameWrapper__LoadResourceList: thiscall(ECX=wrapper) +
 
996
  // gradient_image_2 = 0
997
  (*ddgame).gradient_image_2 = core::ptr::null_mut();
998
 
 
 
999
  // ── Gradient image (0x030) ──
1000
  // The gradient is loaded from "gradient.img" via GfxDir.
1001
  // Simple path: height <= 0x60 AND level_height == 0x2B8
 
1016
  // Complex gradient computation β€” ~200 lines of color interpolation.
1017
  // For now, skip this path (gradient_image remains null/0).
1018
  // This affects the sky background appearance but not gameplay.
 
1019
  // Set gradient_image to null (already zero from memset)
1020
  }
1021
 
 
1040
  }
1041
  }
1042
 
 
 
1043
  // ── HUD weapon sprites ──
1044
  {
1045
  let hud_load: unsafe extern "stdcall" fn(u32, u32) =
 
1070
  init_display_final(disp_obj as u32);
1071
  }
1072
  if *(rb(0x88E485) as *const u8) == 0 {
1073
+ call_usercall_eax(wrapper, FUN_570A90_ADDR);
 
 
1074
  }
1075
 
1076
  // Display layer visibility: vt[0x5C](1,0), vt[0x5C](2,0), vt[0x5C](3,1)
 
1136
  call_gfx_load_and_wrap(name, load_image_addr, wrap_addr)
1137
  }
1138
 
1139
+ // Statics for usercall bridge addresses
1140
+ static mut FUN_570E20_ADDR: u32 = 0;
1141
+ static mut FUN_570A90_ADDR: u32 = 0;
1142
+ static mut FUN_570F30_ADDR: u32 = 0;
1143
+ static mut LOAD_SPEECH_BANKS_ADDR: u32 = 0;
1144
+ static mut LOAD_WEAPON_SPRITES_ADDR: u32 = 0;
1145
+
1146
+ /// Bridge: usercall(ESI=wrapper), plain RET. Used by FUN_570E20, FUN_570F30, LoadSpeechBanks.
1147
+ #[cfg(target_arch = "x86")]
1148
+ #[unsafe(naked)]
1149
+ unsafe extern "C" fn call_usercall_esi(_wrapper: *mut DDGameWrapper, _addr: u32) {
1150
+ core::arch::naked_asm!(
1151
+ "pushl %esi",
1152
+ "movl 8(%esp), %esi", // ESI = wrapper
1153
+ "movl 12(%esp), %eax", // EAX = target address
1154
+ "calll *%eax",
1155
+ "popl %esi",
1156
+ "retl",
1157
+ options(att_syntax),
1158
+ );
1159
+ }
1160
+
1161
+ /// Bridge: usercall(EAX=wrapper), plain RET. Used by FUN_570A90.
1162
+ #[cfg(target_arch = "x86")]
1163
+ #[unsafe(naked)]
1164
+ unsafe extern "C" fn call_usercall_eax(_wrapper: *mut DDGameWrapper, _addr: u32) {
1165
+ core::arch::naked_asm!(
1166
+ "movl 4(%esp), %eax", // EAX = wrapper
1167
+ "movl 8(%esp), %ecx", // ECX = target address (temp)
1168
+ "calll *%ecx",
1169
+ "retl",
1170
+ options(att_syntax),
1171
+ );
1172
+ }
1173
+
1174
+ /// Bridge: usercall(ECX=wrapper), plain RET. Used by FUN_5717A0.
1175
+ #[cfg(target_arch = "x86")]
1176
+ unsafe fn call_usercall_ecx(wrapper: *mut DDGameWrapper, addr: u32) {
1177
+ let f: unsafe extern "thiscall" fn(*mut DDGameWrapper) =
1178
+ core::mem::transmute(addr as usize);
1179
+ f(wrapper);
1180
+ }
1181
+
1182
  /// Bridge to GfxHandler__LoadDir (0x5663E0).
1183
  /// Convention: usercall(EAX=handler), plain RET. Returns nonzero on success.
1184
  #[cfg(target_arch = "x86")]