Пример #1
0
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;
}
Пример #2
0
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());
  }
}
Пример #3
0
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.");
  }
}
Пример #4
0
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;
}
Пример #5
0
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;
}
Пример #6
0
  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;
  }
Пример #7
0
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();
}
Пример #8
0
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;
}
Пример #9
0
// 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;
  }
}
Пример #10
0
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);
  }
}
Пример #11
0
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;
}
Пример #12
0
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);
  }
}
Пример #13
0
  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;
  }
Пример #14
0
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();
}