diff --git a/src/game_api/memory.cpp b/src/game_api/memory.cpp index 60aab4615..4435cd8e0 100644 --- a/src/game_api/memory.cpp +++ b/src/game_api/memory.cpp @@ -82,15 +82,18 @@ LPVOID alloc_mem_rel32(size_t addr, size_t size) { const size_t limit_addr = Memory::get().exe_ptr; LPVOID new_array = nullptr; - size_t test_addr; - test_addr = addr + 0x10000; // dunno why - if (test_addr <= INT32_MAX) - test_addr = 8; + size_t test_addr = addr + 0x10000; // dunno why, without this it can get address that is more than 32bit away + + if (test_addr <= INT32_MAX) // redunded check as you probably won't get address that is less than INT32_MAX from "zero" + test_addr = 8; // but i did it just in case so you can't get overflow, also can't feed 0 to the VirtualAlloc as that just means: find memory wherever else test_addr -= INT32_MAX; - for (; test_addr < limit_addr; test_addr += 0x100000) + // align to 4KB memory page size + test_addr = (test_addr + 0xFFF) & ~0xFFF; + + for (; test_addr < limit_addr; test_addr += 0x1000) // add 4KB memory page size { new_array = VirtualAlloc((LPVOID)test_addr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (new_array) @@ -145,3 +148,46 @@ void recover_mem(std::string name, size_t addr) write_mem_prot(it.address, std::string_view{it.old_data, it.size}, it.prot_used); } } + +size_t patch_and_redirect(size_t addr, size_t replace_size, std::string_view payload, bool just_nop, size_t return_to_addr) +{ + constexpr auto jump_size = 5; + + if (replace_size < jump_size) + return 0; + + size_t data_size_to_move = replace_size; + if (just_nop) + { + data_size_to_move = 0; + } + const size_t target = std::max(return_to_addr, addr); + const auto new_memory_size = payload.size() + data_size_to_move + jump_size; + + auto new_code = (char*)alloc_mem_rel32(target, new_memory_size); + if (new_code == nullptr) + return 0; + + if (!just_nop) + { + std::memcpy(new_code, (void*)addr, data_size_to_move); + } + std::memcpy(new_code + data_size_to_move, payload.data(), payload.size()); + + size_t return_addr = addr; + if (return_to_addr != 0) + { + return_addr = return_to_addr; + } + int32_t rel_back = static_cast(return_addr - (size_t)(new_code + new_memory_size)); + const std::string jump_back = fmt::format("\xE9{}"sv, to_le_bytes(rel_back)); + std::memcpy(new_code + payload.size() + data_size_to_move, jump_back.data(), jump_size); + + DWORD dummy; + VirtualProtect(new_code, new_memory_size, PAGE_EXECUTE_READ, &dummy); + + int32_t rel = static_cast((size_t)new_code - (addr + jump_size)); + const std::string redirect_code = fmt::format("\xE9{}{}"sv, to_le_bytes(rel), std::string(replace_size - jump_size, '\x90')); + write_mem_prot(addr, redirect_code, true); + return (size_t)new_code; +} diff --git a/src/game_api/memory.hpp b/src/game_api/memory.hpp index d5f9c8a3e..ce95bd941 100644 --- a/src/game_api/memory.hpp +++ b/src/game_api/memory.hpp @@ -96,6 +96,11 @@ size_t function_start(size_t off, uint8_t outside_byte = '\xcc'); void write_mem_recoverable(std::string name, size_t addr, std::string_view payload, bool prot); void recover_mem(std::string name, size_t addr = NULL); +// similar to ExecutableMemory but writes automatic jump from and back, moves the code it replaces etc. +// it needs at least 5 bytes to move, use just_nop = true to nuke the oryginal code +// make sure that the first 5 bytes are not a destination for some jump (it's fine if it's exacly at the addr) +size_t patch_and_redirect(size_t addr, size_t replace_size, std::string_view payload, bool just_nop = false, size_t return_to_addr = 0); + template requires std::is_trivially_copyable_v std::string_view to_le_bytes(const T& payload) diff --git a/src/game_api/rpc.cpp b/src/game_api/rpc.cpp index 611b9e3a1..5fb660225 100644 --- a/src/game_api/rpc.cpp +++ b/src/game_api/rpc.cpp @@ -1889,47 +1889,21 @@ void patch_orbs_limit() auto memory = Memory::get(); const auto function_offset = get_virtual_function_address(VTABLE_OFFSET::ITEM_FLOATING_ORB, (uint32_t)VIRT_FUNC::ENTITY_KILL); - if (function_offset == 0) - return; - - uint16_t particle_ids[5][3] = { - {204, 203, 202}, - {207, 206, 205}, - {210, 209, 208}, - {213, 212, 211}, - {216, 215, 214}, - }; - - size_t last_offset = function_offset; - std::array rel_offsets{0}; - for (uint8_t idx = 0; idx < rel_offsets.size(); ++idx) - { - std::string name = fmt::format("Orbs patch in kill virtual ({})", idx); - auto instance = find_inst(memory.exe(), "\x48\x8D\x05"sv, last_offset, function_offset + 0x622, name); - if (instance == 0) // not found, don't crash the game - return; - - rel_offsets[idx] = memory.at_exe(instance) + 3; // 3 pattern size - last_offset = instance + 7; - } - // this is quite an array, but other solution would be to write assembly code and jump to and from it the to original - // maybe TODO: write a code that automatically writes correct jump instructions so you only need to write the insert assembly + // finding exit of the loop that counts orbs, after jump there is unused code so we have enogh space for a jump + auto instance = find_inst(memory.exe(), "\xF3\x75"sv, function_offset, function_offset + 0x622, "Orbs patch in kill virtual"); + auto offset = memory.at_exe(instance) + 3; - uint16_t* new_array = (uint16_t*)alloc_mem_rel32(rel_offsets[4], (size_t)255 * 5 * sizeof(uint16_t)); // 5 arrays of 255 elements of 2 byte size - if (new_array == nullptr) + if (instance == 0) // not found, don't crash the game return; - for (uint8_t idx = 0; idx < rel_offsets.size(); ++idx) - { - auto current_array = new_array + 255 * idx; + auto loop_exit_jump = memory_read(offset + 1); - // +2 to skip the first two elements that we will set separately - std::fill(current_array + 2, current_array + 255, particle_ids[idx][2]); + // set r8b to 2 if it's more than 2 + std::string_view new_code{ + "\x41\x80\xf8\x02"sv // cmp r8b,0x2 + "\x72\x03"sv // jb + "\x41\xb0\x02"sv}; // mov r8b,0x2 - *current_array = particle_ids[idx][0]; - *(current_array + 1) = particle_ids[idx][1]; - int32_t rel = static_cast((size_t)current_array - (rel_offsets[idx] + 4)); - write_mem_prot(rel_offsets[idx], rel, true); - } + patch_and_redirect(offset, 8, new_code, true, offset + 2 + loop_exit_jump); once = true; }