HRESULT WINAPI DXGISwapChainProxy::QueryInterface(REFIID riid, void** obj) { hadesmem::detail::LastErrorPreserver last_error_preserver; last_error_preserver.Revert(); auto const ret = swap_chain_->QueryInterface(riid, obj); last_error_preserver.Update(); if (SUCCEEDED(ret)) { HADESMEM_DETAIL_TRACE_A("Succeeded."); if (*obj == swap_chain_) { refs_++; *obj = this; } else { HADESMEM_DETAIL_TRACE_A("WARNING! Unhandled interface."); HADESMEM_DETAIL_ASSERT(false); static_cast<IUnknown*>(*obj)->Release(); return E_NOINTERFACE; } } else { HADESMEM_DETAIL_TRACE_A("Failed."); } return ret; }
Config::Config() { HADESMEM_DETAIL_TRACE_A("Initializing Config."); auto const config_path = hadesmem::detail::CombinePath( hadesmem::detail::GetSelfDirPath(), L"hadesmem.xml"); HADESMEM_DETAIL_TRACE_FORMAT_W(L"Looking for config file. Path: [%s].", config_path.c_str()); if (hadesmem::detail::DoesFileExist(config_path)) { HADESMEM_DETAIL_TRACE_A("Got config file."); LoadFile(config_path); } }
void HandleWindowChange(HWND wnd) { WindowInfo& window_info = GetWindowInfo(); if (wnd == nullptr) { if (window_info.hooked_ && window_info.old_wndproc_ != nullptr) { ::SetWindowLongPtrW(window_info.old_hwnd_, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(window_info.old_wndproc_)); HADESMEM_DETAIL_TRACE_FORMAT_A("Reset window procedure located at %p.", window_info.old_wndproc_); } HADESMEM_DETAIL_TRACE_A("Clearing window hook data."); window_info.old_hwnd_ = nullptr; window_info.old_wndproc_ = nullptr; window_info.hooked_ = false; return; } if (!window_info.hooked_) { window_info.old_hwnd_ = wnd; ::SetLastError(0); window_info.old_wndproc_ = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW( wnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&WindowProc))); if (!window_info.old_wndproc_) { DWORD const last_error = ::GetLastError(); if (last_error) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"SetWindowLongPtrW failed."} << ErrorCodeWinLast{last_error}); } } window_info.hooked_ = true; HADESMEM_DETAIL_TRACE_FORMAT_A("Replaced window procedure located at %p.", window_info.old_wndproc_); } else { HADESMEM_DETAIL_TRACE_A("Window is already hooked. Skipping hook request."); } }
void D3D10DeviceProxy::Cleanup() { HADESMEM_DETAIL_TRACE_A("Called."); auto& callbacks = GetOnReleaseD3D10Callbacks(); callbacks.Run(device_); }
virtual void RemovePatch() override { hadesmem::detail::AcquireSRWLock const lock( &GetSrwLock(), hadesmem::detail::SRWLockType::Exclusive); HADESMEM_DETAIL_TRACE_A("Unsetting DR hook."); auto& dr_hooks = GetDrHooks(); auto const thread_id = ::GetCurrentThreadId(); auto const iter = dr_hooks.find(thread_id); HADESMEM_DETAIL_ASSERT(iter != std::end(dr_hooks)); auto const dr_index = iter->second; Thread const thread(thread_id); auto context = GetThreadContext(thread, CONTEXT_DEBUG_REGISTERS); // Clear the appropriate DR *(&context.Dr0 + dr_index) = 0; // Clear appropriate L0-L3 flag context.Dr7 &= ~static_cast<std::uintptr_t>(1ULL << (dr_index * 2)); SetThreadContext(thread, context); auto const dr_hooks_removed = dr_hooks.erase(thread_id); (void)dr_hooks_removed; HADESMEM_DETAIL_ASSERT(dr_hooks_removed); auto& veh_hooks = GetVehHooks(); auto const veh_hooks_removed = veh_hooks.erase(target_); (void)veh_hooks_removed; HADESMEM_DETAIL_ASSERT(veh_hooks_removed); }
void DXGISwapChainProxy::Cleanup() { HADESMEM_DETAIL_TRACE_A("Called."); auto& callbacks = GetOnReleaseDXGICallbacks(); callbacks.Run(swap_chain_); }
HRESULT WINAPI DirectInput8WProxy::CreateDevice(REFGUID rguid, LPDIRECTINPUTDEVICE8W* device, LPUNKNOWN outer) { hadesmem::detail::LastErrorPreserver last_error_preserver; HADESMEM_DETAIL_TRACE_FORMAT_A( "Args: [%p] [%p] [%p] [%p].", this, &rguid, device, outer); last_error_preserver.Revert(); auto const ret = direct_input_->CreateDevice(rguid, device, outer); last_error_preserver.Update(); HADESMEM_DETAIL_TRACE_FORMAT_A("Ret: [%ld].", ret); if (SUCCEEDED(ret)) { auto const device_type = DeviceGuidToEnum(direct_input_, rguid); HADESMEM_DETAIL_TRACE_FORMAT_A("Got new IDirectInputDevice8W. Type: [%s].", DeviceTypeToString(device_type).c_str()); *device = new DirectInputDevice8WProxy(*device, device_type); } else { HADESMEM_DETAIL_TRACE_A("Failed."); } return ret; }
// TODO: Fix up the format of the config file so it's all process oriented // rather than plugin oriented. i.e. You should specify your process, and then // attach your config for plugins/gui/etc to that (and also have some sort of // 'global' config which applies for cases where there is no process-specific // override). That way you can override the GUI on a per-process basis so it // matches the plugins you're trying to load. void Config::LoadImpl(pugi::xml_document const& doc) { auto const hadesmem_root = doc.child(L"HadesMem"); if (!hadesmem_root) { HADESMEM_DETAIL_THROW_EXCEPTION(hadesmem::Error{} << hadesmem::ErrorString{ "Failed to find 'HadesMem' root node."}); } auto const cerberus_node = hadesmem_root.child(L"Cerberus"); if (!cerberus_node) { HADESMEM_DETAIL_TRACE_A("No Cerberus node."); return; } for (auto const& plugin_node : cerberus_node.children(L"Plugin")) { auto const path = hadesmem::detail::pugixml::GetAttributeValue(plugin_node, L"Path"); auto const process = hadesmem::detail::pugixml::GetOptionalAttributeValue( plugin_node, L"Process"); HADESMEM_DETAIL_TRACE_FORMAT_A( "Got Plugin entry. Path: [%s]. Process: [%s].", path.c_str(), process.c_str()); plugins_.emplace_back(Plugin{path, process}); } // TODO: Find a better way to do this. auto const gui = hadesmem::detail::pugixml::GetOptionalAttributeValue(cerberus_node, L"GUI"); if (hadesmem::detail::ToUpperOrdinal(gui) == L"ANTTWEAKBAR") { HADESMEM_DETAIL_TRACE_A("AntTweakBar enabled."); ant_tweak_bar_enabled_ = true; } else if (hadesmem::detail::ToUpperOrdinal(gui) == L"GWEN") { HADESMEM_DETAIL_TRACE_A("GWEN enabled."); gwen_enabled_ = true; } else { ant_tweak_bar_enabled_ = false; } }
ChaiScriptScript::~ChaiScriptScript() { try { if (chai_) { chai_->eval("CerberusScriptStop()"); } } catch (...) { HADESMEM_DETAIL_TRACE_A( boost::current_exception_diagnostic_information().c_str()); } }
void CleanupUnchecked() noexcept { try { Cleanup(); } catch (...) { // WARNING: Handle is leaked if 'Cleanup' fails. HADESMEM_DETAIL_TRACE_A( boost::current_exception_diagnostic_information().c_str()); HADESMEM_DETAIL_ASSERT(false); handle_ = GetInvalid(); } }
template <typename... Args> void Run(Args&&... args) const noexcept { hadesmem::detail::AcquireSRWLock lock( &srw_lock_, hadesmem::detail::SRWLockType::Shared); for (auto const& callback : callbacks_) { try { callback.second(std::forward<Args&&>(args)...); } catch (...) { HADESMEM_DETAIL_TRACE_A( boost::current_exception_diagnostic_information().c_str()); HADESMEM_DETAIL_ASSERT(false); } } }
void FreeUnchecked() noexcept { try { Free(); } catch (...) { // WARNING: Memory in remote process is leaked if 'Free' // fails. HADESMEM_DETAIL_TRACE_A( boost::current_exception_diagnostic_information().c_str()); HADESMEM_DETAIL_ASSERT(false); process_ = nullptr; base_ = nullptr; size_ = 0; } }
virtual void RemoveUnchecked() noexcept override { try { Remove(); } catch (...) { // WARNING: Patch may not be removed if Remove fails. HADESMEM_DETAIL_TRACE_A( boost::current_exception_diagnostic_information().c_str()); HADESMEM_DETAIL_ASSERT(false); process_ = nullptr; applied_ = false; target_ = nullptr; detour_ = nullptr; orig_ = nullptr; } }
inline std::size_t WriteCall(Process const& process, void* address, void* target, std::vector<std::unique_ptr<Allocator>>& trampolines) { HADESMEM_DETAIL_TRACE_FORMAT_A("Address = %p, Target = %p", address, target); std::vector<std::uint8_t> call_buf; // TODO: Avoid using a trampoline where possible. #if defined(HADESMEM_DETAIL_ARCH_X64) std::unique_ptr<Allocator> trampoline = AllocatePageNear(process, address); PVOID tramp_addr = trampoline->GetBase(); HADESMEM_DETAIL_TRACE_FORMAT_A("Using trampoline call. Trampoline = %p.", tramp_addr); Write(process, tramp_addr, reinterpret_cast<std::uintptr_t>(target)); trampolines.emplace_back(std::move(trampoline)); call_buf = GenCallTramp64(address, tramp_addr); HADESMEM_DETAIL_ASSERT(call_buf.size() == PatchConstants::kCallSize64); #elif defined(HADESMEM_DETAIL_ARCH_X86) (void)trampolines; HADESMEM_DETAIL_TRACE_A("Using relative call."); call_buf = GenCall32(address, target); HADESMEM_DETAIL_ASSERT(call_buf.size() == PatchConstants::kCallSize32); #else #error "[HadesMem] Unsupported architecture." #endif WriteVector(process, address, call_buf); return call_buf.size(); }
virtual void Apply() override { if (applied_) { return; } if (detached_) { HADESMEM_DETAIL_ASSERT(false); return; } // Reset the trampolines here because we don't do it in remove, otherwise // there's a potential race condition where we want to unhook and unload // safely, so we unhook the function, then try waiting on our ref count to // become zero, but we haven't actually called the trampoline yet, so we end // up jumping to the memory we just free'd! trampoline_ = nullptr; trampolines_.clear(); stub_gate_ = nullptr; SuspendedProcess const suspended_process{process_->GetId()}; std::uint32_t const kMaxInstructionLen = 15; std::uint32_t const kTrampSize = kMaxInstructionLen * 3; trampoline_ = std::make_unique<Allocator>(*process_, kTrampSize); auto tramp_cur = static_cast<std::uint8_t*>(trampoline_->GetBase()); auto const detour_raw = detour_.target<DetourFuncRawT>(); if (detour_raw || detour_) { HADESMEM_DETAIL_TRACE_FORMAT_A( "Target = %p, Detour = %p, Trampoline = %p.", target_, detour_raw, trampoline_->GetBase()); } else { HADESMEM_DETAIL_TRACE_FORMAT_A( "Target = %p, Detour = INVALID, Trampoline = %p.", target_, trampoline_->GetBase()); } auto const buffer = ReadVector<std::uint8_t>(*process_, target_, kTrampSize); ud_t ud_obj; ud_init(&ud_obj); ud_set_input_buffer(&ud_obj, buffer.data(), buffer.size()); ud_set_syntax(&ud_obj, UD_SYN_INTEL); ud_set_pc(&ud_obj, reinterpret_cast<std::uint64_t>(target_)); #if defined(HADESMEM_DETAIL_ARCH_X64) ud_set_mode(&ud_obj, 64); #elif defined(HADESMEM_DETAIL_ARCH_X86) ud_set_mode(&ud_obj, 32); #else #error "[HadesMem] Unsupported architecture." #endif stub_gate_ = detail::AllocatePageNear(*process_, target_); std::size_t const patch_size = GetPatchSize(); std::uint32_t instr_size = 0; do { std::uint32_t const len = ud_disassemble(&ud_obj); if (len == 0) { HADESMEM_DETAIL_THROW_EXCEPTION(Error{} << ErrorString{"Disassembly failed."}); } #if !defined(HADESMEM_NO_TRACE) char const* const asm_str = ud_insn_asm(&ud_obj); char const* const asm_bytes_str = ud_insn_hex(&ud_obj); HADESMEM_DETAIL_TRACE_FORMAT_A( "%s. [%s].", (asm_str ? asm_str : "Invalid."), (asm_bytes_str ? asm_bytes_str : "Invalid.")); #endif ud_operand_t const* const op = ud_insn_opr(&ud_obj, 0); bool is_jimm = op && op->type == UD_OP_JIMM; // Handle JMP QWORD PTR [RIP+Rel32]. Necessary for hook chain support. bool is_jmem = op && op->type == UD_OP_MEM && op->base == UD_R_RIP && op->index == UD_NONE && op->scale == 0 && op->size == 0x40; if ((ud_obj.mnemonic == UD_Ijmp || ud_obj.mnemonic == UD_Icall) && op && (is_jimm || is_jmem)) { std::uint16_t const size = is_jimm ? op->size : op->offset; HADESMEM_DETAIL_TRACE_FORMAT_A("Operand/offset size is %hu.", size); std::int64_t const insn_target = [&]() -> std::int64_t { switch (size) { case sizeof(std::int8_t) * CHAR_BIT: return op->lval.sbyte; case sizeof(std::int16_t) * CHAR_BIT: return op->lval.sword; case sizeof(std::int32_t) * CHAR_BIT: return op->lval.sdword; case sizeof(std::int64_t) * CHAR_BIT: return op->lval.sqword; default: HADESMEM_DETAIL_ASSERT(false); HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"Unknown instruction size."}); } }(); auto const resolve_rel = [](std::uint64_t base, std::int64_t target, std::uint32_t insn_len) { return reinterpret_cast<std::uint8_t*>( static_cast<std::uintptr_t>(base)) + target + insn_len; }; std::uint64_t const insn_base = ud_insn_off(&ud_obj); std::uint32_t const insn_len = ud_insn_len(&ud_obj); auto const resolved_target = resolve_rel(insn_base, insn_target, insn_len); void* const jump_target = is_jimm ? resolved_target : Read<void*>(*process_, resolved_target); HADESMEM_DETAIL_TRACE_FORMAT_A("Jump/call target = %p.", jump_target); if (ud_obj.mnemonic == UD_Ijmp) { HADESMEM_DETAIL_TRACE_A("Writing resolved jump."); tramp_cur += detail::WriteJump( *process_, tramp_cur, jump_target, true, &trampolines_); } else { HADESMEM_DETAIL_ASSERT(ud_obj.mnemonic == UD_Icall); HADESMEM_DETAIL_TRACE_A("Writing resolved call."); tramp_cur += detail::WriteCall(*process_, tramp_cur, jump_target, trampolines_); } } else { std::uint8_t const* const raw = ud_insn_ptr(&ud_obj); Write(*process_, tramp_cur, raw, raw + len); tramp_cur += len; } instr_size += len; } while (instr_size < patch_size); HADESMEM_DETAIL_TRACE_A("Writing jump back to original code."); tramp_cur += detail::WriteJump(*process_, tramp_cur, reinterpret_cast<std::uint8_t*>(target_) + instr_size, true, &trampolines_); FlushInstructionCache( *process_, trampoline_->GetBase(), trampoline_->GetSize()); detail::WriteStubGate<TargetFuncT>(*process_, stub_gate_->GetBase(), &*stub_, &GetOriginalArbitraryUserPtrPtr); orig_ = ReadVector<std::uint8_t>(*process_, target_, patch_size); detail::VerifyPatchThreads(process_->GetId(), target_, orig_.size()); WritePatch(); FlushInstructionCache(*process_, target_, instr_size); applied_ = true; }
virtual void RemovePatch() { HADESMEM_DETAIL_TRACE_A("Restoring original pointer."); Write(*process_, target_, orig_); }
virtual void WritePatch() { HADESMEM_DETAIL_TRACE_A("Writing pointer to stub."); Write(*process_, target_, stub_gate_->GetBase()); }
void DirectInput8WProxy::Cleanup() { HADESMEM_DETAIL_TRACE_A("Called."); }
virtual void WritePatch() override { hadesmem::detail::AcquireSRWLock const lock( &GetSrwLock(), hadesmem::detail::SRWLockType::Exclusive); auto& veh_hooks = GetVehHooks(); HADESMEM_DETAIL_ASSERT(veh_hooks.find(target_) == std::end(veh_hooks)); veh_hooks[target_] = this; auto const veh_cleanup_hook = [&]() { auto const veh_hooks_removed = veh_hooks.erase(target_); (void)veh_hooks_removed; HADESMEM_DETAIL_ASSERT(veh_hooks_removed); }; auto scope_veh_cleanup_hook = hadesmem::detail::MakeScopeWarden(veh_cleanup_hook); HADESMEM_DETAIL_TRACE_A("Setting DR hook."); auto& dr_hooks = GetDrHooks(); auto const thread_id = ::GetCurrentThreadId(); HADESMEM_DETAIL_ASSERT(dr_hooks.find(thread_id) == std::end(dr_hooks)); Thread const thread(thread_id); auto context = GetThreadContext(thread, CONTEXT_DEBUG_REGISTERS); std::uint32_t dr_index = static_cast<std::uint32_t>(-1); for (std::uint32_t i = 0; i < 4; ++i) { // Check whether the DR is available according to the control register bool const control_available = !(context.Dr7 & (1ULL << (i * 2))); // Check whether the DR is zero. Pobably not actually necessary, but // it's a nice additional sanity check. This may require a // user-controlable flag in future though if the code being hooked is // 'hostile'. bool const dr_available = !(&context.Dr0)[i]; if (control_available && dr_available) { dr_index = i; break; } } if (dr_index == static_cast<std::uint32_t>(-1)) { HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"No free debug registers."}); } dr_hooks[ ::GetCurrentThreadId()] = dr_index; auto const dr_cleanup_hook = [&]() { auto const dr_hooks_removed = dr_hooks.erase(::GetCurrentThreadId()); (void)dr_hooks_removed; HADESMEM_DETAIL_ASSERT(dr_hooks_removed); }; auto scope_dr_cleanup_hook = hadesmem::detail::MakeScopeWarden(dr_cleanup_hook); (&context.Dr0)[dr_index] = reinterpret_cast<std::uintptr_t>(target_); // Set appropriate L0-L3 flag context.Dr7 |= static_cast<std::uintptr_t>(1ULL << (dr_index * 2)); // Set appropriate RW0-RW3 field (Execution) std::uintptr_t break_type = 0; context.Dr7 |= (break_type << (16 + 4 * dr_index)); // Set appropriate LEN0-LEN3 field (1 byte) std::uintptr_t break_len = 0; context.Dr7 |= (break_len << (18 + 4 * dr_index)); // Set LE flag std::uintptr_t local_enable = 1 << 8; context.Dr7 |= local_enable; SetThreadContext(thread, context); scope_veh_cleanup_hook.Dismiss(); scope_dr_cleanup_hook.Dismiss(); }
void DumpMemory() { HADESMEM_DETAIL_TRACE_A("Dumping image memory to disk."); hadesmem::Process const process{::GetCurrentProcessId()}; hadesmem::ModuleList modules(process); for (auto const& module : modules) { HADESMEM_DETAIL_TRACE_A("Checking for valid headers."); try { hadesmem::PeFile const pe_file(process, module.GetHandle(), hadesmem::PeFileType::Image, static_cast<DWORD>(module.GetSize())); hadesmem::NtHeaders nt_headers(process, pe_file); } catch (std::exception const& /*e*/) { HADESMEM_DETAIL_TRACE_A("WARNING! Invalid headers."); return; } HADESMEM_DETAIL_TRACE_A("Reading memory."); auto raw = hadesmem::ReadVectorEx<std::uint8_t>( process, module.GetHandle(), module.GetSize(), hadesmem::ReadFlags::kZeroFillReserved); hadesmem::Process const local_process(::GetCurrentProcessId()); hadesmem::PeFile const pe_file(local_process, raw.data(), hadesmem::PeFileType::Image, static_cast<DWORD>(raw.size())); hadesmem::NtHeaders nt_headers(local_process, pe_file); HADESMEM_DETAIL_TRACE_A("Copying headers."); std::vector<std::uint8_t> raw_new; std::copy(std::begin(raw), std::begin(raw) + nt_headers.GetSizeOfHeaders(), std::back_inserter(raw_new)); HADESMEM_DETAIL_TRACE_A("Copying section data."); hadesmem::SectionList const sections(local_process, pe_file); std::vector<std::pair<DWORD, DWORD>> raw_datas; for (auto const& section : sections) { auto const section_size = (std::max)(section.GetVirtualSize(), section.GetSizeOfRawData()); auto const ptr_raw_data_new = section.GetPointerToRawData() < raw_new.size() ? static_cast<DWORD>( RoundUp(raw_new.size(), nt_headers.GetFileAlignment())) : section.GetPointerToRawData(); raw_datas.emplace_back(ptr_raw_data_new, section_size); if (ptr_raw_data_new > raw_new.size()) { raw_new.resize(ptr_raw_data_new); } auto const raw_data = raw.data() + section.GetVirtualAddress(); auto const raw_data_end = raw_data + section_size; raw_new.reserve(raw_new.size() + section_size); std::copy(raw_data, raw_data_end, std::back_inserter(raw_new)); } HADESMEM_DETAIL_ASSERT(raw_new.size() < (std::numeric_limits<DWORD>::max)()); hadesmem::PeFile const pe_file_new(local_process, raw_new.data(), hadesmem::PeFileType::Data, static_cast<DWORD>(raw_new.size())); HADESMEM_DETAIL_TRACE_A("Fixing NT headers."); hadesmem::NtHeaders nt_headers_new(local_process, pe_file_new); nt_headers_new.SetImageBase( reinterpret_cast<ULONG_PTR>(module.GetHandle())); nt_headers_new.UpdateWrite(); HADESMEM_DETAIL_TRACE_A("Fixing section headers."); hadesmem::SectionList sections_new(local_process, pe_file_new); std::size_t n = 0; for (auto& section : sections_new) { section.SetPointerToRawData(raw_datas[n].first); section.SetSizeOfRawData(raw_datas[n].second); section.UpdateWrite(); ++n; } HADESMEM_DETAIL_TRACE_A("Fixing imports."); hadesmem::ImportDirList const import_dirs(local_process, pe_file); hadesmem::ImportDirList const import_dirs_new(local_process, pe_file_new); auto i = std::begin(import_dirs), j = std::begin(import_dirs_new); bool thunk_mismatch = false; for (; i != std::end(import_dirs) && j != std::end(import_dirs_new); ++i, ++j) { hadesmem::ImportThunkList const import_thunks( local_process, pe_file, i->GetOriginalFirstThunk()); hadesmem::ImportThunkList import_thunks_new( local_process, pe_file_new, j->GetFirstThunk()); auto a = std::begin(import_thunks); auto b = std::begin(import_thunks_new); for (; a != std::end(import_thunks) && b != std::end(import_thunks_new); ++a, ++b) { b->SetFunction(a->GetFunction()); b->UpdateWrite(); } thunk_mismatch = thunk_mismatch || ((a != std::end(import_thunks)) ^ (b != std::end(import_thunks_new))); } bool const dir_mismatch = (i != std::end(import_dirs)) ^ (j != std::end(import_dirs)); HADESMEM_DETAIL_TRACE_A("Writing file."); auto const proc_path = hadesmem::GetPath(process); auto const proc_name = proc_path.substr(proc_path.rfind(L'\\') + 1); auto const proc_pid_str = std::to_wstring(process.GetId()); std::wstring dump_path; std::uint32_t c = 0; do { dump_path = proc_name + L"_" + proc_pid_str + L"_" + module.GetName() + L"_" + std::to_wstring(c++) + L".dmp"; } while (hadesmem::detail::DoesFileExist(dump_path) && c < 10); auto const dump_file = hadesmem::detail::OpenFile<char>( dump_path, std::ios::out | std::ios::binary); if (!*dump_file) { HADESMEM_DETAIL_THROW_EXCEPTION(hadesmem::Error() << hadesmem::ErrorString( "Unable to open dump file.")); } if (!dump_file->write(reinterpret_cast<char const*>(raw_new.data()), raw_new.size())) { HADESMEM_DETAIL_THROW_EXCEPTION(hadesmem::Error() << hadesmem::ErrorString( "Unable to write to dump file.")); } if (dir_mismatch) { HADESMEM_DETAIL_THROW_EXCEPTION(hadesmem::Error() << hadesmem::ErrorString( "Mismatch in import dir processing.")); } if (thunk_mismatch) { HADESMEM_DETAIL_THROW_EXCEPTION( hadesmem::Error() << hadesmem::ErrorString( "Mismatch in import thunk processing.")); } } }
inline std::size_t WriteJump(Process const& process, void* address, void* target, bool push_ret_fallback, std::vector<std::unique_ptr<Allocator>>* trampolines) { HADESMEM_DETAIL_TRACE_FORMAT_A( "Address = %p, Target = %p, Push Ret Fallback = %u.", address, target, static_cast<std::uint32_t>(push_ret_fallback)); std::vector<std::uint8_t> jump_buf; #if defined(HADESMEM_DETAIL_ARCH_X64) if (IsNear(address, target)) { HADESMEM_DETAIL_TRACE_A("Using relative jump."); jump_buf = GenJmp32(address, target); HADESMEM_DETAIL_ASSERT(jump_buf.size() == PatchConstants::kJmpSize32); } else { std::unique_ptr<Allocator> trampoline; if (trampolines) { try { trampoline = AllocatePageNear(process, address); } catch (std::exception const& /*e*/) { // Don't need to do anything, we'll fall back to PUSH/RET. } } if (trampoline) { void* tramp_addr = trampoline->GetBase(); HADESMEM_DETAIL_TRACE_FORMAT_A("Using trampoline jump. Trampoline = %p.", tramp_addr); Write(process, tramp_addr, target); trampolines->emplace_back(std::move(trampoline)); jump_buf = GenJmpTramp64(address, tramp_addr); HADESMEM_DETAIL_ASSERT(jump_buf.size() == PatchConstants::kJmpSize64); } else { if (!push_ret_fallback) { // We're out of options... HADESMEM_DETAIL_THROW_EXCEPTION( Error{} << ErrorString{"Unable to use a relative or trampoline " "jump, and push/ret fallback is disabled."}); } HADESMEM_DETAIL_TRACE_A("Using push/ret 'jump'."); auto const target_high = static_cast<std::uint32_t>( (reinterpret_cast<std::uintptr_t>(target) >> 32) & 0xFFFFFFFF); if (target_high) { HADESMEM_DETAIL_TRACE_A("Push/ret 'jump' is big."); jump_buf = GenPush64Ret(target); HADESMEM_DETAIL_ASSERT(jump_buf.size() == PatchConstants::kPushRetSize64); } else { HADESMEM_DETAIL_TRACE_A("Push/ret 'jump' is small."); jump_buf = GenPush32Ret(target); HADESMEM_DETAIL_ASSERT(jump_buf.size() == PatchConstants::kPushRetSize32); } } } #elif defined(HADESMEM_DETAIL_ARCH_X86) (void)push_ret_fallback; (void)trampolines; HADESMEM_DETAIL_TRACE_A("Using relative jump."); jump_buf = GenJmp32(address, target); HADESMEM_DETAIL_ASSERT(jump_buf.size() == PatchConstants::kJmpSize32); #else #error "[HadesMem] Unsupported architecture." #endif WriteVector(process, address, jump_buf); return jump_buf.size(); }