fix EnemyDamageSync crash on Xbox at connect time

This commit is contained in:
Martin Michelsen
2025-10-26 21:06:02 -07:00
parent b39b4197ed
commit 27b5556e4b
3 changed files with 250 additions and 217 deletions
@@ -17,10 +17,140 @@ start:
call write_static_patches
call write_incr_hp_with_sync
call write_6x0A_patch
call write_6xE4_handler
ret
call_write_call_to_code:
call write_call_to_code_multi
ret
write_6xE4_handler:
push 0 # Absolute address, not call opcode
push <VERS 0x00537184 0x00537804 0x0053EB24 0x0053BFA4 0x0053B844 0x0053BFA4 0x0053C344>
push 1
call +4
.deltaof handle_6xE4, handle_6xE4_end
pop eax
push dword [eax]
call call_write_call_to_code
handle_6xE4: # [std] (G_6xE4* cmd @ [esp + 4]) -> void
push ebx
push esi
push edi
test byte [<VERS 0x00630590 0x00630BF0 0x00638738 0x00635C20 0x006354B8 0x00635C20 0x00635FB8>], 0x80
jz handle_6xE4_return
mov ebx, [esp + 0x10] # cmd
movzx eax, word [ebx + 2]
cmp eax, 0x1000
jl handle_6xE4_return
cmp eax, 0x1B50
jge handle_6xE4_return
mov edi, eax
mov eax, <VERS 0x002B36B0 0x002B4180 0x002B5710 0x002B5220 0x002B5400 0x002B5240 0x002B5510>
call eax # TObjEnemy* ene = get_enemy_entity(cmd->header.entity_id);
push eax
movzx eax, word [ebx + 2]
and eax, 0x0FFF
imul eax, eax, 0x0C
add eax, [<VERS 0x00633068 0x006336C8 0x0063B210 0x006386F8 0x00637F90 0x006386F8 0x00638A90>] # eax = state_for_enemy(cmd->header.entity_id)
cmp dword [ebx + 0x0C], 0
jl handle_6xE4_not_proportional
mov cx, [ebx + 0x0A] # cmd->max_hp
sub cx, [eax + 0x06] # st.total_damage
movzx ecx, cx
xor edx, edx
cmp ecx, edx
cmovl ecx, edx
push ecx
fild st0, dword [esp] # current_hp = static_cast<float>(max<int32_t>(cmd->max_hp - st.total_damage, 0))
fld st0, dword [ebx + 0x0C]
fmulp st1, st0
fistp dword [esp], st0
mov ecx, dword [esp] # adjusted_hit_amount = static_cast<int16_t>(current_hp * cmd->factor)
add esp, 4
xor edx, edx
inc edx
cmp ecx, edx
cmovl ecx, edx
mov [ebx + 0x04], cx # cmd->hit_amount = min<int32_t>(1, adjusted_hit_amount)
handle_6xE4_not_proportional:
movzx edx, word [eax + 0x06] # st.total_damage
movsx esi, word [ebx + 0x04] # cmd->hit_amount
movzx edi, word [ebx + 0x0A] # cmd->max_hp
add edx, esi # st.total_damage + cmd->hit_amount
cmp edx, edi
jl handle_6xE4_damage_less_than_max_hp
mov [eax + 0x06], di # st.total_damage = cmd->max_hp;
mov edx, [eax]
test edx, 0x800
jnz handle_6xE4_return_pop_ene
or edx, 0x800
mov [eax], edx
cmp dword [esp], 0
je handle_6xE4_return_pop_ene
push edx # out_cmd.flags
sub esp, 8
mov word [esp], 0x030A # out_cmd.header.{subcommand,size}
mov si, [ebx + 2]
mov [esp + 2], si # out_cmd.header.entity_id
and si, 0x0FFF
mov [esp + 4], si # out_cmd.entity_index
mov [esp + 6], di # out_cmd.total_damage
mov ecx, esp
push ecx # For handle_60 later
mov ebx, [<VERS 0x0071EEFC 0x0071F55C 0x007270A0 0x0072459C 0x00723E20 0x0072459C 0x00724920>] # root_protocol
test ebx, ebx
jz handle_6xE4_root_protocol_missing
mov eax, 0x0C
mov edx, <VERS 0x002DA120 0x002DACF0 0x002DC5B0 0x002DC080 0x002DC580 0x002DC0B0 0x002DC600>
call edx # send_60(root_protocol, &out_cmd, sizeof(out_cmd))
handle_6xE4_root_protocol_missing:
mov dword [<VERS 0x0071E8C8 0x0071EF28 0x00726A68 0x00723F68 0x007237E8 0x00723F68 0x007242E8>], 1
mov eax, <VERS 0x002DBC30 0x002DC7B0 0x002DE070 0x002DDB90 0x002DE090 0x002DDBC0 0x002DE0C0>
call eax # handle_60(&out_cmd)
mov dword [<VERS 0x0071E8C8 0x0071EF28 0x00726A68 0x00723F68 0x007237E8 0x00723F68 0x007242E8>], 0
add esp, 0x14
jmp handle_6xE4_return
handle_6xE4_damage_less_than_max_hp:
xor edi, edi
cmp edx, edx
cmovl edx, edi
mov [eax + 0x06], dx # st.total_damage = std::max<int16_t>(st.total_damage + cmd->hit_amount, 0);
mov esi, eax # esi = ene_st
mov eax, [esp] # eax = ene
test eax, eax
jz handle_6xE4_return_pop_ene
mov ecx, eax
push esi
mov edx, [ecx]
call [edx + 0x138] # ene->vtable[0x4E](ene, &st);
handle_6xE4_return_pop_ene:
add esp, 4
handle_6xE4_return:
pop edi
pop esi
pop ebx
ret
handle_6xE4_end:
write_6x0A_patch:
push 5
push <VERS 0x002B3B55 0x002B4625 0x002B5BB5 0x002B56C5 0x002B58A5 0x002B56E5 0x002B59B5>
@@ -29,7 +159,7 @@ write_6x0A_patch:
.deltaof on_6x0A_patch_start, on_6x0A_patch_end
pop eax
push dword [eax]
call on_6x0A_patch_end
call call_write_call_to_code
on_6x0A_patch_start: # (TObjectV004434c8* this @ eax, int16_t amount @ cx) -> bool @ eax
test byte [<VERS 0x00630590 0x00630BF0 0x00638738 0x00635C20 0x006354B8 0x00635C20 0x00635FB8>], 0x80
@@ -37,11 +167,7 @@ on_6x0A_patch_start: # (TObjectV004434c8* this @ eax, int16_t amount @ cx) -> b
mov [esp + 0x16], ax
on_6x0A_patch_skip_write:
ret
on_6x0A_patch_end:
call write_call_to_code_multi
ret
# Write TObjectV004434c8::incr_hp_with_sync
@@ -81,7 +207,7 @@ write_incr_hp_with_sync:
.deltaof on_add_or_subtract_hp_start, on_add_or_subtract_hp_end
pop eax
push dword [eax]
call on_add_or_subtract_hp_end
call call_write_call_to_code
on_add_or_subtract_hp_start: # (TObjectV004434c8* this @ eax, int16_t amount @ cx) -> bool @ eax
# Check if callsite is subtract_hp_if_not_in_state_2
@@ -153,16 +279,15 @@ on_add_or_subtract_hp_skip_send:
pop ecx
pop eax
jmp edx
on_add_or_subtract_hp_end:
call write_call_to_code_multi
ret
write_static_patches:
.include WriteCodeBlocksXB
.data <VERS 0x002DB7A0 0x002DC370 0x002DDC30 0x002DD700 0x002DDC00 0x002DD730 0x002DDC80>
.data 9
flag_check_start:
@@ -170,123 +295,13 @@ flag_check_start:
jz +0x38
flag_check_end:
# Replace 6x09 handler with 6xE4
.data <VERS 0x00537180 0x00537800 0x0053EB20 0x0053BFA0 0x0053B840 0x0053BFA0 0x0053C340>
.data 8
.data 4
.data 0x000600E4 # subcommand=0xE4, flags=6
.addrof handle_6xE4
.data <VERS 0x002DA510 0x002DB0E0 0x002DC9A0 0x002DC470 0x002DC970 0x002DC4A0 0x002DC9F0>
.deltaof handle_91_replacement, handle_6xE4_end
.address <VERS 0x002DA510 0x002DB0E0 0x002DC9A0 0x002DC470 0x002DC970 0x002DC4A0 0x002DC9F0>
handle_91_replacement: # [std] (S_91* cmd @ [esp + 4]) -> void
ret 4
handle_6xE4: # [std] (G_6xE4* cmd @ [esp + 4]) -> void
push ebx
push esi
push edi
test byte [<VERS 0x00630590 0x00630BF0 0x00638738 0x00635C20 0x006354B8 0x00635C20 0x00635FB8>], 0x80
jz handle_6xE4_return
mov ebx, [esp + 0x10] # cmd
movzx eax, word [ebx + 2]
cmp eax, 0x1000
jl handle_6xE4_return
cmp eax, 0x1B50
jge handle_6xE4_return
mov edi, eax
call <VERS 0x002B36B0 0x002B4180 0x002B5710 0x002B5220 0x002B5400 0x002B5240 0x002B5510> # TObjEnemy* ene = get_enemy_entity(cmd->header.entity_id);
push eax
movzx eax, word [ebx + 2]
and eax, 0x0FFF
imul eax, eax, 0x0C
add eax, [<VERS 0x00633068 0x006336C8 0x0063B210 0x006386F8 0x00637F90 0x006386F8 0x00638A90>] # eax = state_for_enemy(cmd->header.entity_id)
cmp dword [ebx + 0x0C], 0
jl handle_6xE4_not_proportional
mov cx, [ebx + 0x0A] # cmd->max_hp
sub cx, [eax + 0x06] # st.total_damage
movzx ecx, cx
xor edx, edx
cmp ecx, edx
cmovl ecx, edx
push ecx
fild st0, dword [esp] # current_hp = static_cast<float>(max<int32_t>(cmd->max_hp - st.total_damage, 0))
fld st0, dword [ebx + 0x0C]
fmulp st1, st0
fistp dword [esp], st0
mov ecx, dword [esp] # adjusted_hit_amount = static_cast<int16_t>(current_hp * cmd->factor)
add esp, 4
xor edx, edx
inc edx
cmp ecx, edx
cmovl ecx, edx
mov [ebx + 0x04], cx # cmd->hit_amount = min<int32_t>(1, adjusted_hit_amount)
handle_6xE4_not_proportional:
movzx edx, word [eax + 0x06] # st.total_damage
movsx esi, word [ebx + 0x04] # cmd->hit_amount
movzx edi, word [ebx + 0x0A] # cmd->max_hp
add edx, esi # st.total_damage + cmd->hit_amount
cmp edx, edi
jl handle_6xE4_damage_less_than_max_hp
mov [eax + 0x06], di # st.total_damage = cmd->max_hp;
mov edx, [eax]
test edx, 0x800
jnz handle_6xE4_return_pop_ene
or edx, 0x800
mov [eax], edx
cmp dword [esp], 0
je handle_6xE4_return_pop_ene
push edx # out_cmd.flags
sub esp, 8
mov word [esp], 0x030A # out_cmd.header.{subcommand,size}
mov si, [ebx + 2]
mov [esp + 2], si # out_cmd.header.entity_id
and si, 0x0FFF
mov [esp + 4], si # out_cmd.entity_index
mov [esp + 6], di # out_cmd.total_damage
mov ecx, esp
push ecx # For handle_60 later
mov ebx, [<VERS 0x0071EEFC 0x0071F55C 0x007270A0 0x0072459C 0x00723E20 0x0072459C 0x00724920>] # root_protocol
test ebx, ebx
jz handle_6xE4_root_protocol_missing
mov eax, 0x0C
call <VERS 0x002DA120 0x002DACF0 0x002DC5B0 0x002DC080 0x002DC580 0x002DC0B0 0x002DC600> # send_60(root_protocol, &out_cmd, sizeof(out_cmd))
handle_6xE4_root_protocol_missing:
mov dword [<VERS 0x0071E8C8 0x0071EF28 0x00726A68 0x00723F68 0x007237E8 0x00723F68 0x007242E8>], 1
call <VERS 0x002DBC30 0x002DC7B0 0x002DE070 0x002DDB90 0x002DE090 0x002DDBC0 0x002DE0C0> # handle_60(&out_cmd)
mov dword [<VERS 0x0071E8C8 0x0071EF28 0x00726A68 0x00723F68 0x007237E8 0x00723F68 0x007242E8>], 0
add esp, 0x14
jmp handle_6xE4_return
handle_6xE4_damage_less_than_max_hp:
xor edi, edi
cmp edx, edx
cmovl edx, edi
mov [eax + 0x06], dx # st.total_damage = std::max<int16_t>(st.total_damage + cmd->hit_amount, 0);
mov esi, eax # esi = ene_st
mov eax, [esp] # eax = ene
test eax, eax
jz handle_6xE4_return_pop_ene
mov ecx, eax
push esi
mov edx, [ecx]
call [edx + 0x138] # ene->vtable[0x4E](ene, &st);
handle_6xE4_return_pop_ene:
add esp, 4
handle_6xE4_return:
pop edi
pop esi
pop ebx
ret
handle_6xE4_end:
# Handler address written by write_6xE4_handler
@@ -12,7 +12,7 @@ write_call_to_code:
# [esp + 0x18] = code size
# [esp + 0x1C] = callsite count
# [esp + 0x20] = callsite address
# [esp + 0x24] = callsite size
# [esp + 0x24] = callsite size (if zero, write absolute address instead)
# ... (further callsite address/size pairs)
# esi = allocated code addr
# edi = version_info
@@ -55,21 +55,32 @@ next_callsite:
call [ecx] # MmQueryAddressProtect(callsite_addr)
push eax
push 0x04
push dword [esp + ebp + 0x0C]
mov edx, 4
push edx # XBOX_PAGE_READWRITE
mov ecx, [esp + ebp + 0x0C] # callsite_size
test ecx, ecx
cmovz ecx, edx
push ecx
push dword [esp + ebp + 0x0C]
mov ecx, [edi + 0x08]
call [ecx] # MmSetAddressProtect(callsite_addr, callsite_size, XBOX_PAGE_READWRITE)
mov edx, [esp + ebp + 4] # edx = jump callsite
mov edx, [esp + ebp + 4] # edx = callsite addr
mov eax, [esp + ebp + 8] # eax = callsite size
test eax, eax
jnz write_call_opcode_and_nops
write_address:
mov [edx], esi
jmp this_callsite_done
write_call_opcode_and_nops:
lea ecx, [esi - 5]
sub ecx, edx # ecx = (dest code addr) - (jump callsite) - 5
sub ecx, edx # ecx = (dest code addr) - (callsite addr) - 5
mov byte [edx], 0xE8
mov [edx + 1], ecx # Write E8 (call) followed by delta
# Write as many nops after the call opcode as necessary
mov ecx, 5
mov eax, [esp + ebp + 8]
write_nop_again:
cmp ecx, eax
jge this_callsite_done
@@ -80,7 +91,11 @@ write_nop_again:
this_callsite_done:
# Restore the previous protection
# Previous protection is still on the stack from MmQueryAddressProtect call
push dword [esp + ebp + 8]
mov edx, 4
mov ecx, [esp + ebp + 8]
test ecx, ecx
cmovz ecx, edx
push ecx
push dword [esp + ebp + 8]
mov ecx, [edi + 0x08]
call [ecx] # MmSetAddressProtect(callsite_addr, callsite_size, prev_protection)