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; }
ChaiScriptScript::ChaiScriptScript(std::string const& path) : chai_( std::make_unique<chaiscript::ChaiScript>(chaiscript::Std_Lib::library())) { InitializeChaiScriptContext(*chai_, true); auto& log = GetImGuiLogWindow(); try { auto const val = chai_->eval_file(path.c_str()); if (!val.get_type_info().bare_equal(chaiscript::user_type<void>())) { try { auto const str = chai_->eval< std::function<std::string(const chaiscript::Boxed_Value& bv)>>( "to_string")(val); HADESMEM_DETAIL_TRACE_FORMAT_A("[Info]: %s.", str.c_str()); log.AddLog("[Info]: %s\n", str.c_str()); } catch (...) { } } chai_->eval("CerberusScriptStart()"); } catch (const chaiscript::exception::eval_error& ee) { if (ee.call_stack.size() > 0) { HADESMEM_DETAIL_TRACE_FORMAT_A( "[Error]: %s during evaluation at (%d,%d).", boost::current_exception_diagnostic_information().c_str(), ee.call_stack[0]->start().line, ee.call_stack[0]->start().column); log.AddLog("[Error]: %s during evaluation at (%d,%d)\n", boost::current_exception_diagnostic_information().c_str(), ee.call_stack[0]->start().line, ee.call_stack[0]->start().column); } else { HADESMEM_DETAIL_TRACE_FORMAT_A( "[Error]: %s.", boost::current_exception_diagnostic_information().c_str()); log.AddLog("[Error]: %s\n", boost::current_exception_diagnostic_information().c_str()); } } catch (...) { HADESMEM_DETAIL_TRACE_FORMAT_A( "[Error]: %s.", boost::current_exception_diagnostic_information().c_str()); log.AddLog("[Error]: %s\n", boost::current_exception_diagnostic_information().c_str()); } }
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."); } }
ULONG WINAPI DXGISwapChainProxy::Release() { hadesmem::detail::LastErrorPreserver last_error_preserver; refs_--; HADESMEM_DETAIL_ASSERT(refs_ >= 0); if (refs_ == 0) { Cleanup(); } last_error_preserver.Revert(); auto const ret = swap_chain_->Release(); last_error_preserver.Update(); HADESMEM_DETAIL_TRACE_FORMAT_A( "Internal refs: [%lu]. External refs: [%lld].", ret, refs_); if (ret == 0) { delete this; } return ret; }
ULONG WINAPI DXGISwapChainProxy::AddRef() { refs_++; auto const ret = swap_chain_->AddRef(); HADESMEM_DETAIL_TRACE_FORMAT_A( "Internal refs: [%lu]. External refs: [%lld].", ret, refs_); return ret; }
virtual void Apply() { if (applied_) { return; } if (detached_) { HADESMEM_DETAIL_ASSERT(false); return; } stub_gate_ = nullptr; auto const detour_raw = detour_.target<DetourFuncRawT>(); if (detour_raw || detour_) { HADESMEM_DETAIL_TRACE_FORMAT_A( "Target = %p, Detour = %p.", target_, detour_raw); } else { HADESMEM_DETAIL_TRACE_FORMAT_A("Target = %p, Detour = INVALID.", target_); } stub_gate_ = detail::AllocatePageNear(*process_, base_); detail::WriteStubGate<TargetFuncT>(*process_, stub_gate_->GetBase(), &*stub_, &GetOriginalArbitraryUserPtrPtr); orig_ = Read<DWORD>(*process_, target_); WritePatch(); applied_ = true; }
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(); }
inline DWORD ResumeThread(Thread const& thread) { HADESMEM_DETAIL_TRACE_FORMAT_A("Resuming thread with ID 0n%lu.", thread.GetId()); DWORD const suspend_count = ::ResumeThread(thread.GetHandle()); if (suspend_count == static_cast<DWORD>(-1)) { DWORD const last_error = ::GetLastError(); HADESMEM_DETAIL_THROW_EXCEPTION(Error() << ErrorString("ResumeThread failed.") << ErrorCodeWinLast(last_error)); } return suspend_count; }
// 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; } }
void DetourDXGI(HMODULE base) { auto const& process = GetThisProcess(); auto& module = GetDXGIModule(); auto& helper = GetHelperInterface(); if (helper.CommonDetourModule(process, L"DXGI", base, module)) { auto& render_helper = GetRenderHelperInterface(); auto const render_offsets = render_helper.GetRenderOffsets(); auto const dxgi_offsets = &render_offsets->dxgi_offsets_; auto const offset_base = reinterpret_cast<std::uint8_t*>(base); if (!dxgi_offsets->present_) { HADESMEM_DETAIL_TRACE_FORMAT_A( "WARNING! No DXGI offsets. Skipping hooks."); return; } auto const present_fn = offset_base + dxgi_offsets->present_; DetourFunc(process, "IDXGISwapChain::Present", GetIDXGISwapChainPresentDetour(), reinterpret_cast<IDXGISwapChain_Present_Fn>(present_fn), IDXGISwapChain_Present_Detour); auto const resize_buffers_fn = offset_base + dxgi_offsets->resize_buffers_; DetourFunc( process, "IDXGISwapChain::ResizeBuffers", GetIDXGISwapChainResizeBuffersDetour(), reinterpret_cast<IDXGISwapChain_ResizeBuffers_Fn>(resize_buffers_fn), IDXGISwapChain_ResizeBuffers_Detour); auto const present_1_fn = offset_base + dxgi_offsets->present_1_; DetourFunc(process, "IDXGISwapChain1::Present1", GetIDXGISwapChain1Present1Detour(), reinterpret_cast<IDXGISwapChain1_Present1_Fn>(present_1_fn), IDXGISwapChain1_Present1_Detour); } }
HRESULT WINAPI DXGISwapChainProxy::ResizeBuffers(UINT buffer_count, UINT width, UINT height, DXGI_FORMAT new_format, UINT swap_chain_flags) { HADESMEM_DETAIL_TRACE_FORMAT_A("Args: [%p] [%u] [%u] [%u] [%d] [%u].", this, buffer_count, width, height, new_format, swap_chain_flags); auto const ret = swap_chain_->ResizeBuffers( buffer_count, width, height, new_format, swap_chain_flags); auto& callbacks = GetOnResizeDXGICallbacks(); callbacks.Run(swap_chain_, width, height); return ret; }
void DetourD3D9(HMODULE base) { auto const& process = GetThisProcess(); auto& module = GetD3D9Module(); auto& helper = GetHelperInterface(); if (helper.CommonDetourModule(process, L"D3D9", base, module)) { auto& render_helper = GetRenderHelperInterface(); auto const render_offsets = render_helper.GetRenderOffsets(); auto const d3d9_offsets = &render_offsets->d3d9_offsets_; auto const offset_base = reinterpret_cast<std::uint8_t*>(base); if (!d3d9_offsets->add_ref_) { HADESMEM_DETAIL_TRACE_FORMAT_A( "WARNING! No D3D9 offsets. Skipping hooks."); return; } auto const add_ref_fn = offset_base + d3d9_offsets->add_ref_; DetourFunc(process, "IDirect3DDevice9::AddRef", GetIDirect3DDevice9AddRefDetour(), reinterpret_cast<IDirect3DDevice9_AddRef_Fn>(add_ref_fn), IDirect3DDevice9_AddRef_Detour); auto const release_fn = offset_base + d3d9_offsets->release_; DetourFunc(process, "IDirect3DDevice9::Release", GetIDirect3DDevice9ReleaseDetour(), reinterpret_cast<IDirect3DDevice9_Release_Fn>(release_fn), IDirect3DDevice9_Release_Detour); auto const present_fn = offset_base + d3d9_offsets->present_; DetourFunc(process, "IDirect3DDevice9::Present", GetIDirect3DDevice9PresentDetour(), reinterpret_cast<IDirect3DDevice9_Present_Fn>(present_fn), IDirect3DDevice9_Present_Detour); auto const reset_fn = offset_base + d3d9_offsets->reset_; DetourFunc(process, "IDirect3DDevice9::Reset", GetIDirect3DDevice9ResetDetour(), reinterpret_cast<IDirect3DDevice9_Reset_Fn>(reset_fn), IDirect3DDevice9_Reset_Detour); auto const end_scene_fn = offset_base + d3d9_offsets->end_scene_; DetourFunc(process, "IDirect3DDevice9::EndScene", GetIDirect3DDevice9EndSceneDetour(), reinterpret_cast<IDirect3DDevice9_EndScene_Fn>(end_scene_fn), IDirect3DDevice9_EndScene_Detour); auto const present_ex_fn = offset_base + d3d9_offsets->present_ex_; DetourFunc(process, "IDirect3DDevice9Ex::PresentEx", GetIDirect3DDevice9ExPresentExDetour(), reinterpret_cast<IDirect3DDevice9Ex_PresentEx_Fn>(present_ex_fn), IDirect3DDevice9Ex_PresentEx_Detour); auto const reset_ex_fn = offset_base + d3d9_offsets->reset_ex_; DetourFunc(process, "IDirect3DDevice9Ex::ResetEx", GetIDirect3DDevice9ExResetExDetour(), reinterpret_cast<IDirect3DDevice9Ex_ResetEx_Fn>(reset_ex_fn), IDirect3DDevice9Ex_ResetEx_Detour); auto const swap_chain_present_fn = offset_base + d3d9_offsets->swap_chain_present_; DetourFunc( process, "IDirect3DSwapChain9::Present", GetIDirect3DSwapChain9PresentDetour(), reinterpret_cast<IDirect3DSwapChain9_Present_Fn>(swap_chain_present_fn), IDirect3DSwapChain9_Present_Detour); } }
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; }
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(); }