paavohtl Claude Opus 4.6 (1M context) commited on
Commit
ac31270
Β·
1 Parent(s): 9a6b43f

feat: add arrow sprites, DisplayGfx, CoordList, weapon sprites

Browse files

Implement remaining constructor sections in create_ddgame():
- Arrow sprite loading loop (32 iterations) with GfxDir bridges
- Arrow collision SpriteRegions
- Arrow GfxDir objects (conditional on secondary gfxdir)
- DisplayGfx at DDGame+0x138
- CoordList at DDGame+0x50C (allocation, population TODO)
- Weapon sprite loading (2x DDGame__LoadWeaponSprites_Maybe)
- Diagnostic log lines at key milestones

Remaining TODO: gradient images, layer/fill sprites, HUD, display
finalization (~200 lines of original 887).

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -5,6 +5,7 @@ use crate::engine::ddgame_wrapper::DDGameWrapper;
5
  use crate::engine::game_info::GameInfo;
6
  use crate::input::keyboard::DDKeyboard;
7
  use crate::audio::dssound::DSSound;
 
8
  use crate::render::landscape::PCLandscape;
9
  use crate::audio::music::Music;
10
  use crate::display::palette::Palette;
@@ -420,6 +421,8 @@ pub unsafe fn create_ddgame(
420
  game_info: *mut GameInfo,
421
  network_ecx: u32, // implicit ECX from caller
422
  ) -> *mut DDGame {
 
 
423
  // ── 1. Allocate and zero-fill (matches: memset(piVar3, 0, 0x98B8)) ──
424
  let ddgame = wa_malloc(0x98B8) as *mut DDGame;
425
  if ddgame.is_null() {
@@ -471,6 +474,11 @@ pub unsafe fn create_ddgame(
471
  }
472
  }
473
 
 
 
 
 
 
474
  // ── 9. InitVersionFlags β€” sets DDGame+0x7E2E/0x7E2F/0x7E3F ──
475
  call_init_version_flags(wrapper);
476
 
@@ -536,6 +544,10 @@ unsafe fn init_graphics_and_resources(
536
 
537
  let headless_offset = if f914 != 0 { 1u32 } else { 0u32 };
538
  (*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
 
 
 
 
539
 
540
  // ── GfxHandler #2 (conditional) ──
541
  let d778 = *(game_info as *const u8).add(0xD778).cast::<i32>();
@@ -693,6 +705,8 @@ unsafe fn init_graphics_and_resources(
693
  }
694
  }
695
 
 
 
696
  // ── GfxResource (used by PCLandscape and later by gradients) ──
697
  let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
698
  core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
@@ -743,6 +757,10 @@ unsafe fn init_graphics_and_resources(
743
  }
744
  };
745
 
 
 
 
 
746
  // ── TaskStateMachine at DDGame+0x380 (alloc 0x2C) ──
747
  {
748
  let tsm = wa_malloc(0x2C);
@@ -804,16 +822,193 @@ unsafe fn init_graphics_and_resources(
804
  release(gfx_resource);
805
  }
806
 
 
 
807
  // ── Arrow sprites + collision regions (32 iterations) ──
808
- // ── DisplayGfx at +0x138 ──
809
- // ── CoordList at +0x50C ──
810
- // ── Weapon sprites, gradient images, fill image, HUD ──
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  // ── Display finalization ──
812
  //
813
- // These remaining sections (~300 lines of decompile) involve complex
814
- // GfxDir lookups, sprite loading, gradient computation, and display
815
- // layer management. They will be added in subsequent commits.
816
- // For now, the constructor returns after audio + landscape + sprite regions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
817
  }
818
 
819
  /// Bridge to SpriteRegion__Constructor (0x57DB20).
 
5
  use crate::engine::game_info::GameInfo;
6
  use crate::input::keyboard::DDKeyboard;
7
  use crate::audio::dssound::DSSound;
8
+ use crate::log::log_line;
9
  use crate::render::landscape::PCLandscape;
10
  use crate::audio::music::Music;
11
  use crate::display::palette::Palette;
 
421
  game_info: *mut GameInfo,
422
  network_ecx: u32, // implicit ECX from caller
423
  ) -> *mut DDGame {
424
+ let _ = log_line("[DDGame] create_ddgame: begin");
425
+
426
  // ── 1. Allocate and zero-fill (matches: memset(piVar3, 0, 0x98B8)) ──
427
  let ddgame = wa_malloc(0x98B8) as *mut DDGame;
428
  if ddgame.is_null() {
 
474
  }
475
  }
476
 
477
+ let _ = log_line(&format!(
478
+ "[DDGame] create_ddgame: alloc=0x{:08X}, headless={}, sound=0x{:08X}",
479
+ ddgame as u32, is_headless, sound as u32,
480
+ ));
481
+
482
  // ── 9. InitVersionFlags β€” sets DDGame+0x7E2E/0x7E2F/0x7E3F ──
483
  call_init_version_flags(wrapper);
484
 
 
544
 
545
  let headless_offset = if f914 != 0 { 1u32 } else { 0u32 };
546
  (*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
547
+ let _ = log_line(&format!(
548
+ "[DDGame] GfxHandler #1 loaded (idx={}), gfx_mode={}",
549
+ gfx_loaded_idx, (*wrapper).gfx_mode,
550
+ ));
551
 
552
  // ── GfxHandler #2 (conditional) ──
553
  let d778 = *(game_info as *const u8).add(0xD778).cast::<i32>();
 
705
  }
706
  }
707
 
708
+ let _ = log_line("[DDGame] audio init done");
709
+
710
  // ── GfxResource (used by PCLandscape and later by gradients) ──
711
  let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
712
  core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
 
757
  }
758
  };
759
 
760
+ let _ = log_line(&format!(
761
+ "[DDGame] PCLandscape=0x{:08X}", landscape as u32,
762
+ ));
763
+
764
  // ── TaskStateMachine at DDGame+0x380 (alloc 0x2C) ──
765
  {
766
  let tsm = wa_malloc(0x2C);
 
822
  release(gfx_resource);
823
  }
824
 
825
+ let _ = log_line("[DDGame] SpriteRegions + TSM done");
826
+
827
  // ── Arrow sprites + collision regions (32 iterations) ──
828
+ {
829
+ let gfx_handler = (*wrapper)._field_4c0;
830
+ let gfx_find_entry = rb(va::GFX_DIR_FIND_ENTRY) as u32;
831
+ let gfx_load_image = rb(va::GFX_DIR_LOAD_IMAGE) as u32;
832
+ let fun_4f5f80 = rb(0x4F5F80) as u32;
833
+ let _sprintf = rb(0x5D125A) as u32;
834
+
835
+ for i in 0u32..32 {
836
+ // Format "arrow%02u.img\0" into stack buffer
837
+ let mut name_buf = *b"arrow00.img\0\0\0\0\0";
838
+ name_buf[5] = b'0' + (i / 10) as u8;
839
+ name_buf[6] = b'0' + (i % 10) as u8;
840
+
841
+ // Display vtable[5](1) β€” set active layer
842
+ {
843
+ let disp_vt = *((*ddgame).display as *const *const u32);
844
+ let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) =
845
+ core::mem::transmute(*disp_vt.add(5));
846
+ set_layer((*ddgame).display, 1);
847
+ }
848
+
849
+ // GfxDir__FindEntry: usercall(EAX=name) + stdcall(gfx_handler), RET 0x4
850
+ let entry: *mut u8;
851
+ core::arch::asm!(
852
+ "push {handler}",
853
+ "call {find}",
854
+ handler = in(reg) gfx_handler,
855
+ find = in(reg) gfx_find_entry,
856
+ inlateout("eax") name_buf.as_ptr() => entry,
857
+ out("ecx") _,
858
+ out("edx") _,
859
+ clobber_abi("C"),
860
+ );
861
+
862
+ let sprite: *mut u8;
863
+ if !entry.is_null() {
864
+ // Try gfx_handler->vtable[2](entry->field_4)
865
+ let gfx_vt = *(gfx_handler as *const *const u32);
866
+ let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
867
+ core::mem::transmute(*gfx_vt.add(2));
868
+ let cached = load_cached(gfx_handler, *(entry.add(4) as *const u32));
869
+ if !cached.is_null() {
870
+ sprite = cached;
871
+ } else {
872
+ // Fallback to GfxDir__LoadImage
873
+ sprite = call_gfx_load_and_wrap(
874
+ name_buf.as_ptr(), gfx_load_image, fun_4f5f80,
875
+ );
876
+ }
877
+ } else {
878
+ sprite = call_gfx_load_and_wrap(
879
+ name_buf.as_ptr(), gfx_load_image, fun_4f5f80,
880
+ );
881
+ }
882
+
883
+ // Store arrow sprite at DDGame+0x38+i*4
884
+ (*ddgame).arrow_sprites[i as usize] = sprite;
885
+
886
+ // Calculate collision region dimensions from sprite
887
+ if !sprite.is_null() {
888
+ let sprite_w = *(sprite.add(0x14) as *const i32);
889
+ let sprite_h = *(sprite.add(0x18) as *const i32);
890
+ let half_w = (sprite_w / 2 - 10).max(0);
891
+ let half_h = (sprite_h / 2 - 10).max(0);
892
+
893
+ // Create SpriteRegion for collision
894
+ let alloc = wa_malloc(0x9C);
895
+ core::ptr::write_bytes(alloc, 0, 0x9C);
896
+ let region = if !alloc.is_null() {
897
+ call_sprite_region_ctor(alloc, 0, 0, 0, half_w as u32, 0, half_h as u32, 0)
898
+ } else {
899
+ core::ptr::null_mut()
900
+ };
901
+ (*ddgame).arrow_collision_regions[i as usize] = region;
902
+ }
903
+
904
+ // Arrow GfxDir (conditional on secondary gfxdir)
905
+ if !(*ddgame).secondary_gfxdir.is_null() {
906
+ let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
907
+ core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
908
+ (*ddgame).arrow_gfxdirs[i as usize] =
909
+ gfx_resource_create(gfx_handler, core::ptr::null_mut());
910
+ }
911
+ }
912
+ }
913
+
914
+ let _ = log_line("[DDGame] arrow sprites done");
915
+
916
+ // ── DisplayGfx at DDGame+0x138 ──
917
+ {
918
+ let tsm = wa_malloc(0x2C);
919
+ core::ptr::write_bytes(tsm, 0, 0x2C);
920
+ if !tsm.is_null() {
921
+ let tsm_init: unsafe extern "fastcall" fn(u32, u32) =
922
+ core::mem::transmute(rb(va::TASK_STATE_MACHINE_INIT) as usize);
923
+ tsm_init(0, 0);
924
+ *(tsm as *mut u32) = rb(0x6641F8); // DisplayGfx__vtable
925
+ }
926
+ (*ddgame).display_gfx = tsm;
927
+ }
928
+
929
+ // ── CoordList at DDGame+0x50C (capacity 600, 0x12C0 buffer) ──
930
+ {
931
+ let cl = wa_malloc(12) as *mut u32;
932
+ *cl = 0; // count
933
+ *cl.add(1) = 600; // capacity
934
+ let data = wa_malloc(0x12C0);
935
+ core::ptr::write_bytes(data, 0, 0x12C0);
936
+ *cl.add(2) = data as u32;
937
+ (*ddgame).coord_list = cl as *mut u8;
938
+
939
+ // TODO: populate coord_list from landscape data (complex loop)
940
+ }
941
+
942
+ // ── Weapon sprites (FUN_005717A0 Γ—2) ──
943
+ {
944
+ let load_weapon_sprites: unsafe extern "C" fn() =
945
+ core::mem::transmute(rb(va::DDGAME_LOAD_WEAPON_SPRITES) as usize);
946
+ load_weapon_sprites();
947
+ load_weapon_sprites();
948
+ }
949
+
950
+ let _ = log_line("[DDGame] weapon sprites done");
951
+
952
+ // ── Gradient images, layer sprites, fill image, HUD ──
953
  // ── Display finalization ──
954
  //
955
+ // These remaining sections (~200 lines) involve complex gradient
956
+ // computation, sprite loading with GfxDir lookups, and display
957
+ // layer management. Will be added in subsequent commits.
958
+ //
959
+ // TODO: layer.spr, back.spr, debris.spr loading
960
+ // TODO: gradient image processing (largest remaining section)
961
+ // TODO: fill.img β†’ fill_pixel at DDGame+0x7338
962
+ // TODO: HUD_LoadWeaponSprites_Maybe
963
+ // TODO: Close GfxHandlers
964
+ // TODO: Display finalization (layer visibility)
965
+ }
966
+
967
+ /// Helper: load image via GfxDir__LoadImage + wrap with FUN_004F5F80.
968
+ /// Used by arrow sprite loop when GfxDir__FindEntry fails.
969
+ #[cfg(target_arch = "x86")]
970
+ unsafe fn call_gfx_load_and_wrap(
971
+ name: *const u8,
972
+ gfx_load_image_addr: u32,
973
+ fun_4f5f80_addr: u32,
974
+ ) -> *mut u8 {
975
+ // GfxDir__LoadImage: usercall(EAX=name?), stdcall(1 stack param), RET 0x4
976
+ let image: *mut u8;
977
+ core::arch::asm!(
978
+ "push {name}",
979
+ "call {load}",
980
+ name = in(reg) name,
981
+ load = in(reg) gfx_load_image_addr,
982
+ out("eax") image,
983
+ out("ecx") _,
984
+ out("edx") _,
985
+ clobber_abi("C"),
986
+ );
987
+ if image.is_null() {
988
+ return core::ptr::null_mut();
989
+ }
990
+ // FUN_004F5F80(display_gfx_context, image, 1)
991
+ let result: *mut u8;
992
+ core::arch::asm!(
993
+ "push 1",
994
+ "push {img}",
995
+ "push {ctx}",
996
+ "call {wrap}",
997
+ "add esp, 12",
998
+ img = in(reg) image,
999
+ ctx = in(reg) 0u32, // display_gfx context, often 0
1000
+ wrap = in(reg) fun_4f5f80_addr,
1001
+ out("eax") result,
1002
+ out("ecx") _,
1003
+ out("edx") _,
1004
+ clobber_abi("C"),
1005
+ );
1006
+ // Release the raw image
1007
+ let image_vt = *(image as *const *const u32);
1008
+ let destroy: unsafe extern "thiscall" fn(*mut u8, u32) =
1009
+ core::mem::transmute(*image_vt);
1010
+ destroy(image, 1);
1011
+ result
1012
  }
1013
 
1014
  /// Bridge to SpriteRegion__Constructor (0x57DB20).