<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Articles tagged win32 at The Segfault Garden</title>
  <link rel="alternate" type="text/html"
        href="https://blog.segv.page/tags/win32/"/>
  <link rel="self" type="application/atom+xml"
        href="https://blog.segv.page/tags/win32/feed/"/>
  <updated>2026-06-05T17:18:02Z</updated>
  <id>urn:uuid:33f52ccf-2d02-4f2f-b574-8dd1468b3e7b</id>

  <author>
    <name>Luna</name>
    <uri>https://blog.segv.page</uri>
    <email>frgmntedflower@linux.com</email>
  </author>

  
    
  
    
  
    
  <entry>
    <title>CompanionCube32: DLL Injection, VTable Hooking, and an Overlay</title>
    <link rel="alternate" type="text/html" href="https://blog.segv.page/blog/2025/11/03/CompanionCube32-DLL-injection-vtable-hooking-overlay/"/>
    <id>urn:uuid:e841b2e7-3a5d-4f8c-9b1d-6a2c8e4f0b3a</id>
    <updated>2025-11-03T17:30:00Z</updated>
    <category term="cpp"/><category term="win32"/><category term="x86"/><category term="reversing"/>
    <content type="html">
      <![CDATA[<p>CompanionCube32 is a modding tool for Portal (2007) that injects a DLL into the game process and renders a DX9 overlay via vtable hooking. It’s two components: a command-line injector that gets the DLL into the target, and the DLL itself that intercepts the render pipeline and draws with Dear ImGui.</p>

<h2 id="dll-injection">DLL Injection</h2>

<p>The injector uses <code class="language-plaintext highlighter-rouge">CreateToolhelp32Snapshot</code> to walk the process list and find the target by name. Once located, it opens the process with <code class="language-plaintext highlighter-rouge">PROCESS_ALL_ACCESS</code>, writes the DLL path into remote memory via <code class="language-plaintext highlighter-rouge">VirtualAllocEx</code> and <code class="language-plaintext highlighter-rouge">WriteProcessMemory</code>, then calls <code class="language-plaintext highlighter-rouge">CreateRemoteThread</code> with <code class="language-plaintext highlighter-rouge">LoadLibraryA</code> as the entry point.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">HANDLE</span> <span class="n">hProcess</span> <span class="o">=</span> <span class="n">OpenProcess</span><span class="p">(</span><span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
<span class="n">LPVOID</span> <span class="n">pRemotePath</span> <span class="o">=</span> <span class="n">VirtualAllocEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">pathLen</span><span class="p">,</span>
                                     <span class="n">MEM_COMMIT</span><span class="p">,</span> <span class="n">PAGE_READWRITE</span><span class="p">);</span>
<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">pRemotePath</span><span class="p">,</span> <span class="n">dllPath</span><span class="p">,</span> <span class="n">pathLen</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">CreateRemoteThread</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">(</span><span class="n">LPTHREAD_START_ROUTINE</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">GetModuleHandle</span><span class="p">(</span><span class="s">"kernel32"</span><span class="p">),</span>
                                           <span class="s">"LoadLibraryA"</span><span class="p">),</span>
    <span class="n">pRemotePath</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</code></pre></div></div>

<p>The thread handle is waited on and the remote allocation is freed after the DLL loads. Error handling covers the obvious failure modes — process not found, access denied, injection timeout.</p>

<h2 id="vtable-hooking-with-minhook">VTable Hooking With MinHook</h2>

<p>Once the DLL lives inside Portal, it needs to intercept the DirectX 9 render pipeline. The approach is vtable patching: every D3D9 object’s first field is a pointer to its virtual function table. By swapping entries in that table we can redirect calls to our own functions.</p>

<p>The setup happens on <code class="language-plaintext highlighter-rouge">DLL_PROCESS_ATTACH</code>. A dedicated thread initialises MinHook, creates a temporary <code class="language-plaintext highlighter-rouge">IDirect3D9</code> object to retrieve the vtable, and hooks <code class="language-plaintext highlighter-rouge">IDirect3D9::CreateDevice</code> (vtable index 16).</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="o">*</span><span class="n">createDeviceVtable</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="n">d3d9</span><span class="p">;</span>
<span class="n">MH_CreateHook</span><span class="p">((</span><span class="kt">void</span><span class="o">*</span><span class="p">)((</span><span class="n">DWORD_PTR</span><span class="o">*</span><span class="p">)</span><span class="n">createDeviceVtable</span><span class="p">)[</span><span class="mi">16</span><span class="p">],</span>
              <span class="n">hkCreateDevice</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">oCreateDevice</span><span class="p">);</span>
<span class="n">MH_EnableHook</span><span class="p">(</span><span class="n">MH_ALL_HOOKS</span><span class="p">);</span>
</code></pre></div></div>

<p>When Portal calls <code class="language-plaintext highlighter-rouge">CreateDevice</code>, the hook saves the device pointer and chains a second hook onto <code class="language-plaintext highlighter-rouge">IDirect3DDevice9::EndScene</code> (vtable index 42). Every frame, the <code class="language-plaintext highlighter-rouge">EndScene</code> hook renders the overlay.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">HRESULT</span> <span class="n">WINAPI</span> <span class="nf">hkEndScene</span><span class="p">(</span><span class="n">IDirect3DDevice9</span> <span class="o">*</span><span class="n">device</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">initialised</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">IMGUI_CHECKVERSION</span><span class="p">();</span>
        <span class="n">ImGui</span><span class="o">::</span><span class="n">CreateContext</span><span class="p">();</span>
        <span class="n">ImGui_ImplWin32_Init</span><span class="p">(</span><span class="n">FindWindow</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="s">L"Portal"</span><span class="p">));</span>
        <span class="n">ImGui_ImplDX9_Init</span><span class="p">(</span><span class="n">device</span><span class="p">);</span>
        <span class="n">initialised</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">ImGui_ImplDX9_NewFrame</span><span class="p">();</span>
    <span class="n">ImGui_ImplWin32_NewFrame</span><span class="p">();</span>
    <span class="n">ImGui</span><span class="o">::</span><span class="n">NewFrame</span><span class="p">();</span>
    <span class="n">ImGui</span><span class="o">::</span><span class="n">Begin</span><span class="p">(</span><span class="s">"CC-Trainer"</span><span class="p">);</span>
    <span class="n">ImGui</span><span class="o">::</span><span class="n">Text</span><span class="p">(</span><span class="s">"Hello from CC-Trainer!"</span><span class="p">);</span>
    <span class="n">ImGui</span><span class="o">::</span><span class="n">End</span><span class="p">();</span>
    <span class="n">ImGui</span><span class="o">::</span><span class="n">EndFrame</span><span class="p">();</span>
    <span class="n">ImGui</span><span class="o">::</span><span class="n">Render</span><span class="p">();</span>
    <span class="n">ImGui_ImplDX9_RenderDrawData</span><span class="p">(</span><span class="n">ImGui</span><span class="o">::</span><span class="n">GetDrawData</span><span class="p">());</span>
    <span class="k">return</span> <span class="n">oEndScene</span><span class="p">(</span><span class="n">device</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>MinHook handles the tricky parts — allocating executable memory near the target for the trampoline, patching the function prologue, and restoring the original bytes when the hook is removed. The library ships its own x86/x64 instruction length disassembler (HDE) to determine where to place the detour.</p>

<h2 id="build">Build</h2>

<p>The injector builds as a static executable, the trainer as a DLL. Both use CMake with MinGW/GCC and C++23. ImGui and MinHook sources are compiled directly into the trainer — no separate library dependencies at runtime.</p>

<h2 id="results">Results</h2>

<p>The injector loads the DLL in under 10ms. The hook chain resolves the vtable in microseconds. The overlay runs at the game’s native framerate with no perceptible overhead. The entire pipeline — inject, hook, render — is about 250 lines of C++ and has held up across repeated Portal sessions with no crashes or desyncs.</p>
]]>
    </content>
  </entry>
    
  
    
  
    
  
    
  

</feed>
