Merge upstream newserv master

This commit is contained in:
2026-05-09 01:02:34 -04:00
71 changed files with 17519 additions and 495 deletions
@@ -1,6 +1,8 @@
.meta name="Kill count fix"
.meta description="Fixes client-side\nkill counts when\nmultiple enemies are\nkilled on the same\nframe"
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
@@ -9,9 +11,9 @@ start:
.data 0x005E32C8
.data <VERS 0x005E32A4 0x005E32C8>
.deltaof TItemUnitUnsealable_count_kill, TItemUnitUnsealable_count_kill_end
.address 0x005E32C8
.address <VERS 0x005E32A4 0x005E32C8>
TItemUnitUnsealable_count_kill: # [std] (TItemUnitUnsealable* this @ ecx) -> void
mov eax, [ecx + 0xF8]
movsx eax, word [eax + 0x11A] # eax = this->owner_player->num_kills_since_map_load
@@ -29,14 +31,14 @@ TItemUnitUnsealable_count_kill_skip_update:
setae dh
shl edx, 1
or dword [ecx + 0xDC], edx
jmp 0x005E2C34
jmp <VERS 0x005E2C10 0x005E2C34>
TItemUnitUnsealable_count_kill_end:
.data 0x005F3EFC
.data <VERS 0x005F3E94 0x005F3EFC>
.deltaof TItemWeapon_LameDArgent_count_kill, TItemWeapon_LameDArgent_count_kill_end
.address 0x005F3EFC
.address <VERS 0x005F3E94 0x005F3EFC>
TItemWeapon_LameDArgent_count_kill:
mov eax, [ecx + 0xF8]
movsx eax, word [eax + 0x11A]
@@ -59,9 +61,9 @@ TItemWeapon_LameDArgent_count_kill_end:
.data 0x005FCA74
.data <VERS 0x005FC95C 0x005FCA74>
.deltaof TItemWeapon_SealedJSword_count_kill, TItemWeapon_SealedJSword_count_kill_end
.address 0x005FCA74
.address <VERS 0x005FC95C 0x005FCA74>
TItemWeapon_SealedJSword_count_kill:
mov eax, [ecx + 0xF8]
movsx eax, word [eax + 0x11A]
@@ -0,0 +1,624 @@
# This patch changes the number of BB character save slots from 4 to any number
# up to 127.
# This patch is for documentation purposes only; it works when used as a server
# patch via newserv, but is decidedly inconvenient to use via this method. This
# is because it affects logic that runs before any patches can be sent by the
# server, so the player has to connect once to get the patch, then disconnect
# and connect again to use the additional slots.
# As written, this patch changes the slot count from 4 to 12. To use a
# different slot count, first compute the following values:
# slot count = your desired number of player slots (must be >= 4, <= 127)
# total file size = (slot count * 0x2EA4) + 0x14
# bgm_test_songs_unlocked offset = total file size - 0x10
# save_count offset = total file size - 8
# round2_seed offset = total file size - 4
# Then, for each of the above, search for the string to the left of the = sign
# and change the values used in all of the matching lines.
.meta name="More save slots"
.meta description=""
.meta hide_from_patches_menu
entry_ptr:
reloc0:
.offsetof start
# Include a few functions first
write_call_to_code:
.include WriteCallToCode-59NJ
memcpy:
.include CopyData
ret
start:
# Apply all necessary patches
call apply_enable_scroll_patch
call apply_fix_scroll_patch1
call apply_fix_scroll_patch2
call apply_fix_file_index
call apply_preview_window_fix
call apply_static_patches
# Rewrite the existing char file regions to have the appropriate size; this
# must be done after the patches are applied because we call the checksum
# function, which is patched by one of the above calls
call update_existing_char_file_list
jmp update_existing_char_file_list_memcard
apply_enable_scroll_patch:
# This patch enables scrolling behavior within the character list
push -5 # Jump size (negative = jmp instead of call)
push 0x00413B77 # Jump address
call get_code_size_for_enable_scroll
.deltaof enable_scroll_start, enable_scroll_end
get_code_size_for_enable_scroll:
pop eax
push dword [eax]
call enable_scroll_end
enable_scroll_start:
mov eax, dword ptr [edi + 0x28] # cursor = char_select_menu->cursor_obj (TAdSelectCurGC*)
or dword [eax + 0x01F8], 3 # cursor->flags |= 3 # Enable scrolling
mov eax, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
mov ecx, [eax + 0xEC] # ecx = scroll_bar->client_id
imul ecx, ecx, 0x24
# Set up scroll bar graphics (in struct at scroll_bar + 0x1C)
mov dword [eax + ecx + 0x1C], 0x439D0000
mov dword [eax + ecx + 0x20], 0x43360000
mov dword [eax + ecx + 0x24], 0x439D0000
mov dword [eax + ecx + 0x28], 0x4392AB85
mov dword [eax + ecx + 0x2C], 0x40400000
mov dword [eax + ecx + 0x30], 0x425EA3D7
mov dword [eax + ecx + 0x34], 0x00000008
mov dword [eax + ecx + 0x38], 0x00000000
mov dword [eax + ecx + 0x3C], 0x00000000
or dword [eax + 0xF0], 1 # scroll_bar->flags |= 1
mov ecx, [eax + 0xEC]
shl ecx, 4
mov dword [eax + ecx + 0xAC], 0 # scroll_bar->selection_state[client_id].scroll_offset = 0
mov dword [eax + ecx + 0xB0], 0 # scroll_bar->selection_state[client_id].selected_index = 0
mov dword [eax + ecx + 0xB4], 4 # scroll_bar->selection_state[client_id].num_items_in_view = 4
mov dword [eax + ecx + 0xB8], 0x0B # scroll_bar->selection_state[client_id].last_item_index = (slot count - 1)
pop edi
ret
enable_scroll_end:
call write_call_to_code
ret
apply_fix_scroll_patch1:
# This patch fixes character selection cursor object so it will take the
# scroll offset into account
push 6 # Call size
push 0x00413C30 # Call address
call get_code_size_for_fix_scroll_patch1
.deltaof fix_scroll_patch1_start, fix_scroll_patch1_end
get_code_size_for_fix_scroll_patch1:
pop eax
push dword [eax]
call fix_scroll_patch1_end
fix_scroll_patch1_start:
mov edx, [edi + 0x28] # cursor = this->ad_select_cur_obj (TAdSelectCurGC*)
mov ebp, [edx + 0x44] # ebp = cursor->selected_index_within_view
mov eax, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
add ebp, [eax + 0xAC] # ebp += scroll_bar->selection_state[0].scroll_offset
ret
fix_scroll_patch1_end:
call write_call_to_code
ret
apply_fix_scroll_patch2:
# This patch changes the TAdSinglePlyChrSelectGC::selected_index_within_view
# to be the selected character's absolute index (including scroll_offset),
# not the index only within the displayed four characters
push 6 # Call size
push 0x00413CD0 # Call address
call get_code_size_for_fix_scroll_patch2
.deltaof fix_scroll_patch2_start, fix_scroll_patch2_end
get_code_size_for_fix_scroll_patch2:
pop eax
push dword [eax]
call fix_scroll_patch2_end
fix_scroll_patch2_start:
mov eax, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
mov eax, [eax + 0xAC] # eax = scroll_bar->selection_state[0].scroll_offset
mov edx, [edi + 0x28] # cursor = this->ad_select_cur_obj (TAdSelectCurGC*)
add eax, [edx + 0x44] # eax += cursor->selected_index_within_view
ret
fix_scroll_patch2_end:
call write_call_to_code
ret
apply_fix_file_index:
# This patch fixes the character file indexing so it will account for the
# scroll position
push 5 # Call size
push 0x00413CE8 # Call address
call get_code_size_for_selection_index_fix2
.deltaof selection_index_fix2_start, selection_index_fix2_end
get_code_size_for_selection_index_fix2:
pop eax
push dword [eax]
call selection_index_fix2_end
selection_index_fix2_start:
mov eax, [0x00A38BD0]
mov eax, [eax + 0xAC] # eax = TAdScrollBarXb_objs[0]->selection_state[0].scroll_offset
add ebp, eax # arg0 += eax
mov [esp + 4], ebp
mov eax, 0x006C1ABC
jmp eax # set_current_char_slot
selection_index_fix2_end:
call write_call_to_code
ret
apply_preview_window_fix:
# This patch fixes the preview display so it will show the correct section
# ID, level, etc.
push 5 # Call size
push 0x0040216C # Call address
call get_code_size_for_preview_window_fix
.deltaof preview_window_fix_start, preview_window_fix_end
get_code_size_for_preview_window_fix:
pop eax
push dword [eax]
call preview_window_fix_end
preview_window_fix_start:
mov eax, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
mov eax, [eax + 0xAC] # eax = scroll_bar->selection_state[0].scroll_offset
add [esp + 4], eax
mov eax, 0x006C4514 # get_player_preview_info
jmp eax
preview_window_fix_end:
# This patch applies in two places, so push the second set of args now, then
# apply it twice
push 5 # Call size
push 0x00401842 # Call address
push dword [esp + 0x10] # Code size
push dword [esp + 0x10] # Code address
call write_call_to_code
call write_call_to_code
ret
apply_static_patches:
.include WriteCodeBlocksBB
# These patches change various places where the character data size and slot
# count are referenced
.data 0x00475294
.data 0x00000001
.binary 0C # slot count; TDataProtocol::handle_E5
.data 0x0047534B
.data 0x00000001
.binary 0C # slot count; import_player_preview
.data 0x004786D1
.data 0x00000001
.binary 0C # slot count; TDataProtocol::handle_E4
.data 0x00482559
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C17FB
.data 0x00000001
.binary 0C # slot count
.data 0x006C1D07
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C1D3A
.data 0x00000001
.binary 0C # slot count
.data 0x006C1D58
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C1E13
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C226A
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C22A9
.data 0x00000001
.binary 0C # slot count
.data 0x006C22CA
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C22DA
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C2517
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C267F
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C2689
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C272B
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C2741
.data 0x00000004
.data 0x00022FC0 # round2_seed offset
.data 0x006C27CF
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C28A8
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C314F
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C357B
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C35BA
.data 0x00000001
.binary 0C # slot count
.data 0x006C35E6
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C35F3
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C360E
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C3617
.data 0x00000004
.data 0x00022FBC # save_count offset
.data 0x006C371C
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C3B5A
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C424D
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4833
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C486A
.data 0x00000001
.binary 0C # slot count
.data 0x006C49A6
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C49DD
.data 0x00000001
.binary 0C # slot count
.data 0x006C4AC5
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4AFE
.data 0x00000001
.binary 0C # slot count
.data 0x006C4CDE
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4D15
.data 0x00000001
.binary 0C # slot count
.data 0x006C4DFD
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4E36
.data 0x00000001
.binary 0C # slot count
.data 0x006C4F9C
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C4FD7
.data 0x00000001
.binary 0C # slot count
.data 0x006C51C5
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5201
.data 0x00000001
.binary 0C # slot count
.data 0x006C5376
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C53B0
.data 0x00000001
.binary 0C # slot count
.data 0x006C5545
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5581
.data 0x00000001
.binary 0C # slot count
.data 0x006C56F6
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5730
.data 0x00000001
.binary 0C # slot count
.data 0x006C58B6
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C58F0
.data 0x00000001
.binary 0C # slot count
.data 0x006C5A85
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5AC1
.data 0x00000001
.binary 0C # slot count
.data 0x006C5BB2
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5BEC
.data 0x00000001
.binary 0C # slot count
.data 0x006C5D72
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5DAC
.data 0x00000001
.binary 0C # slot count
.data 0x006C5F32
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C5F6C
.data 0x00000001
.binary 0C # slot count
.data 0x006C60F2
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C612C
.data 0x00000001
.binary 0C # slot count
.data 0x006C6346
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6381
.data 0x00000001
.binary 0C # slot count
.data 0x006C6505
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6541
.data 0x00000001
.binary 0C # slot count
.data 0x006C6632
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C666C
.data 0x00000001
.binary 0C # slot count
.data 0x006C67F2
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C682C
.data 0x00000001
.binary 0C # slot count
.data 0x006C69B2
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C69EC
.data 0x00000001
.binary 0C # slot count
.data 0x006C6B87
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6BB8
.data 0x00000004
.data 0x0000005D # memcard block count
.data 0x006C6C3A
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6C74
.data 0x00000001
.binary 0C # slot count
.data 0x006C6E82
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C6EBC
.data 0x00000001
.binary 0C # slot count
.data 0x006C70B9
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C70F3
.data 0x00000001
.binary 0C # slot count
.data 0x006C7A46
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C7D66
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x006C7D7C
.data 0x00000001
.binary 0C # slot count
.data 0x006C7DC0
.data 0x00000004
.data 0x00022FC4 # total file size
.data 0x0077CC72
.data 0x00000004
.data 0x00022FB4 # bgm_test_songs_unlocked offset
# Signature check on all save files (rewritten as loop)
.data 0x006C1C69
.deltaof sig_check_begin, sig_check_end
sig_check_begin:
mov edx, 0xC87ED5B1 # Expected signature value
add eax, 0x04E8 # &char_file_list->chars[0].part2.signature
mov ecx, 0x0C # slot count
again:
cmp dword [eax], 0 # signature == 0 (no char in slot)
je sig_ok
cmp dword [eax], edx # signature == expected value
jne sig_bad
sig_ok:
add eax, 0x2EA4 # Advance to next slot
dec ecx
jnz again
xor eax, eax # All signatures OK (eax = 0)
jmp sig_check_end
sig_bad:
xor eax, eax # Bad signature (eax = 1)
inc eax
jmp sig_check_end
.binary CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
sig_check_end: # 006C1CB2
# Send slot count in E3 command
.data 0x0046EC10 # TDataProtocol::send_E3_for_index
.deltaof send_slot_count_in_E3_begin, send_slot_count_in_E3_end
send_slot_count_in_E3_begin:
# ecx = this (TDataProtocol*)
# [esp + 4] = slot_index
push 0
push dword [esp + 8] # slot_index
push 0x0C # slot count
push 0x00E30010
mov eax, esp
push 0x10
push eax
mov eax, [ecx]
call [eax + 0x20] # this->send_command(&cmd, 0x10) // ret 8
add esp, 8
mov eax, 0x006C1ABC
call eax # set_current_char_slot(slot_index) // ret 0
add esp, 8
ret 4
send_slot_count_in_E3_end:
# Show slot number in each menu item
.data 0x00401D57
.deltaof show_slot_number_begin, show_slot_number_end
show_slot_number_begin:
# Original call (sprintf(line_buf, "LV%d", preview_info->visual.disp.level + 1))
lea edx, [esp + 0x02C4]
mov ebx, [ebx + 8]
inc ebx
push ebx
mov ecx, esi
push edx
mov eax, 0x00402604
call eax
# Find the end of the string
lea eax, [esp + 0x02C4]
show_slot_number_strend_again:
cmp word [eax], 0
je show_slot_number_strend_done
add eax, 2
jmp show_slot_number_strend_again
show_slot_number_strend_done:
# Format the slot number and append it to the string
mov ecx, [0x00A38BD0] # scroll_bar = TAdScrollBarXb_objs[0]
mov ecx, [ecx + 0xAC] # ecx = scroll_bar->selection_state[0].scroll_offset
lea ecx, [ecx + ebp + 1]
push ecx # Slot number (scroll_offset + z)
call get_show_slot_number_suffix_fmt
.binary 20002800230025006400290020000000 # L" (#%d) "
get_show_slot_number_suffix_fmt:
push eax # Destination buffer
mov eax, 0x00835578 # _swprintf
call eax
add esp, 0x0C
jmp show_slot_number_end
.zero 0x96
show_slot_number_end: # 00401E4D
# End static patches
.data 0x00000000
.data 0x00000000
update_existing_char_file_list:
# Replace the existing character list with an appropriately-longer one. This
# part does not need to be done if the patch is applied statically to the
# executable; this is only necessary when used as a server patch because the
# character list is already allocated at the time the patch is applied.
push 0x00022FC4 # total file size
mov eax, 0x00835915 # operator_new
call eax
add esp, 4
mov edx, [0x00A939C4] # edx = old char_file_list
mov [0x00A939C4], eax
mov ecx, [edx + 0xBA94] # Copy bgm_test_songs_unlocked_high to new file
mov [eax + 0x00022FB4], ecx
mov ecx, [edx + 0xBA98] # Copy bgm_test_songs_unlocked_low to new file
mov [eax + 0x00022FB8], ecx
mov ecx, [edx + 0xBA9C] # Copy save_count to new file
mov [eax + 0x00022FBC], ecx
mov ecx, [edx + 0xBAA0] # Copy round2_seed to new file
mov [eax + 0x00022FC0], ecx
add eax, 4
add edx, 4
mov ecx, 0xBA90
call memcpy # Copy the existing 4 characters over
mov eax, [0x00A939C4]
add eax, 0xBA94
mov ecx, 4
clear_next_char:
cmp ecx, 0x0C # slot count
jge clear_next_char_done
lea edx, [eax + 0x2EA4] # edx = ptr to next char (or footer)
clear_next_char_write_again:
mov dword [eax], 0
add eax, 4
cmp eax, edx
jl clear_next_char_write_again
clear_next_char_done:
# Call eh_vector_constructor_iterator(
# &char_file_list.chars[4],
# sizeof(char_file_list.chars[0]),
# countof(char_file_list.chars) - 4,
# PSOCharacterFile::init,
# PSOCharacterFile::destroy)
push 0x006C197C # PSOCharacterFile::destroy
push 0x006C182C # PSOCharacterFile::init
push 0x08 # slot count - 4
push 0x2EA4 # sizeof(PSOCharacterFile)
mov eax, [0x00A939C4]
add eax, 0xBA94
push eax
mov eax, 0x00835E86
call eax
# Fix the file's checksum
mov eax, [0x00A939C4]
mov ecx, 0x006C2738
jmp ecx # PSOBBCharacterFileList::checksum(char_file_list)
update_existing_char_file_list_memcard:
# Allocate a new memory card file area and copy the data there too. It seems
# Sega didn't fully strip out the local saving code from PSOBB; instead, they
# just made it write to a heap-allocated buffer. Since the file is much
# bigger now, we also have to make that heap-allocated buffer larger. We add
# a few "blocks" on the end, since the original code in the game does that
# too, but it's probably not strictly necessary.
# Like the above, this part is not necessary if this patch is statically
# applied to the executable.
mov eax, 0x00022FC4 # total file size
add eax, 0x0000FFFF
and eax, 0xFFFFC000
push eax
mov eax, 0x0084F258
call eax # malloc10(total file size)
add esp, 4
mov [0x00A939AC], eax
mov edx, [0x00A939C4]
mov ecx, 0x00022FC4 # total file size
jmp memcpy
@@ -0,0 +1,103 @@
# This patch causes the client not to generate its own EXP text and instead use
# the EXP values generated by the server when showing the purple text for enemy
# deaths. This makes EXP gained via EXP share visible, as well as makes
# fractional EXP multiplers (in config.json) display properly.
.meta name="Server EXP display"
.meta description=""
.meta hide_from_patches_menu
entry_ptr:
reloc0:
.offsetof start
start:
call install_hook
call apply_static_patches
ret
install_hook:
pop ecx
push 0 # Write address instead of a call/jmp opcode
push 0x00A0DC54
call get_code_size
.deltaof handle_6xBF_start, handle_6xBF_end
get_code_size:
pop eax
push dword [eax]
call handle_6xBF_end
handle_6xBF_start: # [std](G_6xBF* cmd @ [esp + 4]) -> void
mov edx, [esp + 4]
mov ecx, [0x00A9A074] # local_client_id
cmp [edx + 2], cx
jne skip_text
cmp byte [edx + 1], 3
jl skip_text
movzx eax, word [edx + 8] # cmd.from_enemy_id
cmp eax, 0x1000
jl skip_text
cmp eax, 0x1B50
jge skip_text
call get_enemy_entity
test eax, eax
jnz enemy_entity_ok
# Use player entity if enemy entity is already gone
mov eax, 0x0068D618
xchg eax, ecx
call ecx # eax = TObjPlayer::for_client_id(local_client_id); conveniently, this function preserves all regs except eax
enemy_entity_ok:
push 0x0000FFFF # entity_id; ignored by TFontSmallTask if not a player
push dword [edx + 4] # amount = cmd.amount
push 0x00976380 # prefix = L"EXP"
push 0x14
push 0x14
push 0xFFFF00FF # color (ARGB)
add eax, 0x300
push eax # position
mov eax, 0x0078B8E8
call eax # TFontSmallTask___new__(...)
add esp, 0x1C
skip_text:
mov eax, 0x0069292C # Original handle_6xBF
jmp eax # original_handle_6xBF(cmd)
get_enemy_entity:
.include GetEnemyEntity-59NJ
ret
handle_6xBF_end:
push ecx
.include WriteCallToCode-59NJ
apply_static_patches:
.include WriteCodeBlocksBB
.data 0x0078827D
.deltaof disable_kill_enemy_callsite_start, disable_kill_enemy_callsite_end
disable_kill_enemy_callsite_start:
nop
nop
nop
nop
nop
disable_kill_enemy_callsite_end:
.data 0x00777381
.deltaof disable_exp_steal_callsite_start, disable_exp_steal_callsite_end
disable_exp_steal_callsite_start:
add esp, 0x0C # Original function has `ret 0x0C`
nop
nop
disable_exp_steal_callsite_end:
.data 0x00000000
.data 0x00000000
@@ -10,6 +10,8 @@
.meta description=""
.meta hide_from_patches_menu
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
@@ -17,7 +19,7 @@ start:
.include WriteCodeBlocksBB
# Patch 1: rewrite item_is_stackable
.data 0x005C502C
.data <VERS 0x005C5020 0x005C502C>
.deltaof item_is_stackable_start, item_is_stackable_end
item_is_stackable_start:
@@ -32,7 +34,7 @@ item_is_stackable_start:
push eax
mov ecx, esp
.binary E8EC130100 # call max_stack_size_for_tool_start
.binary <VERS E8D8130100 E8EC130100> # call max_stack_size_for_tool_start
pop ecx
cmp eax, 1
jg return_1
@@ -48,7 +50,7 @@ return_1:
item_is_stackable_end:
# Patch 2: rewrite max_stack_size_for_tool
.data 0x005D6430
.data <VERS 0x005D6410 0x005D6430>
.deltaof max_stack_size_for_tool_start, max_stack_size_for_tool_end
max_stack_size_for_tool_start:
@@ -0,0 +1,146 @@
# Currently beta quality, map objects that fade like boxes, and Pioneer's
# background billboards and elevators still have regular draw distance.
# TODO: 90% of stuff is included, bring home the last 10%.
.meta name="Draw Distance"
.meta description="Extends the draw\ndistance of many\nobjects"
entry_ptr:
reloc0:
.offsetof start
write_call_func:
.include WriteCallToCode-59NJ
start:
mov eax, 0x41800000 # Environment clip distance mod 16.0f
mov [0x0097D198], eax # This affects mostly static map objects
mov [0x0097D19C], eax
mov [0x00097D1A0], eax
mov ax, 0x9090
mov [0x00689BC7], ax # Players draw distance 10000.0f always
mov eax, 0x41000000 # Use newly acquired skipped branch room
mov [0x00689BD1], eax # to store our float multiplier
call patch_func_1 # Floor items
call patch_func_2 # Whole bunch of stuff, including NPCs
call patch_func_3 # Duplicate function from above, reuse same hook
call patch_func_4 # TODO: Which objects this affects?
call patch_func_5 # TODO: This one too?
call patch_func_6 # TODO: And this one?
ret
# Floor items
patch_func_1:
pop ecx
push 8
push 0x005C525B
call get_code_size1
.deltaof patch_code1, patch_code_end1
get_code_size1:
pop eax
push dword [eax]
call patch_code_end1
patch_code1:
mov edx, [esp + 0x18]
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x14]
fmulp st1, st0
ret
patch_code_end1:
push ecx
jmp write_call_func
# Whole bunch of stuff, including NPCs
patch_func_2:
pop ecx
push 9
push 0x007BB21E
call get_code_size2
.deltaof patch_code2, patch_code_end2
get_code_size2:
pop eax
push dword [eax]
call patch_code_end2
patch_code2:
test eax, 0x400
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x2C]
fmulp st1, st0
ret
patch_code_end2:
push ecx
jmp write_call_func
# Duplicate function from above, reuse same hook
patch_func_3:
mov eax, dword [0x007BB21F]
add eax, 0x002A1C74
mov dword [0x00518843], eax
mov byte [0x00518842], 0xE8
mov dword [0x00518847], 0x90909090
ret
# TOComputerMachine01
patch_func_4:
pop ecx
push 7
push 0x00616FF4
call get_code_size4
.deltaof patch_code4, patch_code_end4
get_code_size4:
pop eax
push dword [eax]
call patch_code_end4
patch_code4:
lea edx, [edi + 0x38]
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x14]
fmulp st1, st0
ret
patch_code_end4:
push ecx
jmp write_call_func
# TObjCamera
patch_func_5:
pop ecx
push 6
push 0x006439A8
call get_code_size5
.deltaof patch_code5, patch_code_end5
get_code_size5:
pop eax
push dword [eax]
call patch_code_end5
patch_code5:
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x28]
fmulp st1, st0
fchs st0
ret
patch_code_end5:
push ecx
jmp write_call_func
# TODO: And this one?
patch_func_6:
pop ecx
push 6
push 0x0065B959
call get_code_size6
.deltaof patch_code6, patch_code_end6
get_code_size6:
pop eax
push dword [eax]
call patch_code_end6
patch_code6:
mov ebp, ecx
fld st0, dword [0x00689BD1]
fld st0, dword [esp + 0x30]
fmulp st1, st0
ret
patch_code_end6:
push ecx
jmp write_call_func
@@ -0,0 +1,258 @@
.meta name="DMC"
.meta description="Mitigates effects\nof enemy health\ndesync"
.meta client_flag="0x0000001000000000"
entry_ptr:
reloc0:
.offsetof start
write_call_to_code_multi:
.include WriteCallToCodeMulti-59NJ
write_address_of_code:
.include WriteAddressOfCode-59NJ
start:
# Replace 6x09 with 6xE4 in subcommand handler table
mov dword [0x00A0DC30], 0x000600E4 # subcommand=0xE4, flags=6
push 0x00A0DC34
call +4
.deltaof handle_6xE4_start, handle_6xE4_end
pop eax
push dword [eax]
call handle_6xE4_end
handle_6xE4_start: # (G_6xE4* cmd @ [esp + 4]) -> void
push ebx
push esi
push edi
test byte [0x00AA8DFC], 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
movzx eax, word [ebx + 2]
.include GetEnemyEntity-59NJ # auto* ene = get_enemy_entity(cmd->header.entity_id);
push eax
movzx eax, word [ebx + 2]
and eax, 0x0FFF
imul eax, eax, 0x0C
add eax, [0x00AADE38] # 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
mov edx, 0x00801150
call edx # send_and_handle_60(&out_cmd);
add esp, 0x10
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 edx, eax # edx = ene_st
mov eax, [esp] # eax = ene
test eax, eax
jz handle_6xE4_return_pop_ene
mov ecx, eax
push edx
mov edx, [ecx]
call [edx + 0x148] # ene->vtable[0x52](ene, &st);
handle_6xE4_return_pop_ene:
add esp, 4
handle_6xE4_return:
pop edi
pop esi
pop ebx
ret
handle_6xE4_end:
call write_address_of_code
# Write TObjectV00b421c0::incr_hp_with_sync
push 5
push 0x00775224 # TObjectV00b421c0::v18_accept_hit (presumably Resta) - this is add_hp, not subtract_hp!
push 5
push 0x00778063 # TObjectV00b421c0::subtract_hp_if_not_in_state_2
push 5
push 0x00777AB2 # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00777B2B # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00777BFC # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00777C75 # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00776D2D # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x007769C2 # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x0077683C # TObjectV00b421c0::v19_handle_hit_special_effects
push 5
push 0x00776502 # TObjectV00b421c0::v19_handle_hit_special_effects (Devil's/Demon's)
push 5
push 0x00775B57 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00775A23 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x007757F0 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00775606 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x007754BC # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00774E3D # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00774CD6 # TObjectV00b421c0::v18_accept_hit
push 5
push 0x00774713 # TObjectV00b421c0::v17
push 18
call +4
.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
on_add_or_subtract_hp_start: # (TObjectV00b421c0* this @ ecx, int16_t amount @ [esp + 4]) -> bool @ eax
test byte [0x00AA8DFC], 0x80
jz on_add_or_subtract_hp_skip_send
movzx eax, word [ecx + 0x1C] # ene->entity_id
cmp eax, 0x1000
jl on_add_or_subtract_hp_skip_send
cmp eax, 0x1B50
jge on_add_or_subtract_hp_skip_send
and eax, 0x0FFF
imul eax, eax, 0x0C
add eax, [0x00AADE38] # eax = state_for_enemy(cmd->header.entity_id)
sub esp, 0x10
mov word [esp], 0x04E4
mov dx, [ecx + 0x1C]
mov [esp + 0x02], dx # cmd.entity_id
mov dx, [esp + 0x14]
cmp dword [esp + 0x10], 0x00775229 # Check if callsite is add_hp
jne on_add_or_subtract_hp_skip_negate_amount
neg dx
on_add_or_subtract_hp_skip_negate_amount:
mov [esp + 0x04], dx # cmd.hit_amount
mov dx, [eax + 6]
mov [esp + 0x06], dx # cmd.total_damage_before_hit
mov dx, [ecx + 0x0334]
mov [esp + 0x08], dx # cmd.current_hp
mov dx, [ecx + 0x02BC]
mov [esp + 0x0A], dx # cmd.max_hp
mov dword [esp + 0x0C], 0xBF800000 # cmd.factor
cmp dword [esp + 0x10], 0x00776507 # Check if callsite is Devil's/Demon's
jne on_add_or_subtract_hp_not_proportional
# esp is 0x18 down from where it is in caller's context
mov edx, 100
sub edx, [esp + 0x24] # edx = (100 - special_amount)
push edx
fild st0, dword [esp] # current_hp_factor = static_cast<float>(100 - special_amount)
fmul st0, dword [esp + 0x54] # *= weapon_reduction_factor
mov dword [esp], 0x42C80000 # 100.0f
fdiv st0, dword [esp]
add esp, 4
fstp dword [esp + 0x0C], st0 # cmd.factor = ((100 - special_amount) * weapon_reduction_factor) / 100
on_add_or_subtract_hp_not_proportional:
mov edx, esp
push ecx
push 0x10
push edx
mov ecx, [0x00AA8E04]
mov edx, 0x007D4CBC
call edx # send_60(root_protocol, &cmd, sizeof(cmd));
pop ecx
add esp, 0x10
on_add_or_subtract_hp_skip_send:
mov eax, 0x007781F0 # subtract_hp
mov edx, 0x007781B0 # add_hp
cmp dword [esp], 0x00775229 # Check if callsite is add_hp
cmove eax, edx
jmp eax
on_add_or_subtract_hp_end:
call write_call_to_code_multi
push 5
push 0x0078864B
push 1
call +4
.deltaof on_6x0A_patch_start, on_6x0A_patch_end
pop eax
push dword [eax]
call on_6x0A_patch_end
on_6x0A_patch_start: # (TObjectV00b421c0* this @ ecx, int16_t amount @ [esp + 4]) -> bool @ eax
test byte [0x00AA8DFC], 0x80
jz on_6x0A_patch_skip_write
mov [esp + 0x0A], cx
on_6x0A_patch_skip_write:
ret
on_6x0A_patch_end:
call write_call_to_code_multi
ret
@@ -1,45 +1,47 @@
.meta name="Enemy HP bars"
.meta description="Shows HP bars in\nenemy info windows"
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksBB
.data 0x007318DD
.data <VERS 0x0073197D 0x007318DD>
.data 6
.binary 81E2FDFFFFFF
.data 0x00731F2F
.data <VERS 0x00731FCF 0x00731F2F>
.data 1
.binary FA
.data 0x009F2DA4
.data <VERS 0x009F0DA4 0x009F2DA4>
.data 4
.data 0x42480000
.data 0x009F2DAC
.data <VERS 0x009F0DAC 0x009F2DAC>
.data 4
.data 0x41C00000
.data 0x009F2DD4
.data <VERS 0x009F0DD4 0x009F2DD4>
.data 4
.data 0x42480000
.data 0x009F2DDC
.data <VERS 0x009F0DDC 0x009F2DDC>
.data 4
.data 0x41C00000
.data 0x009F2E04
.data <VERS 0x009F0E04 0x009F2E04>
.data 4
.data 0x42480000
.data 0x009F2E0C
.data <VERS 0x009F0E0C 0x009F2E0C>
.data 4
.data 0x41C00000
.data 0x009F2E34
.data <VERS 0x009F0E34 0x009F2E34>
.data 4
.data 0x42480000
.data 0x009F2E3C
.data <VERS 0x009F0E3C 0x009F2E3C>
.data 4
.data 0x41C00000
.data 0x009F2E64
.data <VERS 0x009F0E64 0x009F2E64>
.data 4
.data 0x42200000
.data 0x009F2E80
.data <VERS 0x009F0E80 0x009F2E80>
.data 4
.data 0xFF00FF15
.data 0x00000000
@@ -1,17 +0,0 @@
# This function implements $exit in a game when no quest is loaded.
.meta name="Exit anywhere"
.meta description=""
.meta hide_from_patches_menu
entry_ptr:
reloc0:
.offsetof start
start:
xor eax, eax
mov [0x00A95624], eax # is_in_quest = false
mov [0x00A955E0], eax # dat_source_type = NONE
inc eax
mov [0x00AAE6D4], ax # should_leave_game = true
ret
@@ -0,0 +1,19 @@
# This function implements $exit in a game when no quest is loaded.
.meta name="Exit anywhere"
.meta description=""
.meta hide_from_patches_menu
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
start:
xor eax, eax
mov [<VERS 0x00A931A4 0x00A95624>], eax # is_in_quest = false
mov [<VERS 0x00A93160 0x00A955E0>], eax # dat_source_type = NONE
inc eax
mov [<VERS 0x00AAC254 0x00AAE6D4>], ax # should_leave_game = true
ret
@@ -1,19 +1,21 @@
.meta name="Fast tekker"
.meta description="Skips wind-up sound\nat tekker window"
.versions 59NJ 59NL
entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksBB
.data 0x006DA113
.data <VERS 0x006DA14B 0x006DA113>
.deltaof patch1_start, patch1_end
patch1_start:
mov dword [edi + 0x14C], 1
patch1_end:
.data 0x006DA130
.data <VERS 0x006DA168 0x006DA130>
.deltaof patch2_start, patch2_end
patch2_start:
nop
@@ -0,0 +1,34 @@
.meta name="MAG alert"
.meta description="Plays a sound when\nyour MAG is hungry"
entry_ptr:
reloc0:
.offsetof start
start:
pop ecx
push 6
push 0x005D91BE
call get_code_size
.deltaof patch_code, patch_code_end
get_code_size:
pop eax
push dword [eax]
call patch_code_end
patch_code: # [eax] (TItemMag* this @ ecx) -> void
mov dword [ecx + 0x01B8], eax
mov eax, [ecx + 0x00F8]
movzx eax, word [eax + 0x001C] # eax = this->owner_player->entity_id
cmp [0x00A9A074], eax
jne patch_code_skip_sound
push 0
push 0
push 0
push 0xAC
mov eax, 0x00815020
call eax
add esp, 0x10
patch_code_skip_sound:
ret
patch_code_end:
push ecx
.include WriteCallToCode-59NJ
@@ -0,0 +1,43 @@
# Original patch by Soly, in Blue Burst Patch Project
# https://github.com/Solybum/Blue-Burst-Patch-Project
.meta name="No rare selling"
.meta description="Stops you from accidentally\nselling rares to vendors"
entry_ptr:
reloc0:
.offsetof start
start:
# This works by setting the item price to zero if it's rare, which causes
# the game to prevent you from selling the item. For armors and weapons, this
# is easy because there are easily-patchable opcodes within branches that
# return a constant price for rare items.
xor eax, eax
mov [0x005D258F], eax # Rare armors
mov [0x005D26D1], eax # Unidentified weapons
mov [0x005D26E6], eax # Rare weapons
# For tools, it's harder to implement this, because the price comes from the
# ItemPMT tools table and there is no branch for rares. Still, we can add a
# branch to a stub to handle tools.
pop ecx
push 5
push 0x005D2508
call get_code_size
.deltaof patch_code, patch_code_end
get_code_size:
pop eax
push dword [eax]
call patch_code_end
patch_code:
# TODO: It'd be nice to have something like WriteJumpToAndFromCode, since
# this hook is supposed to return to a different place than where it was
# called, hence this mov [esp].
mov dword [esp], 0x005D2556
xor edi, edi
test byte [eax + 0x14], 0x80 # flags & 0x80 = is rare
cmovz edi, [eax + 0x10] # Use price from table if not rare
ret
patch_code_end:
push ecx
.include WriteCallToCode-59NJ
@@ -0,0 +1,230 @@
# Original patch by Soly, in Blue Burst Patch Project
# https://github.com/Solybum/Blue-Burst-Patch-Project
.meta name="Palette"
.meta description="Enables the alternate action\npalette for number keys"
entry_ptr:
reloc0:
.offsetof start
write_call_func:
.include WriteCallToCode-59NJ
start:
mov al, 0xEB
mov [0x0068A7A5], al # SecondaryPaletteAttack1
xor al, al
mov [0x006A11B7], al # SecondaryPaletteAttack2
mov [0x006A0CB7], al # SecondaryPaletteAttack3
call patch_func_1 # GetCurrentPalette
call patch_func_2 # CheckHotkey1_1
call patch_func_3 # CheckHotkey1_2
call patch_func_4 # CheckHotkey2_1
call patch_func_5 # CheckHotkey2_2
call patch_func_6 # CheckHotkey3_1
call patch_func_7 # CheckHotkey3_2
jmp write_code_blocks # UnsetHotkey1, UnsetHotkey2, SetHotkey
# GetCurrentPalette
patch_func_1:
pop ecx
push 8
push 0x00748990
call get_code_size1
.deltaof patch_code1, patch_code_end1
get_code_size1:
pop eax
push dword [eax]
call patch_code_end1
patch_code1:
mov edx, [ebp - 0x14]
mov edx, [edx + 0x2C]
movzx edx, byte [edx + 0x62]
test edx, edx
setnz byte [0x00748B1B]
mov edx, edi
and edx, 0xFF
ret
patch_code_end1:
push ecx
jmp write_call_func
# CheckHotkey1_1
patch_func_2:
pop ecx
push 5
push 0x007489DE
call get_code_size2
.deltaof patch_code2, patch_code_end2
get_code_size2:
pop eax
push dword [eax]
call patch_code_end2
patch_code2:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx edx, byte [eax + esi * 4 + 0x04] # main palette
ret
movzx edx, byte [eax + esi * 4 + 0x3C] # alt palette
ret
patch_code_end2:
push ecx
jmp write_call_func
# CheckHotkey1_2
patch_func_3:
pop ecx
push 5
push 0x007489ED
call get_code_size3
.deltaof patch_code3, patch_code_end3
get_code_size3:
pop eax
push dword [eax]
call patch_code_end3
patch_code3:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx ecx, byte [eax + ecx * 2 + 0x05] # main palette
ret
movzx ecx, byte [eax + ecx * 2 + 0x3D] # alt palette
ret
patch_code_end3:
push ecx
jmp write_call_func
# CheckHotkey2_1
patch_func_4:
pop ecx
push 5
push 0x00748A88
call get_code_size4
.deltaof patch_code4, patch_code_end4
get_code_size4:
pop eax
push dword [eax]
call patch_code_end4
patch_code4:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx edx, byte [edx + ebx * 4 + 0x04] # main palette
ret
movzx edx, byte [edx + ebx * 4 + 0x3C] # alt palette
ret
patch_code_end4:
push ecx
jmp write_call_func
# CheckHotkey2_2
patch_func_5:
pop ecx
push 5
push 0x00748A97
call get_code_size5
.deltaof patch_code5, patch_code_end5
get_code_size5:
pop eax
push dword [eax]
call patch_code_end5
patch_code5:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx ecx, byte [edx + eax * 2 + 0x05] # main palette
ret
movzx ecx, byte [edx + eax * 2 + 0x3D] # alt palette
ret
patch_code_end5:
push ecx
jmp write_call_func
# CheckHotkey3_1
patch_func_6:
pop ecx
push 5
push 0x007103D3
call get_code_size6
.deltaof patch_code6, patch_code_end6
get_code_size6:
pop eax
push dword [eax]
call patch_code_end6
patch_code6:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx ecx, byte [eax + edx * 4 + 0x04] # main palette
ret
movzx ecx, byte [eax + edx * 4 + 0x3C] # alt palette
ret
patch_code_end6:
push ecx
jmp write_call_func
# CheckHotkey3_2
patch_func_7:
pop ecx
push 5
push 0x007103DC
call get_code_size7
.deltaof patch_code7, patch_code_end7
get_code_size7:
pop eax
push dword [eax]
call patch_code_end7
patch_code7:
cmp byte [0x00748B1B], 0
jnz +0x06
movzx ecx, byte [eax + edx * 4 + 0x05] # main palette
ret
movzx ecx, byte [eax + edx * 4 + 0x3D] # alt palette
ret
patch_code_end7:
push ecx
jmp write_call_func
write_code_blocks:
.include WriteCodeBlocksBB
.data 0x00748A05
.deltaof code_block1_start, code_block1_end
# UnsetHotkey1
code_block1_start:
push dword [0x00748B1B]
push eax
mov eax, 0x0068CE4C # SetPaletteHotkey
call eax
.binary 909090909090909090
code_block1_end:
.data 0x00748AAB
.deltaof code_block2_start, code_block2_end
# UnsetHotkey2
code_block2_start:
push dword [0x00748B1B]
push eax
mov eax, 0x0068CE4C # SetPaletteHotkey
call eax
.binary 909090909090909090
code_block2_end:
.data 0x00748B0A
.deltaof code_block3_start, code_block3_end
# SetHotkey
code_block3_start:
mov eax, [ebp - 0x24]
mov ecx, [ebp - 0x28]
movzx ebx, word [eax]
movzx edx, word [eax + 0x02]
push edx
push ebx
push esi
.binary 6800000000 # tmpCurrentPalette = 0x00748B1B
push 0
mov eax, 0x0068CE4C # SetPaletteHotkey
call eax
.binary 90909090909090909090909090909090
code_block3_end:
.data 0x00000000
.data 0x00000000
@@ -0,0 +1,44 @@
# (uint16_t entity_id @ eax) -> TObjectV00b421c0* @ eax
# Preserves all registers except eax
get_enemy_entity:
push esi
push edi
push edx
push ecx
xor edx, edx
xchg edx, eax
cmp edx, 0x1000
jl done
cmp edx, 0x4000
jge done
mov esi, [0x00AABCE8] # bs_low = next_player_entity_index
mov edi, [0x00AABCE4]
lea edi, [edi + esi - 1] # bs_high = next_player_entity_index + next_enemy_entity_index - 1
bs_again:
cmp esi, edi
jge bs_done
lea ecx, [esi + edi]
shr ecx, 1
mov eax, [ecx * 4 + 0x00AAB2A0] # all_entities[ecx]
cmp [eax + 0x1C], dx
jge bs_not_less
lea esi, [ecx + 1]
jmp bs_again
bs_not_less:
mov edi, ecx
jmp bs_again
bs_done:
mov eax, [esi * 4 + 0x00AAB2A0] # all_entities[bs_low]
test eax, eax
je done
xor ecx, ecx
cmp [eax + 0x1C], dx
cmovne eax, ecx
done:
pop ecx
pop edx
pop edi
pop esi
@@ -0,0 +1,42 @@
# This file defines the following function:
# write_address_of_code(
# const void* patch_code,
# size_t patch_code_size,
# void** ptr_addr);
# This function allocates memory for patch_code, copies patch_code to that
# memory, then writes the address of the allocated code at the specified
# pointer. The allocated memory is never freed.
# This function pops its arguments off the stack before returning.
write_call_to_code:
# [esp + 0x04] = code ptr
# [esp + 0x08] = code size
# [esp + 0x0C] = ptr addr
# Allocate memory for the copied code
mov ecx, [0x00AA8F84]
push dword [esp + 0x08]
mov eax, 0x007A984C
call eax # malloc7
test eax, eax
je done
# Copy the code to the newly-allocated memory
# eax = dest pointer (from malloc7 call above)
mov edx, [esp + 0x04] # edx = source pointer
mov ecx, [esp + 0x08] # ecx = source size
push ebx
memcpy_again:
dec ecx
mov bl, [edx + ecx] # Copy one byte from source to dest
mov [eax + ecx], bl
test ecx, ecx
jne memcpy_again
pop ebx
# Write the address
mov ecx, [esp + 0x0C]
mov [ecx], eax
done:
ret 0x0C
@@ -0,0 +1,76 @@
# This file defines the following function:
# write_call_to_code(
# const void* patch_code,
# size_t patch_code_size,
# void* call_opcode_address,
# ssize_t call_opcode_bytes);
# This function allocates memory for patch_code, copies patch_code to that
# memory, then writes a call or jmp opcode to call_opcode_address that calls
# the code in the allocated memory region. The allocated memory is never freed.
# call_opcode_bytes specifies how many bytes at the callsite should be
# overwritten. This value must be at least 5; the first 5 bytes are overwritten
# with the call/jmp opcode itself; the rest are overwritten with nop opcodes.
# If call_opcode_bytes is positive, a call opcode is written; if it's negative,
# a jmp opcode is written.
# This function pops its arguments off the stack before returning.
write_call_to_code:
# [esp + 0x04] = code ptr
# [esp + 0x08] = code size
# [esp + 0x0C] = jump callsite
# [esp + 0x10] = callsite size (if zero, write the address instead of a call)
# Allocate memory for the copied code
mov ecx, [0x00AA8F84]
push dword [esp + 0x08]
mov eax, 0x007A984C
call eax # malloc7
test eax, eax
je done
# Copy the code to the newly-allocated memory
# eax = dest pointer (from malloc7 call above)
mov edx, [esp + 0x04] # edx = source pointer
mov ecx, [esp + 0x08] # ecx = source size
push ebx
memcpy_again:
dec ecx
mov bl, [edx + ecx] # Copy one byte from source to dest
mov [eax + ecx], bl
test ecx, ecx
jne memcpy_again
pop ebx
mov edx, [esp + 0x0C] # edx = jump callsite
# If the callsite size is zero, just write the address directly
cmp dword [esp + 0x10], 0
jne write_call_or_jmp
mov [edx], eax
jmp done
# Write the call or jmp opcode
write_call_or_jmp:
lea ecx, [eax - 5]
sub ecx, edx # ecx = (dest code addr) - (jump callsite) - 5
cmp dword [esp + 0x10], 0
setl al
or al, 0xE8
mov [edx], al # Write E8 (call), or E9 (jmp) if size was negative
mov [edx + 1], ecx # Write delta
# Write as many nops after the call opcode as necessary
mov ecx, 5
mov eax, [esp + 0x10]
cmp eax, 0
jge write_nop_again
neg eax
write_nop_again:
cmp ecx, eax
jge done
mov byte [edx + ecx], 0x90
inc ecx
jmp write_nop_again
done:
ret 0x10
@@ -0,0 +1,83 @@
# This file defines the following function:
# void [/std] write_call_to_code(
# const void* patch_code @ [esp + 0x04],
# size_t patch_code_size @ [esp + 0x08],
# size_t call_count @ [esp + 0x0C],
# void* call_opcode_address @ [esp + 0x10],
# ssize_t call_opcode_bytes @ [esp + 0x14],
# ...);
# This function allocates memory for patch_code, copies patch_code to that
# memory, then writes a call or jmp opcode to call_opcode_address that calls
# the code in the allocated memory region. The allocated memory is never freed.
# call_opcode_bytes specifies how many bytes at the callsite should be
# overwritten. This value must be at least 5; the first 5 bytes are overwritten
# with the call/jmp opcode itself; the rest are overwritten with nop opcodes.
# This function pops its arguments off the stack before returning (including
# all the varargs).
write_call_to_code:
# [esp + 0x04] = code ptr
# [esp + 0x08] = code size
# [esp + 0x0C] = callsite count
# [esp + 0x10] = callsite address
# [esp + 0x14] = callsite size
# ... (further callsite address/size pairs)
# Allocate memory for the copied code
mov ecx, [0x00AA8F84]
push dword [esp + 0x08]
mov eax, 0x007A984C
call eax # malloc7
test eax, eax
je done
# Copy the code to the newly-allocated memory
# eax = dest pointer (from malloc7 call above)
mov edx, [esp + 0x04] # edx = source pointer
mov ecx, [esp + 0x08] # ecx = source size
push ebx
memcpy_again:
dec ecx
mov bl, [edx + ecx] # Copy one byte from source to dest
mov [eax + ecx], bl
test ecx, ecx
jne memcpy_again
pop ebx
# Write the call opcodes
xchg ebx, [esp + 0x0C] # Save ebx; get callsite count
mov [esp - 0x08], esi
mov [esp - 0x0C], eax
mov esi, 0x10 # Stack offset of first callsite pair
next_callsite:
mov edx, [esp + esi] # edx = jump callsite
lea ecx, [eax - 5]
sub ecx, edx # ecx = (dest code addr) - (jump callsite) - 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 + esi + 4]
write_nop_again:
cmp ecx, eax
jge this_callsite_done
mov byte [edx + ecx], 0x90
inc ecx
jmp write_nop_again
this_callsite_done:
mov eax, [esp - 0x0C]
add esi, 8
dec ebx
jnz next_callsite
mov ecx, esi
mov ebx, [esp + 0x0C]
mov esi, [esp - 0x08]
done:
mov eax, [esp]
add esp, ecx
jmp eax