paavohtl Claude Opus 4.6 commited on
Commit
6eccc99
·
1 Parent(s): eb1677d

fix: preserve ECX in usercall trampoline for thiscall MapColor hook

Browse files

WA's ProcessSprite loops calling PaletteContext__MapColor without
re-setting ECX between iterations, relying on the original function
preserving ECX (it only reads [ECX+offset]). Our trampoline's cdecl
call clobbered ECX, causing garbage reads on 2nd+ iteration and
rendering all sprites/text white.

Add a `preserve_ecx` variant to usercall_trampoline! that saves and
restores ECX across the cdecl impl call.

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

crates/openwa-wormkit/src/hook.rs CHANGED
@@ -57,6 +57,29 @@ macro_rules! usercall_trampoline {
57
  }
58
  };
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  // 1 register arg, 3 stack params, ret N
61
  (fn $name:ident; impl_fn = $impl:path; reg = $reg:ident;
62
  stack_params = 3; ret_bytes = $ret:literal) => {
 
57
  }
58
  };
59
 
60
+ // 1 register arg (ECX), 1 stack param, ret N — preserves ECX across call.
61
+ // Callers of thiscall functions may rely on ECX being preserved even though
62
+ // the ABI says it's caller-saved (MSVC often generates loops that don't
63
+ // re-set ECX between calls if the callee happens to preserve it).
64
+ (fn $name:ident; impl_fn = $impl:path; reg = ecx;
65
+ stack_params = 1; ret_bytes = $ret:literal; preserve_ecx) => {
66
+ #[unsafe(naked)]
67
+ unsafe extern "C" fn $name() {
68
+ core::arch::naked_asm!(
69
+ "push edx",
70
+ "push ecx", // save ECX for restoration
71
+ "push [esp+12]", // stack param (shifted +4 by extra push)
72
+ "push ecx", // cdecl arg (ECX still valid here)
73
+ "call {impl_fn}",
74
+ "add esp, 8",
75
+ "pop ecx", // restore ECX
76
+ "pop edx",
77
+ concat!("ret ", $ret),
78
+ impl_fn = sym $impl,
79
+ );
80
+ }
81
+ };
82
+
83
  // 1 register arg, 3 stack params, ret N
84
  (fn $name:ident; impl_fn = $impl:path; reg = $reg:ident;
85
  stack_params = 3; ret_bytes = $ret:literal) => {
crates/openwa-wormkit/src/replacements/sprite.rs CHANGED
@@ -48,7 +48,7 @@ usercall_trampoline!(fn trampoline_process_sprite; impl_fn = process_sprite_impl
48
  // ---------------------------------------------------------------------------
49
 
50
  usercall_trampoline!(fn trampoline_palette_map_color; impl_fn = palette_map_color_impl;
51
- reg = ecx; stack_params = 1; ret_bytes = "0x4");
52
 
53
  unsafe extern "cdecl" fn palette_map_color_impl(palette_ctx: u32, rgb: u32) -> u32 {
54
  let ctx = palette_ctx as *mut openwa_core::render::palette::PaletteContext;
 
48
  // ---------------------------------------------------------------------------
49
 
50
  usercall_trampoline!(fn trampoline_palette_map_color; impl_fn = palette_map_color_impl;
51
+ reg = ecx; stack_params = 1; ret_bytes = "0x4"; preserve_ecx);
52
 
53
  unsafe extern "cdecl" fn palette_map_color_impl(palette_ctx: u32, rgb: u32) -> u32 {
54
  let ctx = palette_ctx as *mut openwa_core::render::palette::PaletteContext;