<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://apple1417.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://apple1417.dev/" rel="alternate" type="text/html" /><updated>2026-07-25T17:27:05+00:00</updated><id>https://apple1417.dev/feed.xml</id><title type="html">apple1417.dev</title><entry><title type="html">Linking against Python when Cross Compiling, Revisited</title><link href="https://apple1417.dev/posts/2026-03-22-python-cross-compiling" rel="alternate" type="text/html" title="Linking against Python when Cross Compiling, Revisited" /><published>2026-03-22T00:00:00+00:00</published><updated>2026-03-22T00:00:00+00:00</updated><id>https://apple1417.dev/posts/python-cross-compiling</id><content type="html" xml:base="https://apple1417.dev/posts/2026-03-22-python-cross-compiling"><![CDATA[<p>I have a windows executable, which uses <a href="https://github.com/pybind/pybind11">pybind11</a> to embed a
Python interpreter. How can I compile it from a Linux host? I’m assuming you already have a working
cross-compiler, and while you could try cross-compile Python itself, I’d rather link against the
pre-built version to not have to worry about all the extra dependencies.</p>

<p>I’ve previously written a
<a href="/posts/2023-07-03-python-cross-compiling">post about this same topic</a>, however
changes to how Python for Windows is packaged mean the method described there will stop working, and
the new method turns out to be a lot simpler.</p>

<h1 id="using-an-existing-windows-install">Using an existing Windows Install</h1>
<p>Firstly, let’s try just using an existing Window install, we’ll worry about grabbing the files we
need later.</p>

<p>Our initial CMake file looks something like this.</p>
<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="nb">find_package</span><span class="p">(</span>Python 3.14 COMPONENTS Development<span class="p">)</span>
<span class="nb">add_subdirectory</span><span class="p">(</span>pybind11<span class="p">)</span>

<span class="nb">target_link_libraries</span><span class="p">(</span>my_app PRIVATE
    pybind11::embed
    pybind11::lto
    pybind11::windows_extras
<span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The first step is to remove <code class="language-plaintext highlighter-rouge">FindPython</code>. It really feels like it’s designed around creating a
module, it’s hard to coerce into finding a particular install (e.g. it will pick 64bit installs when
compiling for 32bit), and critically it will only find the host’s install, it just doesn’t work at
all when cross compiling. If we don’t call it, pybind will for us, so we need to disable that too.</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="nb">set</span><span class="p">(</span>PYBIND11_NOPYTHON True<span class="p">)</span>
<span class="nb">add_subdirectory</span><span class="p">(</span>pybind11<span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Next we point it at the files from the Windows install manually.</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="nb">target_include_directories</span><span class="p">(</span>my_app PRIVATE <span class="s2">"</span><span class="si">${</span><span class="nv">WIN_PYTHON_DIR</span><span class="si">}</span><span class="s2">/include"</span><span class="p">)</span>

<span class="nb">file</span><span class="p">(</span>GLOB _py_libs <span class="s2">"</span><span class="si">${</span><span class="nv">WIN_PYTHON_DIR</span><span class="si">}</span><span class="s2">/libs/*.lib"</span><span class="p">)</span>
<span class="nb">target_link_libraries</span><span class="p">(</span>my_app PRIVATE <span class="si">${</span><span class="nv">_py_libs</span><span class="si">}</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And just like that, we’re done already, we can compile. If you’re just building a Python module,
this should be enough, but since we’re building an embedded interpreter we need a couple more files.
On the Python website, one of the extra downloads for each version is <code class="language-plaintext highlighter-rouge">Windows embeddable package</code> -
we can just download this, and stick it in our binaries folder. We’ll automate this later.</p>

<h1 id="without-using-windows-or-wine">Without Using Windows (or Wine)</h1>
<p>So now let’s try grab these files without using Windows at all. This is where the major changes
compared to my previous method are.</p>

<p>Exploring the Python ftp a little, we can find where the Windows files are downloaded from. These
files exist for all versions from 3.11 upwards - if you want to link against an older version of
Python for some reason, you’ll have to use
<a href="/posts/2023-07-03-python-cross-compiling">my previous method</a> instead.</p>

<p>The main install is just:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>https://www.python.org/ftp/python/&lt;version&gt;/python-&lt;version&gt;-&lt;arch&gt;.zip
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If you want a version with debug symbols (perhaps for your debug builds), they’re in:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>https://www.python.org/ftp/python/&lt;version&gt;/python-&lt;version&gt;-test-&lt;arch&gt;.zip
</pre></td></tr></tbody></table></code></pre></div></div>
<p>This also includes a bunch of test modules (hence the name), but you can just ignore them.</p>

<p>If you want free-threaded builds, they’re in:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>https://www.python.org/ftp/python/&lt;version&gt;/python-&lt;version&gt;t-&lt;arch&gt;.zip
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And finally, the embedded package is in:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>https://www.python.org/ftp/python/&lt;version&gt;/python-&lt;version&gt;-embeddable-&lt;arch&gt;.zip
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Since all these files are all just zips, CMake <code class="language-plaintext highlighter-rouge">FetchContent</code> can handle them all for you, with no
need for external scripts or dependencies. A lot easier than my previous method.</p>

<h2 id="custom-embeddable-package">Custom embeddable package</h2>
<p>There’s no debug or free-threaded embeddable packages, if you want these you’ll have to create your
own. You can even do this for the main install, you don’t actually need to download the embeddable
package.</p>

<p>Most of the embeddable package is just a bunch of <code class="language-plaintext highlighter-rouge">.dll</code>s/<code class="language-plaintext highlighter-rouge">.pyd</code>s. You can grab them from your
install:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">python3.dll</code></li>
  <li><code class="language-plaintext highlighter-rouge">python314.dll</code></li>
  <li><code class="language-plaintext highlighter-rouge">DLLs/*.dll</code></li>
  <li><code class="language-plaintext highlighter-rouge">DLLs/*.pyd</code></li>
</ul>

<p>If you’re using the test package, you probably also want to grab all the <code class="language-plaintext highlighter-rouge">.pdb</code>s.</p>

<p>The next important thing is the <code class="language-plaintext highlighter-rouge">python314.zip</code>. This zip holds the compiled bytecode of the entire
standard library. You <em>could</em> create this yourself using
<a href="https://docs.python.org/3/library/zipfile.html#pyzipfile-objects"><code class="language-plaintext highlighter-rouge">PyZipFile</code></a> - but Python
bytecode isn’t stable across major versions, and compiling it with the exact same version adds a
bunch of complexity to the build. Another option is just to grab it from the base embeddable
package, it’s bytecode, it’s not binary specific. Or you could just copy the <code class="language-plaintext highlighter-rouge">Lib</code> folder as-is, it
won’t be precompiled, but it’s the same sources.</p>

<p>The last file you may be interested in is the <code class="language-plaintext highlighter-rouge">python314._pth</code>.
<a href="https://docs.python.org/3/library/sys_path_init.html#pth-files"><code class="language-plaintext highlighter-rouge">._pth</code> files</a> set up a default
<code class="language-plaintext highlighter-rouge">sys.path</code>, you might not even need one. If you do, they’re just a couple lines of text, so its easy
to make your own.</p>

<h1 id="final-cmake-file">Final CMake File</h1>
<p>If we put it all together, we get the following. I’m skipping over the free threaded and debug
builds, it’s relatively simple to swap them in (even based on build type).</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
</pre></td><td class="rouge-code"><pre><span class="nb">set</span><span class="p">(</span>VERSION <span class="s2">"3.14.3"</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>ARCH <span class="s2">"amd64"</span><span class="p">)</span>

<span class="c1"># Download the relevant zips</span>
<span class="nb">include</span><span class="p">(</span>FetchContent<span class="p">)</span>
<span class="nf">FetchContent_Declare</span><span class="p">(</span>
    _python_main
    URL https://www.python.org/ftp/python/<span class="si">${</span><span class="nv">VERSION</span><span class="si">}</span>/python-<span class="si">${</span><span class="nv">VERSION</span><span class="si">}</span>-<span class="si">${</span><span class="nv">ARCH</span><span class="si">}</span>.zip
    <span class="c1"># Maybe add URL_HASH</span>
<span class="p">)</span>
<span class="nf">FetchContent_Declare</span><span class="p">(</span>
    _python_embeddable
    URL https://www.python.org/ftp/python/<span class="si">${</span><span class="nv">VERSION</span><span class="si">}</span>/python-<span class="si">${</span><span class="nv">VERSION</span><span class="si">}</span>-embeddable-<span class="si">${</span><span class="nv">ARCH</span><span class="si">}</span>.zip
<span class="p">)</span>
<span class="nf">FetchContent_MakeAvailable</span><span class="p">(</span>_python_main _python_embeddable<span class="p">)</span>

<span class="c1"># Link against the downloaded version</span>
<span class="nb">target_include_directories</span><span class="p">(</span>my_app PRIVATE <span class="s2">"</span><span class="si">${</span><span class="nv">_python_main_SOURCE_DIR</span><span class="si">}</span><span class="s2">/include"</span><span class="p">)</span>

<span class="nb">file</span><span class="p">(</span>GLOB _py_libs <span class="s2">"</span><span class="si">${</span><span class="nv">_python_main_SOURCE_DIR</span><span class="si">}</span><span class="s2">/libs/*.lib"</span><span class="p">)</span>
<span class="nb">target_link_libraries</span><span class="p">(</span>my_app PRIVATE <span class="si">${</span><span class="nv">_py_libs</span><span class="si">}</span><span class="p">)</span>

<span class="c1"># Make an install grab all the same files as an embeddable build</span>
<span class="nb">file</span><span class="p">(</span>GLOB _version_dll <span class="s2">"</span><span class="si">${</span><span class="nv">_python_main_SOURCE_DIR</span><span class="si">}</span><span class="s2">/python3?*.dll"</span><span class="p">)</span>
<span class="nb">file</span><span class="p">(</span>GLOB _dlls <span class="s2">"</span><span class="si">${</span><span class="nv">_python_main_SOURCE_DIR</span><span class="si">}</span><span class="s2">/DLLs/*.dll"</span><span class="p">)</span>
<span class="nb">file</span><span class="p">(</span>GLOB _pyds <span class="s2">"</span><span class="si">${</span><span class="nv">_python_main_SOURCE_DIR</span><span class="si">}</span><span class="s2">/DLLs/*.pyd"</span><span class="p">)</span>
<span class="nb">file</span><span class="p">(</span>GLOB _bytecode_zip <span class="s2">"</span><span class="si">${</span><span class="nv">_python_embeddable_SOURCE_DIR</span><span class="si">}</span><span class="s2">/python*.zip"</span><span class="p">)</span>
<span class="nb">install</span><span class="p">(</span>
    FILES
        <span class="si">${</span><span class="nv">_python_main_SOURCE_DIR</span><span class="si">}</span>/python3.dll
        <span class="si">${</span><span class="nv">_version_dll</span><span class="si">}</span>
        <span class="si">${</span><span class="nv">_dlls</span><span class="si">}</span>
        <span class="si">${</span><span class="nv">_pyds</span><span class="si">}</span>
        <span class="si">${</span><span class="nv">_bytecode_zip</span><span class="si">}</span>
    DESTINATION <span class="s2">"my_app"</span>
<span class="p">)</span>
<span class="c1"># Alternatively, to use the Lib folder instead of the bytecode zip:</span>
<span class="c1"># install(</span>
<span class="c1">#     DIRECTORY "${_python_main_SOURCE_DIR}/Lib"</span>
<span class="c1">#     DESTINATION "my_app"</span>
<span class="c1"># )</span>
<span class="nf">cmake_path</span><span class="p">(</span>GET _version_dll STEM _version_stem<span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>_pth_contents <span class="s2">"</span><span class="si">${</span><span class="nv">_version_stem</span><span class="si">}</span><span class="s2">.zip</span><span class="se">\n</span><span class="s2">.</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="nb">install</span><span class="p">(</span>CODE <span class="s2">"file(WRITE $&lt;INSTALL_PREFIX&gt;/my_app/</span><span class="si">${</span><span class="nv">_version_stem</span><span class="si">}</span><span class="s2">._pth </span><span class="se">\"</span><span class="si">${</span><span class="nv">_pth_contents</span><span class="si">}</span><span class="se">\"</span><span class="s2">)"</span><span class="p">)</span>

<span class="c1"># Link with pybind</span>
<span class="nb">set</span><span class="p">(</span>PYBIND11_NOPYTHON True<span class="p">)</span>
<span class="nb">add_subdirectory</span><span class="p">(</span>pybind11<span class="p">)</span>

<span class="nb">target_link_libraries</span><span class="p">(</span>my_app PRIVATE
    pybind11::embed
    pybind11::lto
    pybind11::windows_extras
<span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[How to link against the pre-built Python for Windows libraries from a Linux host.]]></summary></entry><entry><title type="html">Python Import Hooks in Practice</title><link href="https://apple1417.dev/posts/2025-12-14-python-import-hooks-in-practice" rel="alternate" type="text/html" title="Python Import Hooks in Practice" /><published>2025-12-14T00:00:00+00:00</published><updated>2025-12-14T00:00:00+00:00</updated><id>https://apple1417.dev/posts/python-import-hooks-in-practice</id><content type="html" xml:base="https://apple1417.dev/posts/2025-12-14-python-import-hooks-in-practice"><![CDATA[<p>Python allows you to write Import Hooks to customize how the import system works. The official docs
are quite information dense however, and since there usually isn’t much reason to mess with them,
there aren’t many simpler examples out there. In this post I’ll show you how to get started, and
show off a few cases I had a use for them.</p>

<p>As a bit of background, all the use cases I’ll be discussing come from the legacy compat wrapper
of the <a href="https://github.com/bl-sdk/willow2-mod-manager/">Borderlands 2/TPS Python Mod Manager</a>. For
reasons I won’t go into, there was a major update with many breaking changes, and the goal was to
get all old mods running under the new version without needing them to be updated. This context does
heavily affect the viability of some of these use cases, as we’ll see later. I’m going to give
examples you can run locally though, all tested in Python 3.13.</p>

<p>I also have a <a href="/posts/2024-06-08-python-import-hook-aliasing">previous post</a> about
using import hooks to create module aliases, to allow importing them under another name. It is not
required reading, I’ll be going over the basics again. If you’re interested in the import system,
it does dive into some other details not covered here.</p>

<h1 id="your-first-import-hook">Your first import hook</h1>
<p>So lets get started: how do you write an import hook. The relevant docs are the
<a href="https://docs.python.org/3/reference/import.html">import system</a> and
<a href="https://docs.python.org/3/library/importlib.html"><code class="language-plaintext highlighter-rouge">importlib</code></a>. As I mentioned at the start of the
post, these are very information dense. It’s best to keep them open, you’ll probably want to refer
back to them a lot.</p>

<p>There are two types of import hook: a meta path finder, and a path entry finder. I haven’t used path
entry finders, so this post won’t have have any info on them. I <em>think</em> the difference is path
finders specifically relate to real files on disk, while meta path finders are an earlier type of
hook. From this point onwards, anywhere this post talks about an import hook, I mean a meta path
finder.</p>

<p>The simplest import hook looks like the following:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Sequence</span>
<span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">ModuleSpec</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="k">class</span> <span class="nc">LoggingMetaPathFinder</span><span class="p">:</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">find_spec</span><span class="p">(</span>
        <span class="n">cls</span><span class="p">,</span>
        <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">path</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">target</span><span class="p">:</span> <span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">None</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>All meta path finders go into the <code class="language-plaintext highlighter-rouge">sys.meta_path</code> list. When trying to import something, each one’s
tried in sequence, stopping when the first returns a module spec - returning None means “I don’t
know how to find it”.</p>

<p>Lets try it out.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">sys</span><span class="p">.</span><span class="n">meta_path</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">LoggingMetaPathFinder</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">importlib</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">xml.etree.ElementTree</span> <span class="k">as</span> <span class="n">ET</span>
<span class="n">xml</span> <span class="bp">None</span> <span class="bp">None</span>
<span class="n">xml</span><span class="p">.</span><span class="n">etree</span> <span class="p">[</span><span class="s">'/usr/lib/python3.13/xml'</span><span class="p">]</span> <span class="bp">None</span>
<span class="n">xml</span><span class="p">.</span><span class="n">etree</span><span class="p">.</span><span class="n">ElementTree</span> <span class="p">[</span><span class="s">'/usr/lib/python3.13/xml/etree'</span><span class="p">]</span> <span class="bp">None</span>
<span class="n">xml</span><span class="p">.</span><span class="n">etree</span><span class="p">.</span><span class="n">ElementPath</span> <span class="p">[</span><span class="s">'/usr/lib/python3.13/xml/etree'</span><span class="p">]</span> <span class="bp">None</span>
<span class="n">_elementtree</span> <span class="bp">None</span> <span class="bp">None</span>
<span class="n">pyexpat</span> <span class="bp">None</span> <span class="bp">None</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The first thing to note here is that modules which have already been imported do not go through
import hooks. I find this is a slightly more well known fact, the import statement first does a
lookup in <code class="language-plaintext highlighter-rouge">sys.modules</code>. If you just want to add some fake names, which don’t line up with what’s on
disk, messing with <code class="language-plaintext highlighter-rouge">sys.modules</code> might be all you need - see also my
<a href="/posts/2024-06-08-python-import-hook-aliasing">previous post</a>.</p>

<p>When we come to a fresh import, we can see how Python always has to import the top level modules
first. This is somewhat intuitive again, if the <code class="language-plaintext highlighter-rouge">xml</code> module could not be found, it would stop
there. We then also see a few extra imports at the end, triggered by the contents of <code class="language-plaintext highlighter-rouge">ElementTree</code>
specifically - in this case neither of the parent modules had uncached imports of their own.</p>

<p>So what do each of these args actually mean. As you’ve probably already guessed, <code class="language-plaintext highlighter-rouge">fullname</code> is the
full name of the module being imported.</p>

<p><code class="language-plaintext highlighter-rouge">path</code> is a little more interesting. It’s <code class="language-plaintext highlighter-rouge">None</code> when doing a top level import, otherwise it’s a
sequence of strings the import system may look for child modules. In normal usage, it’s always a
list of length 1, but it can be other types or different lengths when you start customizing things.
Each import hook decides how it parses these paths itself - typically they’re treated as files, but
you could treat it as a url for example. The arg actually comes from <code class="language-plaintext highlighter-rouge">parent_module.__path__</code>, so
you could imagine your hook also setting that to include the url in the first place.</p>

<p>The last argument, <code class="language-plaintext highlighter-rouge">target</code> has been <code class="language-plaintext highlighter-rouge">None</code> in every example so far. It’s relevant when it comes to
reloading modules - it’s passed the old module. I don’t think there’s any default logic using it,
but can certainly imagine how it might be useful in a custom import hook.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">_</span> <span class="o">=</span> <span class="n">importlib</span><span class="p">.</span><span class="nb">reload</span><span class="p">(</span><span class="n">ET</span><span class="p">)</span>
<span class="n">xml</span><span class="p">.</span><span class="n">etree</span><span class="p">.</span><span class="n">ElementTree</span> <span class="p">[</span><span class="s">'/usr/lib/python3.13/xml/etree'</span><span class="p">]</span> <span class="o">&lt;</span><span class="n">module</span> <span class="s">'xml.etree.ElementTree'</span> <span class="k">from</span> <span class="s">'/usr/lib/python3.13/xml/etree/ElementTree.py'</span><span class="o">&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The last question then is what’s this <code class="language-plaintext highlighter-rouge">ModuleSpec</code> we’re supposed to return? From the type hint,
obviously it’s supposed to be an <code class="language-plaintext highlighter-rouge">importlib.machinery.ModuleSpec</code> instance, but what’s it supposed
to have on it? My answer is, honestly, don’t worry about it. <code class="language-plaintext highlighter-rouge">importlib.util</code> has two helpers,
<code class="language-plaintext highlighter-rouge">spec_from_loader</code> and <code class="language-plaintext highlighter-rouge">spec_from_file_location</code>, which can create it for you. You can mostly treat
it as an opaque type.</p>

<h1 id="demo-import-hook">Demo Import Hook</h1>
<p>Every import hook really starts the same way: you do some form of matching on the arguments to
decide if this is a module you understand. In the mod manager, I did this by walking
<code class="language-plaintext highlighter-rouge">inspect.frame()</code> to find the importing module, and then just using a big
<code class="language-plaintext highlighter-rouge">match importing_module_name, fullname:</code> statement. This is ultimately going to be very specific to
your use case.</p>

<p>The interesting part of using import hooks is really in how you construct the module spec. To
facilitate showing that off better, I’m going to use this import hook in the following examples:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Sequence</span>
<span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">ModuleSpec</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="n">next_module_spec</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="nb">str</span><span class="p">],</span> <span class="n">ModuleSpec</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span>

<span class="k">class</span> <span class="nc">DemoMetaPathFinder</span><span class="p">:</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">find_spec</span><span class="p">(</span>
        <span class="n">cls</span><span class="p">,</span>
        <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">path</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">target</span><span class="p">:</span> <span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">fullname</span><span class="p">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">"demo"</span><span class="p">):</span>
            <span class="k">return</span> <span class="n">next_module_spec</span><span class="p">(</span><span class="n">fullname</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">None</span>

<span class="c1"># Append so it only applies to otherwise failed lookups
</span><span class="n">sys</span><span class="p">.</span><span class="n">meta_path</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">DemoMetaPathFinder</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This lets us more easily add a custom module spec to new imports while messing around in the REPL.</p>

<h1 id="importing-from-a-different-path">Importing from a different path</h1>
<p>So normally the import hierarchy is strongly tied to the filesystem. What if we wanted to break
this, and import a file from a different arbitrary path? The original motivation for this case was
the following scenario:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre>MyMod
|-- __init__.py
`-- dist
    `-- semver.py
</pre></td></tr></tbody></table></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">MyMod.py</code>:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">site</span>
<span class="n">site</span><span class="p">.</span><span class="n">addsitedir</span><span class="p">(</span><span class="s">"Mods/MyMod/dist"</span><span class="p">)</span>
<span class="kn">import</span> <span class="nn">semver</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The Mod Manager isn’t well integrated with standard PyPI packages, so it’s somewhat common practice
to vendor dependencies. But one of the breaking changed broke this relative path - how can we make
the import still work without editing the code?</p>

<p>Now this specific case is simple enough we could solve it a few other ways. I could just have also
added the correct path, knowing that it would do nothing if it wasn’t installed, or I could mess
with <code class="language-plaintext highlighter-rouge">sys.modules</code>. Really, I mostly just used an import hook because I already had one.</p>

<p>If we’re going to use an import hook anyway, this is the perfect case for
<code class="language-plaintext highlighter-rouge">importlib.util.spec_from_file_location()</code>. It creates a module spec just like importing a from a
normal source file - but with no requirements on where that file is located with respect to anything
else.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">importlib.util</span> <span class="kn">import</span> <span class="n">spec_from_file_location</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">next_module_spec</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">_</span><span class="p">:</span> <span class="n">spec_from_file_location</span><span class="p">(</span><span class="s">"demo_json"</span><span class="p">,</span> <span class="s">"/usr/lib/python3.13/json/__init__.py"</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">demo_json</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">demo_json</span>
<span class="o">&lt;</span><span class="n">module</span> <span class="s">'demo_json'</span> <span class="k">from</span> <span class="s">'/usr/lib/python3.13/json/__init__.py'</span><span class="o">&gt;</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">demo_json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span><span class="s">"hi"</span><span class="p">:</span> <span class="mi">1</span><span class="p">})</span>
<span class="s">'{"hi": 1}'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Interestingly, the module name you pass can be essentially arbitrary. However, this might prevent
accessing nested modules.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">next_module_spec</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">_</span><span class="p">:</span> <span class="n">spec_from_file_location</span><span class="p">(</span><span class="s">"fake.fake with spaces.fake"</span><span class="p">,</span> <span class="s">"/usr/lib/python3.13/json/__init__.py"</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">demo_json2</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">demo_json2</span>
<span class="o">&lt;</span><span class="n">module</span> <span class="s">'fake.fake with spaces.fake'</span> <span class="k">from</span> <span class="s">'/usr/lib/python3.13/json/__init__.py'</span><span class="o">&gt;</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">demo_json</span> <span class="kn">import</span> <span class="n">nested</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">demo_json2</span> <span class="kn">import</span> <span class="n">nested2</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
  <span class="n">File</span> <span class="s">"&lt;python-input-10&gt;"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
    <span class="kn">from</span> <span class="nn">demo_json2</span> <span class="kn">import</span> <span class="n">nested2</span>
<span class="nb">ImportError</span><span class="p">:</span> <span class="n">cannot</span> <span class="kn">import</span> <span class="nn">name</span> <span class="s">'nested2'</span> <span class="k">from</span> <span class="s">'fake.fake with spaces.fake'</span> <span class="p">(</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">python3</span><span class="p">.</span><span class="mi">13</span><span class="o">/</span><span class="n">json</span><span class="o">/</span><span class="n">__init__</span><span class="p">.</span><span class="n">py</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now lets try something more advanced.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="nn">importlib.util</span> <span class="kn">import</span> <span class="n">spec_from_file_location</span>

<span class="k">def</span> <span class="nf">next_module_spec</span><span class="p">(</span><span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span><span class="p">:</span>
    <span class="n">path</span> <span class="o">=</span> <span class="s">"/usr/lib/python3.13/encodings/"</span> <span class="o">+</span> <span class="n">fullname</span><span class="p">.</span><span class="n">removeprefix</span><span class="p">(</span><span class="s">"demo_"</span><span class="p">)</span> <span class="o">+</span> <span class="s">".py"</span>
    <span class="k">return</span> <span class="n">spec_from_file_location</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">demo_utf_8</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">demo_utf_8</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="s">"🎉"</span><span class="p">)</span>
<span class="p">(</span><span class="sa">b</span><span class="s">'</span><span class="se">\xf0\x9f\x8e\x89</span><span class="s">'</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You could imagine this logic getting quite complex.</p>

<p>So far we’ve kind of been looking at the output - we know what path we want, how do we tell that to
the import system. Sometimes you might be interested in the input - given a module name, where would
it normally be imported from. You might then apply transformations to that path. You can get this
info via <code class="language-plaintext highlighter-rouge">importlib.machinery.PathFinder.find_spec()</code>, which implements the default import logic.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">PathFinder</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">spec</span> <span class="o">=</span> <span class="n">PathFinder</span><span class="p">.</span><span class="n">find_spec</span><span class="p">(</span><span class="s">"xml"</span><span class="p">,</span> <span class="bp">None</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">spec</span><span class="p">.</span><span class="n">origin</span>
<span class="s">'/usr/lib/python3.13/xml/__init__.py'</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">spec</span><span class="p">.</span><span class="n">submodule_search_locations</span>
<span class="p">[</span><span class="s">'/usr/lib/python3.13/xml'</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h1 id="importing-from-a-string">Importing from a string</h1>
<p>This example was motivated by a very similar case to the previous one.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">site</span>
<span class="n">site</span><span class="p">.</span><span class="n">addsitedir</span><span class="p">(</span><span class="s">"Mods/MyMod/dist"</span><span class="p">)</span>
<span class="kn">import</span> <span class="nn">requests</span>

<span class="k">try</span><span class="p">:</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="sa">f</span><span class="s">"https://api.github.com/repos/</span><span class="si">{</span><span class="n">repository</span><span class="si">}</span><span class="s">/releases"</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
    <span class="n">check_updates_available</span><span class="p">(</span><span class="n">response</span><span class="p">)</span>
<span class="k">except</span> <span class="nb">Exception</span><span class="p">:</span>
    <span class="p">...</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Unfortunately, this mod shipped a very old version of requests, which ran into several issues trying
to run in a far newer version of Python than it was made for. But it was only ever used in this one
spot. Couldn’t we just fake this one call, what if we pretended the entire source file was just:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">timeout</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="k">raise</span> <span class="nb">NotImplementedError</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><small>
The mod’s author agreed with breaking the update check if it got it working on new versions.
</small></p>

<p>To tackle this one, we need to start learning about loaders. A loader is what, given a module spec,
actually loads the module. All loaders look like the following:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">ModuleSpec</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="k">class</span> <span class="nc">Loader</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">create_module</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">spec</span><span class="p">:</span> <span class="n">ModuleSpec</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">types</span><span class="p">.</span><span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">pass</span>
    <span class="k">def</span> <span class="nf">exec_module</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">module</span><span class="p">:</span> <span class="n">types</span><span class="p">.</span><span class="n">ModuleType</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">pass</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>We’re not going to worry about the exact semantics of this class too much - once again <code class="language-plaintext highlighter-rouge">importlib</code>
implements a number of helper classes we’re going to use instead. I’m showing it just because I
wanted to point out this two phase initialization - it may occasionally be relevant when deciding
what methods to overload.</p>

<p>I found the easiest loader to work off of was <code class="language-plaintext highlighter-rouge">importlib.machinery.SourceFileLoader</code>. This is
essentially the standard import system loader, and it’s a concrete class that’s already implemented
everything, so we can just overwrite the specific parts we care about. Unfortunately, the importlib
docs are very information dense, and the source code is very convoluted, using multiple inheritance
in several places, so finding what you want can still be quite a challenge. Eventually, I found
<code class="language-plaintext highlighter-rouge">FileLoader.get_data()</code>:</p>

<blockquote>
  <p>Reads path as a binary file and returns the bytes from it.</p>
</blockquote>

<p>Let’s give it a shot.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">SourceFileLoader</span>

<span class="k">class</span> <span class="nc">StringSourceLoader</span><span class="p">(</span><span class="n">SourceFileLoader</span><span class="p">):</span>
    <span class="n">source</span><span class="p">:</span> <span class="nb">bytes</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">source</span><span class="p">:</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
        <span class="nb">super</span><span class="p">().</span><span class="n">__init__</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="s">"&lt;string&gt;"</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">source</span> <span class="o">=</span> <span class="n">source</span>

    <span class="k">def</span> <span class="nf">get_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bytes</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">source</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>One oddity is this <code class="language-plaintext highlighter-rouge">path="&lt;string&gt;"</code> - while not required in the raw loader definition, basically
all the importlib machinery assumes you’re loading from real files. Luckily, this is mostly just
visual, for debugging, like with tracebacks or <code class="language-plaintext highlighter-rouge">inspect.getfile</code>, it doesn’t need to be a real path.</p>

<p>So we’ve got our loader. How do we turn it into a module spec? <code class="language-plaintext highlighter-rouge">importlib.util.spec_from_loader()</code>.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">importlib.util</span> <span class="kn">import</span> <span class="n">spec_from_loader</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">source_code</span> <span class="o">=</span> <span class="sa">b</span><span class="s">"""
def get(url: str, timeout: int) -&gt; str:
    raise NotImplementedError
"""</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">loader</span> <span class="o">=</span> <span class="n">StringSourceLoader</span><span class="p">(</span><span class="s">"demo_requests"</span><span class="p">,</span> <span class="n">source_code</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">next_module_spec</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">_</span><span class="p">:</span> <span class="n">spec_from_loader</span><span class="p">(</span><span class="s">"demo_requests"</span><span class="p">,</span> <span class="n">loader</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">demo_requests</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">demo_requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"dummy"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
  <span class="n">File</span> <span class="s">"&lt;python-input-9&gt;"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
    <span class="n">demo_requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"dummy"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="o">~~~~~~~~~~~~~~~~~^^^^^^^^^^^^</span>
  <span class="n">File</span> <span class="s">"&lt;string&gt;"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">3</span><span class="p">,</span> <span class="ow">in</span> <span class="n">get</span>
<span class="nb">NotImplementedError</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>I’m sure you can imagine all sorts of wild things you could build on top of this.</p>

<h1 id="source-code-replacements">Source code replacements</h1>
<p>…like this for one. Maybe don’t do this.</p>

<p>All along we’ve been restrained by the fact that we can’t just edit the source code of existing
mods. It would’ve been so much easier just to subtly tweak those <code class="language-plaintext highlighter-rouge">Mods/MyMod/dist</code> paths. Well if we
can load from raw bytes, who’s to say we can’t?</p>

<p>Now this only really works well in our specific scenario. We have a finite amount of legacy mods,
we know their exact source code, and we know they’re never going to be edited (since updates will
built for the newer version). And in every case it was needed, I was able to talk to the original
mod author, and we agreed it was the best way to handle it. This was only ever used as the solution
of last resort.</p>

<p>You may have seen previous posts by others doing something similar to this using custom encodings
(<a href="https://pydong.org/posts/PythonsPreprocessor/">example 1</a>,
<a href="https://www.bitecode.dev/p/change-pythons-syntax-with-the-coding">example 2</a>). The problem with
using an encoding for us is it requires modifying the source files to add a magic <code class="language-plaintext highlighter-rouge"># coding</code> line.
An import hook can work without modifying the original files - with the caveat being that you need
to get the import hook registered <em>before</em> importing any of the files you want to modify. This
tradeoff works for us.</p>

<p>So to implement this, we want to find the module via the normal import process, but then edit the
file contents when it’s loaded. So we’re going to need a custom loader.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">ReplacementSourceLoader</span><span class="p">(</span><span class="n">FileLoader</span><span class="p">,</span> <span class="n">SourceLoader</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">get_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bytes</span><span class="p">:</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">"rb"</span><span class="p">)</span> <span class="k">as</span> <span class="nb">file</span><span class="p">:</span>
            <span class="n">data</span> <span class="o">=</span> <span class="nb">file</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
            <span class="c1"># Mess with the source code - perhaps use ast.parse()
</span>            <span class="n">data</span> <span class="o">+=</span> <span class="sa">f</span><span class="s">'</span><span class="se">\n</span><span class="s">print("module loaded: </span><span class="si">{</span><span class="bp">self</span><span class="p">.</span><span class="n">name</span><span class="si">}</span><span class="s">")'</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="s">"utf8"</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">data</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>One oddity I ran into was that if I inherited from <code class="language-plaintext highlighter-rouge">SourceFileLoader</code> directly, it would sometimes
get given a path to a bytecode file. Which obviously causes problems when you’re expecting text.
Inheriting from its two base classes did not do this. Some parts of the importlib machinery do
change behaviour based on which subclass is being used, but I didn’t investigate this further.</p>

<p>Since we want to replace contents of an existing source file, the next step is to find where that
actually is. I briefly touched on how to do this earlier, we’re going to want a new meta path finder
class.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">ReplacementMetaPathFinder</span><span class="p">:</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">find_spec</span><span class="p">(</span>
        <span class="n">cls</span><span class="p">,</span>
        <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">path</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">target</span><span class="p">:</span> <span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">spec</span> <span class="o">=</span> <span class="n">PathFinder</span><span class="p">.</span><span class="n">find_spec</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">spec</span> <span class="ow">is</span> <span class="bp">None</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">spec</span><span class="p">.</span><span class="n">has_location</span> <span class="ow">or</span> <span class="n">spec</span><span class="p">.</span><span class="n">origin</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>

        <span class="c1"># No-op: return spec using the same location as the default
</span>        <span class="k">return</span> <span class="n">spec_from_file_location</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">origin</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Finally, we can add some filtering on the specific import, and, when it matches, add our custom
loader. Putting it all together:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Sequence</span>
<span class="kn">from</span> <span class="nn">importlib.abc</span> <span class="kn">import</span> <span class="n">FileLoader</span><span class="p">,</span> <span class="n">SourceLoader</span>
<span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">ModuleSpec</span><span class="p">,</span> <span class="n">PathFinder</span>
<span class="kn">from</span> <span class="nn">importlib.util</span> <span class="kn">import</span> <span class="n">spec_from_file_location</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="k">class</span> <span class="nc">ReplacementSourceLoader</span><span class="p">(</span><span class="n">FileLoader</span><span class="p">,</span> <span class="n">SourceLoader</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">get_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bytes</span><span class="p">:</span>
        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">"rb"</span><span class="p">)</span> <span class="k">as</span> <span class="nb">file</span><span class="p">:</span>
            <span class="n">data</span> <span class="o">=</span> <span class="nb">file</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
            <span class="k">return</span> <span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span>
                <span class="sa">b</span><span class="s">'""".+"""'</span><span class="p">,</span>
                <span class="sa">b</span><span class="s">'"vzcbeg ubbxf pna or qnatrebhf va gur jebat unaqf"'</span><span class="p">,</span>
                <span class="n">data</span><span class="p">,</span>
                <span class="n">flags</span><span class="o">=</span><span class="n">re</span><span class="p">.</span><span class="n">S</span><span class="p">,</span>
            <span class="p">)</span>

<span class="k">class</span> <span class="nc">ReplacementMetaPathFinder</span><span class="p">:</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">find_spec</span><span class="p">(</span>
        <span class="n">cls</span><span class="p">,</span>
        <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">path</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">target</span><span class="p">:</span> <span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">fullname</span> <span class="o">==</span> <span class="s">"this"</span><span class="p">:</span>
            <span class="n">spec</span> <span class="o">=</span> <span class="n">PathFinder</span><span class="p">.</span><span class="n">find_spec</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">spec</span> <span class="ow">is</span> <span class="bp">None</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">spec</span><span class="p">.</span><span class="n">has_location</span> <span class="ow">or</span> <span class="n">spec</span><span class="p">.</span><span class="n">origin</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
                <span class="k">return</span> <span class="bp">None</span>
            <span class="k">return</span> <span class="n">spec_from_file_location</span><span class="p">(</span>
                <span class="n">fullname</span><span class="p">,</span>
                <span class="n">spec</span><span class="p">.</span><span class="n">origin</span><span class="p">,</span>
                <span class="n">loader</span><span class="o">=</span><span class="n">ReplacementSourceLoader</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">spec</span><span class="p">.</span><span class="n">origin</span><span class="p">),</span>
            <span class="p">)</span>
        <span class="k">return</span> <span class="bp">None</span>

<span class="c1"># Has to be first to overwrite the normal import
</span><span class="n">sys</span><span class="p">.</span><span class="n">meta_path</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">ReplacementMetaPathFinder</span><span class="p">)</span>
<span class="kn">import</span> <span class="nn">this</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Give it a try :)</p>]]></content><author><name></name></author><summary type="html"><![CDATA[An introduction to import hooks, and a few things I've found them useful for.]]></summary></entry><entry><title type="html">Runtime Duck Typing in C++</title><link href="https://apple1417.dev/posts/2025-03-02-runtime-duck-typing" rel="alternate" type="text/html" title="Runtime Duck Typing in C++" /><published>2025-03-02T00:00:00+00:00</published><updated>2025-03-02T00:00:00+00:00</updated><id>https://apple1417.dev/posts/runtime-duck-typing</id><content type="html" xml:base="https://apple1417.dev/posts/2025-03-02-runtime-duck-typing"><![CDATA[<p>C⁠+⁠+ is generally considered a statically typed language. Virtual functions and
templates blur the lines a bit, but you generally still need to know how all your types are laid out
at compile time. So what if you didn’t?</p>

<p><a href="/posts/2025-01-15-unreal-object-layouts">In the previous post</a>, I went over
how Unreal Engine’s core class’ layouts have changed over time, and how the Borderlands series’
modding sdk has historically handled these changes. In the sdk code, we don’t actually particularly
care about the exact object layout, all we care about is that a field of the given name and type
exists. We can consider the different versions as different types, and we want to swap between them
using some form of duck typing.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
</pre></td><td class="rouge-code"><pre><span class="k">namespace</span> <span class="n">bl2</span> <span class="p">{</span>

<span class="k">class</span> <span class="nc">UClass</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UStruct</span> <span class="p">{</span> 
    <span class="kt">uint8_t</span> <span class="n">UnknownData00</span><span class="p">[</span><span class="mh">0xCC</span><span class="p">];</span>
    <span class="n">UObject</span><span class="o">*</span> <span class="n">ClassDefaultObject</span><span class="p">;</span>
    <span class="kt">uint8_t</span> <span class="n">UnknownData01</span><span class="p">[</span><span class="mh">0x48</span><span class="p">];</span>
    <span class="n">TArray</span><span class="o">&lt;</span><span class="n">FImplementedInterface</span><span class="o">&gt;</span> <span class="n">Interfaces</span><span class="p">;</span>
<span class="p">};</span>

<span class="p">}</span>

<span class="k">namespace</span> <span class="n">tps</span> <span class="p">{</span>

<span class="k">class</span> <span class="nc">UClass</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UStruct</span> <span class="p">{</span> 
    <span class="kt">uint8_t</span> <span class="n">UnknownData00</span><span class="p">[</span><span class="mh">0xCC</span><span class="p">];</span>
    <span class="n">UObject</span><span class="o">*</span> <span class="n">ClassDefaultObject</span><span class="p">;</span>
    <span class="kt">uint8_t</span> <span class="n">UnknownData01</span><span class="p">[</span><span class="mh">0x14</span><span class="p">];</span>  <span class="c1">// &lt;-- different size</span>
    <span class="n">TArray</span><span class="o">&lt;</span><span class="n">FImplementedInterface</span><span class="o">&gt;</span> <span class="n">Interfaces</span><span class="p">;</span>
<span class="p">};</span>

<span class="p">}</span>

<span class="kt">bool</span> <span class="n">UClass</span><span class="o">::</span><span class="n">implements</span><span class="p">(</span><span class="k">const</span> <span class="n">UClass</span><span class="o">*</span> <span class="n">interface</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">any_of</span><span class="p">(</span><span class="k">this</span><span class="o">-&gt;</span><span class="n">Interfaces</span><span class="p">,</span> <span class="c1">// &lt;-- pick the right one</span>
                               <span class="p">[</span><span class="o">&amp;</span><span class="p">](</span><span class="k">auto</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">x</span><span class="p">.</span><span class="n">Class</span> <span class="o">==</span> <span class="n">interface</span><span class="p">;</span> <span class="p">});</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now before continuing let’s quickly answer why the obvious approaches won’t work.</p>

<ul>
  <li>
    <p>Why is this post called <em>runtime</em> duck typing, why not just use <code class="language-plaintext highlighter-rouge">#if</code>s?</p>

    <p>One of the core tenents of the sdk’s design is that all unreal properties are looked up at
runtime, using Unreal’s object introspection facilities<sup>1</sup>. This is in contrast to the
“traditional” method of using an sdk generator to create a single static set of header files. This
means the sdk accommodates game updates incredibly well, to the point that quite often, without
doing any extra work, the exact same mod file works on multiple games in the series (at least
those based on the same engine).</p>

    <p>So because we have great cross-game support, because we prefer to ship a single mod manager zip
for multiple games where possible, and because the sdk is designed around looking stuff up at
runtime anyway, it’d be preferable to also look up the core object layouts at runtime.</p>

    <p><small>
<sup>1</sup> The core fields we’re talking about in this post aren’t covered by this
             introspection.
</small></p>
  </li>
  <li>
    <p>Why not just use virtual functions, and have game specific subclasses?</p>

    <p>We don’t own these types. They’re all replicating existing unreal types already compiled into the
engine. The sdk works by detouring base engine code in a few places, pulling some pointers out of
the void, then casting them to our types, and assuming they line up. We can’t add virtual
functions because the virtual function tables are already compiled into the engine, and because
it’d throw off all the field offsets.</p>
  </li>
</ul>

<h1 id="what-worked">What worked</h1>
<p>So to start with, let’s go over what aspects of the historical solutions discussed in the previous
post worked well, and should be incorporated into the new one.</p>

<h2 id="defining-types-using-classes-and-inheritance">Defining types using classes and inheritance</h2>
<p>It feels a bit stupid pointing this one out when it’s so obvious, but I have to. If I want to define
a class, it’s nice defining it… as a class. I don’t want to have to fill any big tables of magic
numbers for each field.</p>

<p>As a specific example, in the previous post we went over <code class="language-plaintext highlighter-rouge">UProperty::read_field</code>, which took member
pointers instead of relying on magic numbers - it even worked if you inherited through multiple
types.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UObjectProperty</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UProperty</span> <span class="p">{</span>
   <span class="nl">private:</span>
    <span class="n">UClass</span><span class="o">*</span> <span class="n">PropertyClass</span><span class="p">;</span>

   <span class="nl">public:</span>
    <span class="n">UClass</span><span class="o">*</span> <span class="n">get_property_class</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">read_field</span><span class="p">(</span><span class="o">&amp;</span><span class="n">UObjectProperty</span><span class="o">::</span><span class="n">PropertyClass</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">UClassProperty</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UObjectProperty</span> <span class="p">{</span>
   <span class="nl">private:</span>
    <span class="n">UClass</span><span class="o">*</span> <span class="n">MetaClass</span><span class="p">;</span>

   <span class="nl">public:</span>
    <span class="n">UClass</span><span class="o">*</span> <span class="n">get_meta_class</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">read_field</span><span class="p">(</span><span class="o">&amp;</span><span class="n">UClassProperty</span><span class="o">::</span><span class="n">MetaClass</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="returning-references-to-members">Returning references to members</h2>
<p>There were a small handful of places where the sdk needed to set a field whose offset changed
dynamically. Turning these into functions returning a reference lead to really nice semantics for
calling code - it’s essentially the closest C⁠+⁠+ can get to a property.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="k">this</span><span class="o">-&gt;</span><span class="n">func</span><span class="o">-&gt;</span><span class="n">FunctionFlags</span><span class="p">()</span> <span class="o">|=</span> <span class="n">UFunction</span><span class="o">::</span><span class="n">FUNC_NATIVE</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>While it’s a lesser consideration, this also makes porting code quite easy, just add a pair of
brackets. Clang in fact detects this exact error case.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre> /workspaces/unrealsdk/src/unrealsdk/game/bl3/console.cpp:166:19: error: reference to non-static member function must be called; did you mean to call it with no arguments?
  166 |         viewport-&gt;Class-&gt;find_prop_and_validate&lt;UObjectProperty&gt;(L"ViewportConsole"_fn);
      |         ~~~~~~~~~~^~~~~
      |                        ()
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This project was always going to require a major version bump, a breaking change is ok, it’s just
nice it’s one relatively easy to fix.</p>

<h2 id="wrapper-types">Wrapper types</h2>
<p>For the cases where the internal unreal types where completely different, we used wrapper types, so
that all calling code used a common interface.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">GObjects</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="k">struct</span> <span class="nc">Iterator</span><span class="p">;</span>

    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="n">UObject</span><span class="o">*</span> <span class="n">obj_at</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">idx</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
    
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="n">Iterator</span> <span class="n">begin</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="k">static</span> <span class="n">Iterator</span> <span class="n">end</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="n">UObject</span><span class="o">*</span> <span class="n">get_weak_object</span><span class="p">(</span><span class="k">const</span> <span class="n">FWeakObjectPtr</span><span class="o">*</span> <span class="n">ptr</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
    <span class="kt">void</span> <span class="n">set_weak_object</span><span class="p">(</span><span class="n">FWeakObjectPtr</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span> <span class="k">const</span> <span class="n">UObject</span><span class="o">*</span> <span class="n">obj</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Since these are our own types, which we’re never going to hand back to the engine, we can actually
get away with using virtual functions here, if required.</p>

<h1 id="putting-it-together">Putting it together</h1>
<p>Basically all the historical ways of dealing with these different object layouts boiled down to
working out what offset the field is supposed to be at, and then manually doing the pointer maths.
If we want to extend this system to cover every field, the obvious first idea is to just store every
field’s offset.</p>

<p>After a bunch of experimenting, I came up with the following.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
</pre></td><td class="rouge-code"><pre><span class="k">using</span> <span class="n">offset_type</span> <span class="o">=</span> <span class="kt">uint16_t</span><span class="p">;</span>

<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">From</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">To</span><span class="p">&gt;</span>
<span class="k">using</span> <span class="n">copy_cv</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">conditional_t</span><span class="o">&lt;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">is_const_v</span><span class="o">&lt;</span><span class="n">From</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">std</span><span class="o">::</span><span class="n">add_const_t</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">conditional_t</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">is_volatile_v</span><span class="o">&lt;</span><span class="n">From</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">add_volatile_t</span><span class="o">&lt;</span><span class="n">To</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">To</span><span class="o">&gt;&gt;</span><span class="p">,</span>
    <span class="cm">/* align      */</span> <span class="n">std</span><span class="o">::</span><span class="n">conditional_t</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">is_volatile_v</span><span class="o">&lt;</span><span class="n">From</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">add_volatile_t</span><span class="o">&lt;</span><span class="n">To</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">To</span><span class="o">&gt;&gt;</span><span class="p">;</span>

<span class="k">class</span> <span class="nc">UObject</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="k">struct</span> <span class="nc">Offsets</span> <span class="p">{</span>
        <span class="n">offset_type</span> <span class="n">Class</span><span class="p">;</span>
        <span class="n">offset_type</span> <span class="n">Name</span><span class="p">;</span>
        
        <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
        <span class="k">static</span> <span class="k">constexpr</span> <span class="n">Offsets</span> <span class="n">from</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">static_assert</span><span class="p">(</span><span class="n">offsetof</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="n">Class</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">numeric_limits</span><span class="o">&lt;</span><span class="n">offset_type</span><span class="o">&gt;::</span><span class="n">max</span><span class="p">());</span>
            <span class="k">static_assert</span><span class="p">(</span><span class="n">offsetof</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="n">Name</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">numeric_limits</span><span class="o">&lt;</span><span class="n">offset_type</span><span class="o">&gt;::</span><span class="n">max</span><span class="p">());</span>
            
            <span class="k">return</span> <span class="p">{</span>
                <span class="n">offsetof</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="n">Class</span><span class="p">),</span>
                <span class="n">offsetof</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="n">Name</span><span class="p">),</span>
            <span class="p">};</span>
        <span class="p">}</span>

        <span class="k">static</span> <span class="n">offset_type</span> <span class="n">get</span><span class="p">(</span><span class="n">offset_type</span> <span class="n">Offsets</span><span class="o">::*</span> <span class="n">field</span><span class="p">);</span>
    <span class="p">};</span>

    <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="n">copy_cv</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">UClass</span><span class="o">*&gt;&amp;</span> <span class="n">Class</span><span class="p">(</span><span class="k">this</span> <span class="n">T</span><span class="o">&amp;</span> <span class="n">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">copy_cv</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">UClass</span><span class="o">*&gt;*&gt;</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uintptr_t</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">self</span><span class="p">)</span>
                                                       <span class="o">+</span> <span class="n">Offsets</span><span class="o">::</span><span class="n">get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Offsets</span><span class="o">::</span><span class="n">Class</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="n">copy_cv</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">FName</span><span class="o">&gt;&amp;</span> <span class="n">Name</span><span class="p">(</span><span class="k">this</span> <span class="n">T</span><span class="o">&amp;</span> <span class="n">self</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">copy_cv</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">FName</span><span class="o">&gt;*&gt;</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uintptr_t</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">self</span><span class="p">)</span>
                                                     <span class="o">+</span> <span class="n">Offsets</span><span class="o">::</span><span class="n">get</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Offsets</span><span class="o">::</span><span class="n">Name</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="nc">OffsetList</span> <span class="p">{</span>
    <span class="n">UObject</span><span class="o">::</span><span class="n">Offsets</span> <span class="n">UObject</span><span class="p">;</span>

    <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
    <span class="k">static</span> <span class="k">constexpr</span> <span class="n">OffsetList</span> <span class="n">from</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="p">{</span>
            <span class="n">UObject</span><span class="o">::</span><span class="n">Offsets</span><span class="o">::</span><span class="n">from</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">T</span><span class="o">::</span><span class="n">UObject</span><span class="o">&gt;</span><span class="p">(),</span>
        <span class="p">};</span>
    <span class="p">}</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>In each type, we add an <code class="language-plaintext highlighter-rouge">Offsets</code> struct, holding each field we’re interested in. We also add a
helper function to generate the struct based off of a type. We then also add a reference getter for
each field, which gets the relevant offset and adds it to its pointer. These getters use
C⁠+⁠+⁠23’s <em>deducing this</em> to automatically return a const reference on const
pointers. We then create an <code class="language-plaintext highlighter-rouge">OffsetList</code> type holding all sets of offsets, along with a templated
helper again.</p>

<p>Now in practice, to avoid copy paste errors, this is actually implemented using a number of X
macros - reflection isn’t quite here yet, it would probably be cleaner. The actual code in each
type’s header is a lot simpler.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="cp">#define UNREALSDK_UOBJECT_FIELDS(X)   \
    X(object_flags_type, ObjectFlags) \
    X(int32_t, InternalIndex)         \
    X(UClass*, Class)                 \
    X(FName, Name)                    \
    X(UObject*, Outer)
</span>
    <span class="n">UNREALSDK_DEFINE_FIELDS_HEADER</span><span class="p">(</span><span class="n">UObject</span><span class="p">,</span> <span class="n">UNREALSDK_UOBJECT_FIELDS</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>So the next question is how do we actually swap the offsets out? The sdk already used an
<code class="language-plaintext highlighter-rouge">AbstractHook</code> type to swap handling of the engine functions it hooked. We can simply add on
returning the current offset list to its responsibilities.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="nc">AbstractHook</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="k">virtual</span> <span class="k">const</span> <span class="n">OffsetList</span><span class="o">&amp;</span> <span class="n">get_offsets</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Then each game can implement this by defining all its own types, and calling our templated helpers.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="k">namespace</span> <span class="p">{</span>

<span class="k">struct</span> <span class="nc">OffsetClasses</span> <span class="p">{</span>
    <span class="k">using</span> <span class="n">UObject</span> <span class="o">=</span> <span class="n">bl2</span><span class="o">::</span><span class="n">UObject</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">const</span> <span class="k">auto</span> <span class="n">OFFSETS</span> <span class="o">=</span> <span class="n">OffsetList</span><span class="o">::</span><span class="n">from</span><span class="o">&lt;</span><span class="n">OffsetClasses</span><span class="o">&gt;</span><span class="p">();</span>

<span class="p">}</span>  <span class="c1">// namespace</span>

<span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="k">const</span> <span class="n">OffsetList</span><span class="o">&amp;</span> <span class="n">BL2Hook</span><span class="o">::</span><span class="n">get_offsets</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">OFFSETS</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And finally, we can implement <code class="language-plaintext highlighter-rouge">Offsets::get</code> with the following. This is it’s own function,
implemented in the source file, mostly just to avoid recursive include issues.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="n">offset_type</span> <span class="n">UObject</span><span class="o">::</span><span class="n">Offsets</span><span class="o">::</span><span class="n">get</span><span class="p">(</span><span class="n">offset_type</span> <span class="n">UObject</span><span class="o">::</span><span class="n">Offsets</span><span class="o">::*</span> <span class="n">field</span><span class="p">)</span> <span class="p">{</span> 
    <span class="k">return</span> <span class="n">hook_instance</span><span class="o">-&gt;</span><span class="n">get_offsets</span><span class="p">().</span><span class="n">UObject</span><span class="p">.</span><span class="o">*</span><span class="n">field</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h1 id="handling-subclasses">Handling subclasses</h1>
<p>So one annoyance with the above approach is to do with handling subclasses. Some subclasses are
relatively trivial, and have remained identical across all games.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UField</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UObject</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="n">UField</span><span class="o">*</span> <span class="n">Next</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>However, if the parent class has changed, that means even though the child class’s definition hasn’t
changed, the offsets of its fields certainly have. A parent class changing forces you to redefine
all its children. And <code class="language-plaintext highlighter-rouge">UObject</code> is <em>the</em> parent class, all other unreal object types inherit from
it. As of writing this the sdk currently includes 39 of its subclasses. We’d prefer not to need to
redeclare every other class if they’re unchanged.</p>

<p>Instead, we can define a templated generic class, which inherits from the game-specific class.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="k">namespace</span> <span class="n">generic</span> <span class="p">{</span>

<span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">class</span> <span class="nc">UField</span> <span class="o">:</span> <span class="k">public</span> <span class="n">T</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="n">UField</span><span class="o">*</span> <span class="n">Next</span><span class="p">;</span>
<span class="p">};</span>

<span class="p">}</span>  <span class="c1">// namespace generic</span>

<span class="k">struct</span> <span class="nc">OffsetClasses</span> <span class="p">{</span>
    <span class="k">using</span> <span class="n">UObject</span> <span class="o">=</span> <span class="n">bl2</span><span class="o">::</span><span class="n">UObject</span><span class="p">;</span>
    <span class="k">using</span> <span class="n">UField</span> <span class="o">=</span> <span class="n">generic</span><span class="o">::</span><span class="n">UField</span><span class="o">&lt;</span><span class="n">bl2</span><span class="o">::</span><span class="n">UObject</span><span class="o">&gt;</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h1 id="downsides">Downsides</h1>
<p>Now, while they sounded good in theory, as I was implementing the previous techniques, I came
across a few small downsides.</p>

<p>The first, and most obvious one: changing all members to functions is a breaking change, that
requires going through the entire codebase to fix every reference. I knew this, I pointed it out
earlier, but it was still quite annoying.</p>

<p>A more “real” problem is that turning the members into functions means you don’t get to see them
while debugging anymore. If you need to see a specific field, you can manually call it’s function,
it doesn’t have side effects, but you just don’t get to see a listing of all an object’s members.
You can also manually cast to the game-specific version of the object, though that’s normally more
typing.</p>

<p>But my biggest concern was performance. Obviously, adding any sort of dynamic typing will always
have worse performance than being able to read from a static offset. And the sdk already uses slow
runtime lookups for most unreal properties - but those lookups were exactly why I was worried, I
didn’t want to make them too much worse. They work by iterating through several linked lists, and
comparing each object’s name. With these changes, at minimum each iteration involves two virtual
function calls (next pointer + name), and a bit of pointer maths - and that’s assuming perfect link
time optimization.</p>

<p>To test this, I ported <code class="language-plaintext highlighter-rouge">UObject</code>, <code class="language-plaintext highlighter-rouge">UField</code>, <code class="language-plaintext highlighter-rouge">UStruct</code>, and <code class="language-plaintext highlighter-rouge">UProperty</code> to the new system, then
upgraded <a href="https://github.com/bl-sdk/pyunrealsdk/"><code class="language-plaintext highlighter-rouge">pyunrealsdk</code></a> to support it. This is the main way
the sdk is actually used, from it’s Python bindings, so better to benchmark there. I then manually
ran the following console commands from the main menu of BL3:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="n">py</span> <span class="kn">import</span> <span class="nn">timeit</span>
<span class="n">py</span> <span class="kn">from</span> <span class="nn">mods_base</span> <span class="kn">import</span> <span class="n">get_pc</span>
<span class="n">py</span> <span class="n">pc</span> <span class="o">=</span> <span class="n">get_pc</span><span class="p">()</span>
<span class="n">py</span> <span class="k">print</span><span class="p">(</span><span class="n">timeit</span><span class="p">.</span><span class="n">timeit</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="n">pc</span><span class="p">.</span><span class="n">OakCharacter</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000000</span><span class="p">))</span>
<span class="n">py</span> <span class="k">print</span><span class="p">(</span><span class="n">timeit</span><span class="p">.</span><span class="n">timeit</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="n">pc</span><span class="p">.</span><span class="n">ExecuteUbergraph</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000000</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">pc</code> was a <code class="language-plaintext highlighter-rouge">BP_MenuPlayerController_C</code>. Finding <code class="language-plaintext highlighter-rouge">ExecuteUbergraph</code> on this class iterates though 7
classes and 948 total fields. This is pretty much the worst case I could find. <code class="language-plaintext highlighter-rouge">OakCharacter</code> on the
other hand is the best case, it’s the very first field.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left"> </th>
      <th style="text-align: right">Static typed</th>
      <th style="text-align: right">Duck typed</th>
      <th style="text-align: right">% slower</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">OakCharacter</code></td>
      <td style="text-align: right">1.507s</td>
      <td style="text-align: right">1.582s</td>
      <td style="text-align: right">5.0%</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ExecuteUbergraph</code></td>
      <td style="text-align: right">4.897s</td>
      <td style="text-align: right">5.048s</td>
      <td style="text-align: right">3.1%</td>
    </tr>
  </tbody>
</table>

<p>Seems acceptable enough, guess it’s not a problem.</p>

<h1 id="an-alternative-approach">An alternative approach</h1>
<p>I did come up with a few concepts for an alternative approach while I was working on this. Since the
existing one is good enough, I haven’t tried implementing it, there are still a number of open
questions, and I imagine it would probably be a lot more complex. But I figure it’s worth putting
the idea out there.</p>

<p>Essentially, I imagine the “core” unreal types wouldn’t have any members, and you’d instead be
forced to convert them to a “concrete”, game specific, type before you can access them. A templated
lambda could be used to automatically handle the different object layouts - the implementation would
call it with a different templated type for each set of layouts.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="n">unrealsdk</span><span class="o">::</span><span class="n">to_concrete</span><span class="p">([]</span><span class="o">&lt;</span><span class="k">typename</span> <span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">T</span><span class="o">::</span><span class="n">UObject</span><span class="o">*</span> <span class="n">obj</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">print</span><span class="p">(</span><span class="s">"Object Name: {}"</span><span class="p">,</span> <span class="n">obj</span><span class="o">-&gt;</span><span class="n">Name</span><span class="p">);</span>
<span class="p">},</span> <span class="n">obj</span><span class="p">);</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>This is making use of how templates are (compile-time) ducked typed to begin with, and then just
adding a runtime switch at the front.</p>

<p>Looking back at the downsides of the current approach, while it doesn’t fully solve any of them,
this approach would help with all. You wouldn’t need to update every single member access, but you
would still need to wrap larger code blocks. When debugging, the core types would still be unusable,
but in cases you want to see the members, it’s likely you already converted your objects to a
concrete type, and within the lambda you could see all their members. And for performance, while
we’ll still need to check which objects layouts to use once, at the start, it would only be that
once, the code within each lambda should compile similarly to before.</p>

<p>Now I like I said, there’s still a number of open questions to this I haven’t investigated.</p>
<ul>
  <li>How do you keep a mapping between the core types and the concrete ones? If I have a core
<code class="language-plaintext highlighter-rouge">UProperty*</code> I expect a <code class="language-plaintext highlighter-rouge">bl2::UProperty*</code> back.</li>
  <li>How can we convert multiple objects all at once?</li>
  <li>How does this interact with dependant types? For example, <code class="language-plaintext highlighter-rouge">UStruct</code> exposes a few iterators, is
there a way to avoid needing to convert to a concrete type in every <code class="language-plaintext highlighter-rouge">Iterator::operator++</code> call?</li>
  <li>How do you structure this code without causing recursive includes?</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Swapping between different object layouts at runtime.]]></summary></entry><entry><title type="html">Handling Unreal Engine’s changing object layouts while modding</title><link href="https://apple1417.dev/posts/2025-01-15-unreal-object-layouts" rel="alternate" type="text/html" title="Handling Unreal Engine’s changing object layouts while modding" /><published>2025-01-15T00:00:00+00:00</published><updated>2025-01-15T00:00:00+00:00</updated><id>https://apple1417.dev/posts/unreal-object-layouts</id><content type="html" xml:base="https://apple1417.dev/posts/2025-01-15-unreal-object-layouts"><![CDATA[<p>I’ve been cleaning up the Borderlands series’ modding sdk for a while now. The central component of
this is my library <a href="https://github.com/bl-sdk/unrealsdk"><code class="language-plaintext highlighter-rouge">unrealsdk</code></a>, which handles all the
interaction with Unreal Engine objects. In the modding community, there are generally considered to
be 7 distinct games in the series, released across a span of 13 years, with Borderlands 4 on the way
shortly. Luckily, the logic used by the sdk hasn’t really changed, but what has changed quite a lot
is the way objects are laid out in memory. The same field might be at an offset of 0x50 in one game,
0x58 in another, and 0x20 in a third.</p>

<p>In this post I’ll go through some of the particular problems we encounter, and how they have been
handled historically. In the next post we’ll build a system that can support all layouts in the same
codebase, with the ability to swap at runtime - one dll, any game.</p>

<h1 id="background">Background</h1>
<p>So before we get started, the rest of this will make a bit more sense if you understand how the
various games differ tech wise. Lets start with how they branched off each other.</p>

<p style="max-width: 60%; margin: auto"><img src="/assets/posts/unreal-object-layouts/game-branches.svg" alt="Game branch diagram" /></p>

<p>Borderlands 1 was based off a custom version of Unreal Engine 3, which Gearbox acquired the rights
to modify. Borderlands 2 and The Pre-Sequel are further iterations of this engine, each on slightly
later versions. Then it came time for the remasters. As you’d expect, Borderlands 1 Enhanced is a
fork of BL1, and Attack on Dragon Keep Standalone, which was originally a BL2 DLC, is a fork of BL2.
Borderlands 3 ran on a brand new engine, based on Unreal 4, with Wonderlands being a further
iteration of it. And finally, while we can’t say for sure before release, there’s evidence to point
to Borderlands 4 being an iteration on the WL engine, perhaps merging in some Unreal 5 features.</p>

<p>The games are often grouped into the following categories, based on Gearbox’s codenames. Mods are
often compatible between games in their category.</p>
<ul>
  <li><strong>Willow</strong>: BL1 and BL1E</li>
  <li><strong>Willow2</strong>: BL2, TPS, and AoDK</li>
  <li><strong>Oak</strong>: BL3 and WL</li>
</ul>

<p>Another interesting technical spec to note is the architecture of each game.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Game</th>
      <th style="text-align: center">32-bit</th>
      <th style="text-align: center">64-bit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">BL1</td>
      <td style="text-align: center">X</td>
      <td style="text-align: center"> </td>
    </tr>
    <tr>
      <td style="text-align: left">BL2</td>
      <td style="text-align: center">X</td>
      <td style="text-align: center"> </td>
    </tr>
    <tr>
      <td style="text-align: left">TPS</td>
      <td style="text-align: center">X</td>
      <td style="text-align: center"> </td>
    </tr>
    <tr>
      <td style="text-align: left">BL1E</td>
      <td style="text-align: center"> </td>
      <td style="text-align: center">X</td>
    </tr>
    <tr>
      <td style="text-align: left">AoDK</td>
      <td style="text-align: center">X</td>
      <td style="text-align: center"> </td>
    </tr>
    <tr>
      <td style="text-align: left">BL3</td>
      <td style="text-align: center"> </td>
      <td style="text-align: center">X</td>
    </tr>
    <tr>
      <td style="text-align: left">WL</td>
      <td style="text-align: center"> </td>
      <td style="text-align: center">X</td>
    </tr>
  </tbody>
</table>

<p>Now of course this kind of breaks that nice catchphrase from before “one dll, any game”, we’re
forced to have separate dlls for each architecture. We’ll still endeavour to support all games of
the same architecture within the same dll though.</p>

<p>The case of BL1 vs BL1E here is worth pointing out. Since they’ve based on very similar engine
versions, they should have all the same object layouts, only differing due to pointer size.</p>

<h1 id="uproperty"><code class="language-plaintext highlighter-rouge">UProperty</code></h1>
<p>One of the most important types the sdk uses is <code class="language-plaintext highlighter-rouge">UProperty</code>. Unreal has a very in depth object
introspection system, which is powered by these properties. Each <code class="language-plaintext highlighter-rouge">UProperty</code> describes a single
field on an object, it’s type, where it’s located, how big it is, and any other type specific
parameters.</p>

<p>Here’s a (simplified) example of how one might be used, using BL2’s object layouts.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UProperty</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UField</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="kt">int32_t</span> <span class="n">ArrayDim</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">ElementSize</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">PropertyFlags</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="kt">uint8_t</span> <span class="n">UnknownData00</span><span class="p">[</span><span class="mh">0x14</span><span class="p">];</span>

   <span class="nl">public:</span>
    <span class="kt">int32_t</span> <span class="n">Offset_Internal</span><span class="p">;</span>
    <span class="n">UProperty</span><span class="o">*</span> <span class="n">PropertyLinkNext</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="kt">uint8_t</span> <span class="n">UnknownData01</span><span class="p">[</span><span class="mh">0x18</span><span class="p">];</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">UObjectProperty</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UProperty</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="n">UClass</span><span class="o">*</span> <span class="n">PropertyClass</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">UObject</span><span class="o">*</span> <span class="n">set_obj_property</span><span class="p">(</span><span class="n">UObject</span><span class="o">*</span> <span class="n">obj</span><span class="p">,</span> <span class="k">const</span> <span class="n">UObjectProperty</span><span class="o">*</span> <span class="n">prop</span><span class="p">,</span> <span class="k">const</span> <span class="n">UObject</span><span class="o">*</span> <span class="n">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">!=</span> <span class="nb">nullptr</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">value</span><span class="o">-&gt;</span><span class="n">is_instance</span><span class="p">(</span><span class="n">prop</span><span class="o">-&gt;</span><span class="n">PropertyClass</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="p">(</span><span class="s">"Object is not instance of "</span> <span class="o">+</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">)</span><span class="n">prop</span><span class="o">-&gt;</span><span class="n">PropertyClass</span><span class="o">-&gt;</span><span class="n">Name</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">auto</span> <span class="n">addr</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uintptr_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="o">+</span> <span class="n">prop</span><span class="o">-&gt;</span><span class="n">Offset_Internal</span><span class="p">;</span>
    <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">UObject</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">addr</span><span class="p">)</span> <span class="o">=</span> <span class="n">value</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>So with this being such a core type, it’s probably unsurprising it was one of the first to run into
issues. So what’s the problem? Well, in TPS, which was directly based on BL2, it looks like this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UProperty</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UField</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="kt">int32_t</span> <span class="n">ArrayDim</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">ElementSize</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">PropertyFlags</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="kt">uint8_t</span> <span class="n">UnknownData00</span><span class="p">[</span><span class="mh">0x14</span><span class="p">];</span>

   <span class="nl">public:</span>
    <span class="kt">int32_t</span> <span class="n">Offset_Internal</span><span class="p">;</span>
    <span class="n">UProperty</span><span class="o">*</span> <span class="n">PropertyLinkNext</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="kt">uint8_t</span> <span class="n">UnknownData01</span><span class="p">[</span><span class="mh">0xC</span><span class="p">];</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="p">@@ -12,5 +12,5 @@</span> class UProperty : public UField {
     UProperty* PropertyLinkNext;

    private:
<span class="gd">-    uint8_t UnknownData01[0x18];
</span><span class="gi">+    uint8_t UnknownData01[0xC];
</span> };
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If we want to read <code class="language-plaintext highlighter-rouge">UObjectProperty::PropertyClass</code>, in BL2 we need to read from offset 0x80, while
in TPS we need to read from offset 0x74. For two Willow2 games, which we’d normally consider pretty
closely related.</p>

<h2 id="solutions">Solutions</h2>
<p>So how has the sdk historically handled this? Well for <code class="language-plaintext highlighter-rouge">UObjectProperty::PropertyClass</code>
specifically, turns out the answer is actually it didn’t, the original sdk just didn’t validate
object properties. Whoops. Let’s switch to a different, but still very similar example.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UArrayProperty</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UProperty</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="n">UProperty</span><span class="o">*</span> <span class="n">Inner</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The original sdk handled this with a simple helper function.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="n">UProperty</span><span class="o">*</span> <span class="n">UArrayProperty</span><span class="o">::</span><span class="n">GetInner</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">UnrealSDK</span><span class="o">::</span><span class="n">EngineVersion</span> <span class="o">&lt;=</span> <span class="mi">8631</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="o">*</span><span class="p">((</span><span class="n">UProperty</span> <span class="o">**</span><span class="p">)(((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x74</span><span class="p">));</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">return</span> <span class="o">*</span><span class="p">((</span><span class="n">UProperty</span> <span class="o">**</span><span class="p">)(((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="k">this</span><span class="p">)</span> <span class="o">+</span> <span class="mh">0x80</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now this isn’t amazing. it uses a lot of magic numbers - at one point all instances of 8630 had to be
bumped to 8631 in fact - and they need to be copied to every property with it’s own fields. It’s
also a bit weird that the newer game has a lower number.</p>

<p>When I took over development, and started writing <code class="language-plaintext highlighter-rouge">unrealsdk</code>, I got a little fancier. We can use a
templated member pointer to have a single function do all the offset adjusting, without any magic
numbers.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UProperty</span> <span class="p">{</span>
   <span class="nl">private:</span>
    <span class="k">static</span> <span class="kt">size_t</span> <span class="n">class_size</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>  <span class="c1">// Implemented using Unreal introspection</span>

   <span class="nl">protected:</span>
    <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">PropertyType</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">FieldType</span><span class="p">&gt;</span>
    <span class="n">FieldType</span> <span class="n">read_field</span><span class="p">(</span><span class="n">FieldType</span> <span class="n">PropertyType</span><span class="o">::*</span><span class="n">field</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
        <span class="kt">ptrdiff_t</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">UProperty</span><span class="o">::</span><span class="n">class_size</span><span class="p">()</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UProperty</span><span class="p">);</span>

        <span class="k">auto</span> <span class="n">as_derived</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">PropertyType</span><span class="o">*&gt;</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
        <span class="k">auto</span> <span class="n">field_ptr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="p">(</span><span class="n">as_derived</span><span class="o">-&gt;*</span><span class="n">field</span><span class="p">);</span>

        <span class="k">auto</span> <span class="n">adjusted_ptr</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uintptr_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">field_ptr</span><span class="p">)</span> <span class="o">+</span> <span class="n">offset</span><span class="p">;</span>
        <span class="k">return</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">FieldType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">adjusted_ptr</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="k">class</span> <span class="nc">UArrayProperty</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UProperty</span> <span class="p">{</span>
   <span class="nl">private:</span>
    <span class="n">UProperty</span><span class="o">*</span> <span class="n">Inner</span><span class="p">;</span>

   <span class="nl">public:</span>
    <span class="n">UProperty</span><span class="o">*</span> <span class="n">get_inner</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">read_field</span><span class="p">(</span><span class="o">&amp;</span><span class="n">UArrayProperty</span><span class="o">::</span><span class="n">Inner</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This solution also optimizes quite well. All three major compilers optimize it down to essentially
just a function call (which may also get inlined in practice) and a single addition, completely
getting rid of the branch from the previous implementation.</p>

<p><a href="https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:3,endLineNumber:35,positionColumn:1,positionLineNumber:27,selectionStartColumn:3,selectionStartLineNumber:35,startColumn:1,startLineNumber:27),source:'%23include+%3Ccstdint%3E%0A%23include+%3Ccstddef%3E%0A%0Ausing+std::uint8_t%3B%0Ausing+std::uint32_t%3B%0Ausing+std::size_t%3B%0A%0Aclass+UProperty+%7B%0A+++private:%0A++++uint8_t+padding%5B32%5D%3B%0A%0A++++static+size_t+class_size()%3B%0A%0A+++protected:%0A++++template+%3Ctypename+PropertyType,+typename+FieldType%3E%0A++++FieldType+read_field(FieldType+PropertyType::*field)+const+%7B%0A++++++++ptrdiff_t+offset+%3D+UProperty::class_size(void)+-+sizeof(UProperty)%3B%0A%0A++++++++auto+as_derived+%3D+reinterpret_cast%3Cconst+PropertyType*%3E(this)%3B%0A++++++++auto+field_ptr+%3D+%26(as_derived-%3E*field)%3B%0A%0A++++++++auto+adjusted_ptr+%3D+reinterpret_cast%3Cuintptr_t%3E(field_ptr)+%2B+offset%3B%0A++++++++return+*reinterpret_cast%3CFieldType*%3E(adjusted_ptr)%3B%0A++++%7D%0A%7D%3B%0A%0Aclass+UArrayProperty+:+public+UProperty+%7B%0A+++private:%0A++++UProperty*+Inner%3B%0A%0A+++public:%0A++++UProperty*+get_inner(void)+const+%7B%0A++++++++return+this-%3Eread_field(%26UArrayProperty::Inner)%3B%0A++++%7D%0A%7D%3B%0A%0AUProperty*+getter(UArrayProperty*+arr)+%7B%0A++++return+arr-%3Eget_inner()%3B%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:gsnapshot,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1',verboseDemangling:'0'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:'-O3',overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+x86-64+gcc+(trunk)+(Editor+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4">Compiler Explorer</a></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="n">UProperty</span><span class="o">*</span> <span class="nf">getter</span><span class="p">(</span><span class="n">UArrayProperty</span><span class="o">*</span> <span class="n">arr</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">arr</span><span class="o">-&gt;</span><span class="n">get_inner</span><span class="p">();</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="nf">getter</span><span class="p">(</span><span class="nv">UArrayProperty</span><span class="o">*</span><span class="p">):</span>
        <span class="c1">; Stack work for function call</span>
        <span class="nf">sub</span>     <span class="nb">rsp</span><span class="p">,</span> <span class="mi">24</span>
        <span class="nf">mov</span>     <span class="kt">QWORD</span> <span class="nv">PTR</span> <span class="p">[</span><span class="nb">rsp</span><span class="o">+</span><span class="mi">8</span><span class="p">],</span> <span class="nb">rdi</span>
        <span class="nf">call</span>    <span class="nv">UProperty</span><span class="p">::</span><span class="nb">cl</span><span class="nv">ass_size</span><span class="p">()</span>
        <span class="nf">mov</span>     <span class="nb">rdi</span><span class="p">,</span> <span class="kt">QWORD</span> <span class="nv">PTR</span> <span class="p">[</span><span class="nb">rsp</span><span class="o">+</span><span class="mi">8</span><span class="p">]</span>
        
        <span class="c1">; The actual calculation</span>
        <span class="nf">mov</span>     <span class="nb">rax</span><span class="p">,</span> <span class="kt">QWORD</span> <span class="nv">PTR</span> <span class="p">[</span><span class="nb">rdi</span><span class="o">+</span><span class="nb">rax</span><span class="p">]</span>
        
        <span class="c1">; Stack work for return</span>
        <span class="nf">add</span>     <span class="nb">rsp</span><span class="p">,</span> <span class="mi">24</span>
        <span class="nf">ret</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>But now for some of the downsides.</p>

<p>Firstly, this is entirely dependent on there being no padding between types. If
<code class="language-plaintext highlighter-rouge">sizeof(UProperty) == 36</code>, assuming a 64-bit program, <code class="language-plaintext highlighter-rouge">offsetof(UArrayProperty, Inner) == 40</code>, to
keep the pointer 8 byte aligned. This will cause the compiler to insert an extra constant +4 into
the addition - you can try it out by editing the size of the <code class="language-plaintext highlighter-rouge">padding</code> struct in the Compiler
Explorer example. As long as <code class="language-plaintext highlighter-rouge">UProperty::class_size()</code> returns 36, the maths still checks out. But
if one version of the game optimized it a bit, and got it down to 32 bytes, then the padding goes
away, and the +4 screws up our formula.</p>

<p>Now in practice, this doesn’t end up being much of a problem. When reverse engineering Unreal’s
types, we don’t know if the end of a type is padding, or just a value that happens to be zero 99% of
the time. The classes always end up naturally aligned because we end up just considering the padding
as part of the class, usually in one of those unknown data arrays. And as long as the class size
always includes the padding too, it always works out.</p>

<p>Another downside is with setters. In the cases where we used this solution, we only needed getters,
so it worked well enough. Adding a setter basically requires repeating all that offset adjusting
code though. And it might get more complicated for types with non trivial copy/move assignment
operators. We’ll see another approach to handle this later.</p>

<p>Perhaps the biggest downside to the approach however is it’s very specific. It only works when the
difference in object layouts happens due to a block of unknown data at the end of a class changing
size. It would not work if we wanted to access a field on <code class="language-plaintext highlighter-rouge">UProperty</code> after the one which changed
size. And it would not work if the overall size of the class stayed the same, but some internal
layout changed.</p>

<h1 id="borderlands-3">Borderlands 3</h1>
<p>So BL3 caused a lot of problems. The original version of the sdk only supported Willow2, it was made
before BL3 ever released, so the only problem it ever ran into was <code class="language-plaintext highlighter-rouge">UProperty</code>. BL3 came in on a
completely new engine, and upgraded to 64-bit, so basically everything changed.</p>

<p>The original sdk’s codebase wasn’t the cleanest to begin with, so trying to hack in support for
these changes proved very difficult. Eventually, I gave in, and started a from scratch rewrite, with
the explicit design goal of being able to swap out layouts as required - this became <code class="language-plaintext highlighter-rouge">unrealsdk</code>.
This process took long enough that Wonderlands had actually released in the meantime - though it has
no layout differences to BL3.</p>

<p>The main way <code class="language-plaintext highlighter-rouge">unrealsdk</code> handles different games is through an <code class="language-plaintext highlighter-rouge">AbstractHook</code> class. During sdk
initialization, a selector function picks which implementation to use (based off of the exe name),
and it’s constructor does any game specific initialization logic. Afterwards, the sdk can just call
virtual functions, which get forwarded to the right implementation. Except wait a minute, this was a
post about object layouts. How do virtual functions help us swap object layouts? Well the secret is
they don’t, we cheat. The Willow2 games are all 32-bit UE3, the Oak games are all 64-bit UE4,
they’re significantly different and they can’t possibly be supported in the same dll - so surely
it’s fine to just use the preprocessor right? While it worked for this scenario, it turned out not
to be a long term solution.</p>

<p>Unreal’s actually a constantly moving target. Every minor version makes a bunch of small tweaks,
which over a large enough time span add up to some major breaking changes for us. For example,
Unreal 4.23 (afaik) made some major changes to the core <code class="language-plaintext highlighter-rouge">FName</code> struct. And despite what Epic’s
marketing team would like you to believe, there’s really not anything that special about the major
version. This means it’s not really right to treat “UE4” as a monolith, if we supported 4.10 we
might not support 4.27, and if we supported 4.27 we might still support 5.0. So if we were to use
the preprocessor, really we should have a value for every single minor version as well, which breaks
our “one dll, any game” goal, and which will quickly get out of hand.</p>

<p>So I already explained the solution we used for BL3’s layout differences, just using the
preprocessor. Let’s still go through some of the particular problems we ran into, since these will
inform the improved design we’ll come up with later.</p>

<h2 id="uobject"><code class="language-plaintext highlighter-rouge">UObject</code></h2>
<p><code class="language-plaintext highlighter-rouge">UObject</code> is the base class all unreal objects inherit from. Being such a core class, it seems they
spent some time optimizing by the engine version used in Oak. In the following examples private
fields are known, but are not used by the sdk - the public interface should be identical between the
games. I believe technically the compiler is allowed to rearrange private fields, but in practice
this turned out not to be an issue across any of the major three.</p>

<p>In Willow2, <code class="language-plaintext highlighter-rouge">UObject</code> looks like this.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UObject</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="kt">uintptr_t</span><span class="o">*</span> <span class="n">vftable</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="kt">void</span><span class="o">*</span> <span class="n">HashNext</span><span class="p">;</span>

   <span class="nl">public:</span>
    <span class="kt">uint64_t</span> <span class="n">ObjectFlags</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="kt">void</span><span class="o">*</span> <span class="n">HashOuterNext</span><span class="p">;</span>
    <span class="kt">void</span><span class="o">*</span> <span class="n">StateFrame</span><span class="p">;</span>
    <span class="n">UObject</span><span class="o">*</span> <span class="n">_Linker</span><span class="p">;</span>
    <span class="kt">void</span><span class="o">*</span> <span class="n">_LinkerIndex</span><span class="p">;</span>

   <span class="nl">public:</span>
    <span class="kt">int32_t</span> <span class="n">InternalIndex</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="kt">int32_t</span> <span class="n">NetIndex</span><span class="p">;</span>

   <span class="nl">public:</span>
    <span class="n">UObject</span><span class="o">*</span> <span class="n">Outer</span><span class="p">;</span>
    <span class="n">FName</span> <span class="n">Name</span><span class="p">;</span>
    <span class="n">UClass</span><span class="o">*</span> <span class="n">Class</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="n">UObject</span><span class="o">*</span> <span class="n">ObjectArchetype</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>In Oak, it instead looks like this:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UObject</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="kt">uintptr_t</span><span class="o">*</span> <span class="n">vftable</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">ObjectFlags</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">InternalIndex</span><span class="p">;</span>
    <span class="n">UClass</span><span class="o">*</span> <span class="n">Class</span><span class="p">;</span>
    <span class="n">FName</span> <span class="n">Name</span><span class="p">;</span>
    <span class="n">UObject</span><span class="o">*</span> <span class="n">Outer</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>To format this another way, if we assumed 8 byte pointers, so we can make a like for like
comparison, and given the fact that <code class="language-plaintext highlighter-rouge">FName</code> is 8 bytes, the fields we care about would be at the
following offsets:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Field</th>
      <th style="text-align: right">Willow2</th>
      <th style="text-align: right">Oak</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">vftable</code></td>
      <td style="text-align: right">0x0 +8</td>
      <td style="text-align: right">0x0 +8</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ObjectFlags</code></td>
      <td style="text-align: right">0x10 +8</td>
      <td style="text-align: right">0x8 +4</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">InternalIndex</code></td>
      <td style="text-align: right">0x38 +4</td>
      <td style="text-align: right">0xC +4</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Outer</code></td>
      <td style="text-align: right">0x40 +8</td>
      <td style="text-align: right">0x20 +8</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Name</code></td>
      <td style="text-align: right">0x48 +8</td>
      <td style="text-align: right">0x18 +8</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Class</code></td>
      <td style="text-align: right">0x50 +8</td>
      <td style="text-align: right">0x10 +8</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">sizeof(UObject)</code></td>
      <td style="text-align: right">0x58</td>
      <td style="text-align: right">0x28</td>
    </tr>
  </tbody>
</table>

<p>This is a fantastic example, it covers pretty much every problem we might run into. The entire class
is a different size. The offsets between fields in the middle of the object are different. Some
fields are in different orders. And one of the fields is even a completely different size (it’s a
bitfield in both versions, so if it counts as a different type is a bit debatable).</p>

<h2 id="gobjects"><code class="language-plaintext highlighter-rouge">GObjects</code></h2>
<p><code class="language-plaintext highlighter-rouge">GObjects</code> is a global array holding every active unreal object. In the sdk we use it for two main
reasons:</p>
<ul>
  <li>Getting an arbitrary object, as part of some bootstrapping process</li>
  <li>Getting every object of a certain class</li>
</ul>

<p>In Willow2, this is a simple array:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">T</span><span class="p">&gt;</span>
<span class="k">struct</span> <span class="nc">TArray</span> <span class="p">{</span>
   <span class="nl">public:</span>
    <span class="n">T</span><span class="o">*</span> <span class="n">data</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">count</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">max</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">TArray</span><span class="o">&lt;</span><span class="n">UObject</span><span class="o">*&gt;</span> <span class="n">GObjects</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>In Oak, it gets a lot more complex:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="nc">FUObjectItem</span> <span class="p">{</span>
    <span class="n">UObject</span><span class="o">*</span> <span class="n">Object</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">Flags</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">ClusterRootIndex</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">int32_t</span><span class="o">&gt;</span> <span class="n">SerialNumber</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="nc">FChunkedFixedUObjectArray</span> <span class="p">{</span>
    <span class="n">FUObjectItem</span><span class="o">**</span> <span class="n">Objects</span><span class="p">;</span>
    <span class="n">FUObjectItem</span><span class="o">*</span> <span class="n">PreAllocatedObjects</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">Max</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">Count</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">MaxChunks</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">NumChunks</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">struct</span> <span class="nc">FUObjectArray</span> <span class="p">{</span>
    <span class="kt">int32_t</span> <span class="n">ObjFirstGCIndex</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">ObjLastNonGCIndex</span><span class="p">;</span>
    <span class="kt">int32_t</span> <span class="n">MaxObjectsNotConsideredByGC</span><span class="p">;</span>
    <span class="kt">bool</span> <span class="n">OpenForDisregardForGC</span><span class="p">;</span>
    <span class="n">FChunkedFixedUObjectArray</span> <span class="n">ObjObjects</span><span class="p">;</span>

   <span class="nl">private:</span>
    <span class="kt">uint8_t</span> <span class="n">UnknownData00</span><span class="p">[</span><span class="mh">0x178</span><span class="p">];</span>

   <span class="nl">public:</span>
    <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">int32_t</span><span class="o">&gt;</span> <span class="n">MasterSerialNumber</span><span class="p">;</span>
<span class="p">};</span>

<span class="n">FUObjectArray</span> <span class="n">GObjects</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now some of these are just variables moved from other places - I suspect <code class="language-plaintext highlighter-rouge">FUObjectItem::Flags</code> is
where the 32 bits of <code class="language-plaintext highlighter-rouge">UObject::ObjectFlags</code> went for example. But the main point is this is just a
completely different data structure, it’s a two level chunked array. No changing of field offsets is
going to fix interacting with this.</p>

<p>Another interesting thing to note is the two atomics. <code class="language-plaintext highlighter-rouge">MasterSerialNumber</code> is a global counter,
incremented every time an object is created, and copied to that object’s <code class="language-plaintext highlighter-rouge">SerialNumber</code>. This is
used to implement weak object pointers - a weak pointer holds the object’s index, and it’s unique
serial number, which ensures that a different object hasn’t taken the same slot. The problem here is
there’s no such equivalent in Willow2, UE3 just doesn’t have weak object pointers.</p>

<p>So how were these handled? Well, like before, it does half rely on the preprocessor. But since the
data structures are so dramatically different, we also use a wrapper type to make external usage
consistent.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">GObjects</span> <span class="p">{</span>
   <span class="nl">public:</span>
<span class="cp">#ifdef UE4
</span>    <span class="k">using</span> <span class="n">internal_type</span> <span class="o">=</span> <span class="n">FUObjectArray</span><span class="o">*</span><span class="p">;</span>
<span class="cp">#else
</span>    <span class="k">using</span> <span class="n">internal_type</span> <span class="o">=</span> <span class="n">TArray</span><span class="o">&lt;</span><span class="n">UObject</span><span class="o">*&gt;*</span><span class="p">;</span>
<span class="cp">#endif
</span>   <span class="nl">private:</span>
    <span class="n">internal_type</span> <span class="n">internal</span><span class="p">;</span>

   <span class="nl">public:</span>
    <span class="k">struct</span> <span class="nc">Iterator</span><span class="p">;</span>

    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="n">UObject</span><span class="o">*</span> <span class="n">obj_at</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">idx</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
    
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="n">Iterator</span> <span class="n">begin</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="k">static</span> <span class="n">Iterator</span> <span class="n">end</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="n">UObject</span><span class="o">*</span> <span class="n">get_weak_object</span><span class="p">(</span><span class="k">const</span> <span class="n">FWeakObjectPtr</span><span class="o">*</span> <span class="n">ptr</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
    <span class="kt">void</span> <span class="n">set_weak_object</span><span class="p">(</span><span class="n">FWeakObjectPtr</span><span class="o">*</span> <span class="n">ptr</span><span class="p">,</span> <span class="k">const</span> <span class="n">UObject</span><span class="o">*</span> <span class="n">obj</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If these were changed to virtual functions, we could easily swap the internal type at runtime.</p>

<p>So the only question that remains is how do we deal with weak objects in UE3? Well, since they’re
not implemented, there’s no reason user code should ever call those function in a UE3 game. So we
just throw an exception.</p>

<h1 id="borderlands-1-orks-must-die-unchained">Borderlands 1, Orks Must Die Unchained</h1>
<p>During the process of porting to Oak, some other people had taken a look at some other games.
trumank attempted porting the sdk to Orks Must Die Unchained, and later Ry0511 attempted porting it
to Borderlands 1. Both ran into a few layout differences, though there’s nothing we haven’t really
seen before, so I won’t go into details.</p>

<p>But this is a problem since both of these are 32-bit UE3 games. Just like how I said before it’s
wrong to treat UE4 as a monolith, it was wrong to treat UE3 as one too. But that’s exactly what the
preprocessor stuff was doing, so it wasn’t exactly easy to integrate these new layouts. Both of
these ended up living in their own forks. Pretty much the worst case scenario when our goal was one
dll to support everything.</p>

<h1 id="ustruct"><code class="language-plaintext highlighter-rouge">UStruct</code></h1>
<p>So this one came as quite a surprise. In the original SDK, there was no special handling for
<code class="language-plaintext highlighter-rouge">UStruct</code>, we just assumed it was the same type across all of Willow2, and everything worked fine.
I came along, rewrote everything in <code class="language-plaintext highlighter-rouge">unrealsdk</code>, and created and released a new mod manger for Oak,
and everything worked fine. Then I spent a while rewriting the old Willow2 mod manager, released it,
…and it’s hard crashing TPS on launch.</p>

<p>After spending a while debugging, I tracked it down to the following:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="gd">--- "a/.\\bl2.txt"
</span><span class="gi">+++ "b/.\\tps.txt"
</span><span class="p">@@ -2,20 +2,20 @@</span>
 class UStruct : public UField {
    private:
     uint8_t UnknownData00[0x8];
 
    public:
     UStruct* SuperField;
     UField* Children;
 
    private:
     uint16_t PropertySize;
     uint8_t UnknownData01[0x1A];
 
    public:
     UProperty* PropertyLink;
 
    private:
<span class="gd">-    uint8_t UnknownData02[0x10];
</span><span class="gi">+    uint8_t UnknownData02[0x4];
</span> 
     TArray&lt;UObject*&gt; ScriptObjectReferences;
 };
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The crash specifically was coming from trying to dereference <code class="language-plaintext highlighter-rouge">UClass::ClassDefaultObject</code>, where
<code class="language-plaintext highlighter-rouge">UClass</code> inherits from <code class="language-plaintext highlighter-rouge">UStruct</code>. Due to design mistakes I won’t go into, in the original sdk you
essentially couldn’t access this field, so no one had ever run into this before. What was more
shocking was <code class="language-plaintext highlighter-rouge">UFunction:FunctionFlags</code> - this field actually had a bit get set and cleared, in what
turns out was the complete wrong location, and everything still kept working.</p>

<p>Now luckily for us, just like <code class="language-plaintext highlighter-rouge">UProperty</code>, this is at the end of the object. But there’s an extra
set of requirements here - we need a setter too. With a little bit of fiddling, I came up with this:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">UStruct</span> <span class="p">{</span>
   <span class="nl">private:</span>
    <span class="k">static</span> <span class="kt">size_t</span> <span class="n">class_size</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>  <span class="c1">// Implemented using Unreal introspection</span>

   <span class="nl">protected:</span>
    <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">SubType</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">FieldType</span><span class="p">&gt;</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="k">const</span> <span class="n">FieldType</span><span class="o">&amp;</span> <span class="n">get_field</span><span class="p">(</span><span class="n">FieldType</span> <span class="n">SubType</span><span class="o">::*</span><span class="n">field</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
        <span class="kt">ptrdiff_t</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">UStruct</span><span class="o">::</span><span class="n">class_size</span><span class="p">()</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UStruct</span><span class="p">);</span>

        <span class="k">auto</span> <span class="n">as_derived</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">SubType</span><span class="o">*&gt;</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
        <span class="k">auto</span> <span class="n">field_ptr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="p">(</span><span class="n">as_derived</span><span class="o">-&gt;*</span><span class="n">field</span><span class="p">);</span>

        <span class="k">auto</span> <span class="n">adjusted_ptr</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uintptr_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">field_ptr</span><span class="p">)</span> <span class="o">+</span> <span class="n">offset</span><span class="p">;</span>
        <span class="k">return</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">FieldType</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">adjusted_ptr</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">template</span> <span class="o">&lt;</span><span class="k">typename</span> <span class="nc">SubType</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">FieldType</span><span class="p">&gt;</span>
    <span class="n">FieldType</span><span class="o">&amp;</span> <span class="n">get_field</span><span class="p">(</span><span class="n">FieldType</span> <span class="n">SubType</span><span class="o">::*</span><span class="n">field</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">const_cast</span><span class="o">&lt;</span><span class="n">FieldType</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="k">const_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">UStruct</span><span class="o">*&gt;</span><span class="p">(</span><span class="k">this</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">get_field</span><span class="p">(</span><span class="n">field</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">};</span>


<span class="k">class</span> <span class="nc">UFunction</span> <span class="o">:</span> <span class="k">public</span> <span class="n">UStruct</span> <span class="p">{</span>
   <span class="nl">private:</span>
    <span class="kt">uint32_t</span> <span class="n">FunctionFlags_internal</span><span class="p">;</span>

   <span class="nl">public:</span>
    <span class="k">decltype</span><span class="p">(</span><span class="n">FunctionFlags_internal</span><span class="p">)</span><span class="o">&amp;</span> <span class="n">FunctionFlags</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">get_field</span><span class="p">(</span><span class="o">&amp;</span><span class="n">UFunction</span><span class="o">::</span><span class="n">FunctionFlags_internal</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="k">const</span> <span class="k">decltype</span><span class="p">(</span><span class="n">FunctionFlags_internal</span><span class="p">)</span><span class="o">&amp;</span> <span class="n">FunctionFlags</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">get_field</span><span class="p">(</span><span class="o">&amp;</span><span class="n">UFunction</span><span class="o">::</span><span class="n">FunctionFlags_internal</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This is all the same logic as before, except this time we return a reference to the field, meaning
we can edit it. This makes for really nice readable code - and we can update existing code just by
adding a bracket pair.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="k">this</span><span class="o">-&gt;</span><span class="n">func</span><span class="o">-&gt;</span><span class="n">FunctionFlags</span><span class="p">()</span> <span class="o">|=</span> <span class="n">UFunction</span><span class="o">::</span><span class="n">FUNC_NATIVE</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>An extra issue with this approach, on top of all those discussed for <code class="language-plaintext highlighter-rouge">UProperty</code>, is all the
repeated code to properly deal with the object’s constness. If we have a non-const object, we want
to get a non-const reference back, so that we can edit it. If we have a const object, we obviously
can’t do that, but we still want to get a const reference which we can read from.</p>

<p>So I hurridly swapped out all the fields on <code class="language-plaintext highlighter-rouge">UStruct</code> subclasses, following this pattern, and got a
new release fixing TPS. Then I got a report that the mod menu just plain didn’t work. And they were
right. Whoops. I’d taken not crashing == works and didn’t do a basic sanity check. So what was it
this time?</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="gd">--- a/bl2.txt
</span><span class="gi">+++ b/tps.txt
</span><span class="p">@@ -1,6 +1,6 @@</span>
 class UClass : public UStruct { 
     uint8_t UnknownData00[0xCC];
     UObject* ClassDefaultObject;
<span class="gd">-    uint8_t UnknownData01[0x48];
</span><span class="gi">+    uint8_t UnknownData01[0x14];
</span>     TArray&lt;FImplementedInterface&gt; Interfaces;
 };
</pre></td></tr></tbody></table></code></pre></div></div>

<p>I mentioned earlier that the old sdk never actually validated object properties. It certainly also
didn’t validate interface properties, nothing ever checked this. Since the <code class="language-plaintext highlighter-rouge">Interfaces</code> field didn’t
line up in TPS, when the mod menu tried setting an interface property, the new sdk assumed the
object didn’t actually implement it, and threw an exception.</p>

<p>Now since this difference is in the middle of the object, we can’t use the old strategy. How did I
get this working? Well, in the hurry to get a working release out, this went full circle, I used
some magic numbers.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="k">namespace</span> <span class="p">{</span>

<span class="k">const</span> <span class="k">constexpr</span> <span class="k">auto</span> <span class="n">UCLASS_SIZE_TPS</span> <span class="o">=</span> <span class="mh">0x18C</span><span class="p">;</span>
<span class="k">const</span> <span class="k">constexpr</span> <span class="k">auto</span> <span class="n">UCLASS_INTERFACES_OFFSET_TPS</span> <span class="o">=</span> <span class="mh">0x160</span><span class="p">;</span>

<span class="p">}</span>  <span class="c1">// namespace</span>

<span class="p">[[</span><span class="n">nodiscard</span><span class="p">]]</span> <span class="k">const</span> <span class="k">decltype</span><span class="p">(</span><span class="n">UClass</span><span class="o">::</span><span class="n">Interfaces_internal</span><span class="p">)</span><span class="o">&amp;</span> <span class="n">UClass</span><span class="o">::</span><span class="n">Interfaces</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
    <span class="k">static</span> <span class="k">const</span> <span class="k">auto</span> <span class="n">use_tps_offset</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">Class</span><span class="o">-&gt;</span><span class="n">get_struct_size</span><span class="p">()</span> <span class="o">==</span> <span class="n">UCLASS_SIZE_TPS</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">use_tps_offset</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="o">*</span><span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="k">decltype</span><span class="p">(</span><span class="n">UClass</span><span class="o">::</span><span class="n">Interfaces_internal</span><span class="p">)</span><span class="o">*&gt;</span><span class="p">(</span>
            <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">uintptr_t</span><span class="o">&gt;</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="o">+</span> <span class="n">UCLASS_INTERFACES_OFFSET_TPS</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">get_field</span><span class="p">(</span><span class="o">&amp;</span><span class="n">UClass</span><span class="o">::</span><span class="n">Interfaces_internal</span><span class="p">);</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[An overview of how the Borderlands modding sdk has addressed various differences in the object layouts.]]></summary></entry><entry><title type="html">Launching multiple game instances under proton</title><link href="https://apple1417.dev/posts/2025-01-01-proton-multiple-game-instances" rel="alternate" type="text/html" title="Launching multiple game instances under proton" /><published>2025-01-01T00:00:00+00:00</published><updated>2025-01-01T00:00:00+00:00</updated><id>https://apple1417.dev/posts/proton-multiple-game-instances</id><content type="html" xml:base="https://apple1417.dev/posts/2025-01-01-proton-multiple-game-instances"><![CDATA[<p>Occasionally I need to launch the same game twice under proton. It’s a rare enough occurrence that I
keep forgetting how, so figured it’s time to document.</p>

<ol>
  <li>
    <p>Look up the game’s steam app id, and the location of it’s executable.</p>
  </li>
  <li>
    <p>Work out which Steam Linux Runtime you’re using for the game, and where it’s installed. There’s
probably a better way of doing this.</p>

    <ol>
      <li>
        <p>Temporarily add the launch args <code class="language-plaintext highlighter-rouge">PROTON_LOG=1 STEAM_COMPAT_LAUNCHER_SERVICE=proton</code> for one
launch. You can remove them after.</p>
      </li>
      <li>
        <p>Open the newly created <code class="language-plaintext highlighter-rouge">~/steam-$appid.log</code>. Near the top you should see a block like:</p>

        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre>======================
      
Starting program with command-launcher service.
      
To run commands in the per-app container, use a command like:
      
/mnt/g/SteamLibrary/steamapps/common/SteamLinuxRuntime_sniper/pressure-vessel/bin/steam-runtime-launch-client \
    --bus-name=:1.113 \
    --directory='' \
    -- \
    bash
</pre></td></tr></tbody></table></code></pre></div>        </div>
      </li>
    </ol>
  </li>
  <li>
    <p>[Optional??] Create a <code class="language-plaintext highlighter-rouge">steam_appid.txt</code> in the same folder as the game’s executable, with the
app id as the only contents. I’m not sure if this is always required, but I’ve always had it for all games I’ve tried.</p>

    <p>Typically, if you launch a game executable directly, it will call into
<a href="https://partner.steamgames.com/doc/sdk/api#SteamAPI_RestartAppIfNecessary"><code class="language-plaintext highlighter-rouge">SteamAPI_RestartAppIfNecessary</code></a>.
This will restart the game through Steam, and quit the executable you launched. But then Steam
will usually see the game’s already running, and just do nothing. While the <code class="language-plaintext highlighter-rouge">steam_appid.txt</code> is
intended for development, helpfully for us it prevents the restart through steam, so usually
unlocks running multiple instances again.</p>
  </li>
  <li>
    <p>Launch your first copy of the game normally. Once it’s running, run the command:</p>

    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nv">$steam_runtime_launch_client</span> <span class="nt">--bus-name</span><span class="o">=</span>com.steampowered.App<span class="nv">$appid</span> <span class="nt">--directory</span><span class="o">=</span><span class="s1">''</span> <span class="nt">--</span> wine <span class="nv">$executable</span> &amp;&gt;/dev/null &amp;
</pre></td></tr></tbody></table></code></pre></div>    </div>
    <p>Where <code class="language-plaintext highlighter-rouge">$executable</code> is relative to the game’s root directory.</p>
  </li>
</ol>]]></content><author><name></name></author><summary type="html"><![CDATA[Occasionally I need to launch the same game twice under proton. It’s a rare enough occurrence that I keep forgetting how, so figured it’s time to document.]]></summary></entry><entry><title type="html">Python Import Hooks and Module Aliasing</title><link href="https://apple1417.dev/posts/2024-06-08-python-import-hook-aliasing" rel="alternate" type="text/html" title="Python Import Hooks and Module Aliasing" /><published>2024-06-08T00:00:00+00:00</published><updated>2024-06-08T00:00:00+00:00</updated><id>https://apple1417.dev/posts/python-import-hook-aliasing</id><content type="html" xml:base="https://apple1417.dev/posts/2024-06-08-python-import-hook-aliasing"><![CDATA[<script>
    window.addEventListener("load", _ => {
        document.querySelectorAll(".no-lineno[start-line]").forEach(x => {
            const s = +x.getAttribute("start-line");
            const l = x.querySelector("pre.lineno");
            const n = (l.innerText.match(/\n/g)||[]).length;
            l.innerText = [...Array(n).keys()].map(i => i + s).join("\n");
            x.classList.remove("no-lineno");
        });
    });
</script>

<p><strong>Update 2025-12-14:</strong>
I’ve written a <a href="/posts/2025-12-14-python-import-hooks-in-practice">new post</a> on
import hooks, which I think is a better introduction to them. This post still covers some extra
content more specific to creating module aliases however.</p>

<hr />

<p>It turns out, most of the Python import system is written in Python itself, and is quite
customizable. There generally isn’t much reason to touch these however, so there aren’t many
examples of how to use out there. In this post I’ll go over how I tried using import hooks to allow
importing modules under a legacy name.</p>

<p>This post is written targeting Python 3.12.3, though I wouldn’t expect things to change too much in
future versions.</p>

<h1 id="background">Background</h1>
<p>I’ve been working on the <a href="https://github.com/bl-sdk/willow2-mod-manager/">Borderlands 2/TPS Python SDK</a>,
which allows creating mods via python scripts. There are a number of issues with the original
version, which have necessitated breaking changes to make improvements. But because there are so
many existing mods, we need a compatibility layer to try keep them running until they’re upgraded.
The particular thing we’ll be talking about today is to do with how the sdk modules are laid out.</p>

<p>The original sdk was initialized by importing the <code class="language-plaintext highlighter-rouge">Mods</code> module. <code class="language-plaintext highlighter-rouge">Mods/__init__.py</code> was then written
to scan for all subfolders, and try auto import them. This meant that very single mod ends up
packaged under the <code class="language-plaintext highlighter-rouge">Mods</code> module, e.g. <code class="language-plaintext highlighter-rouge">Mods.ModMenu</code>.</p>

<p>The newer sdk instead swaps out the <code class="language-plaintext highlighter-rouge">Mods</code> module for running a specific initialization script
instead. Because of this, we don’t need the mods folder to be it’s own module, all mods are now top
level modules.</p>

<p>So this means if you have a folder <code class="language-plaintext highlighter-rouge">Mods/ModMenu/</code>, under the old sdk you’d import it via
<code class="language-plaintext highlighter-rouge">Mods.ModMenu</code>, while under the new one it’s just under <code class="language-plaintext highlighter-rouge">ModMenu</code>. We cannot trust users to handle
two mods folders, and correctly split old and new mods between them. Instead, we need the
compatibility layer to redirect all the existing <code class="language-plaintext highlighter-rouge">from Mods import ModMenu</code> imports to just
<code class="language-plaintext highlighter-rouge">import ModMenu</code>.</p>

<h1 id="basic-module-aliases">Basic module aliases</h1>
<p>So lets take a step back from adding compatibility for <em>all</em> mods. If you’ve just renamed a single
module, how can you create an alias from the deprecated name back to it. This actually turns out to
be quite simple.</p>

<p><code class="language-plaintext highlighter-rouge">sys.modules</code> is a dictionary holding all loaded modules. It maps their name to the module object -
so we can simply add a new entry with a different name. You just need to make sure to set this up
before any imports using the old name.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>

<span class="k">try</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">old_module</span>  <span class="c1"># throws
</span><span class="k">except</span> <span class="nb">ImportError</span><span class="p">:</span>
    <span class="k">pass</span>

<span class="kn">import</span> <span class="nn">new_module</span>
<span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"old_module"</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_module</span>

<span class="kn">import</span> <span class="nn">old_module</span>  <span class="c1"># ok
</span>
<span class="k">assert</span> <span class="n">old_module</span> <span class="o">==</span> <span class="n">new_module</span>  <span class="c1"># True
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>If you’ve made enough changes, you may want to create a separate compatibility module, and alias
that instead.</p>

<p>We can also use this to temporarily alter the semantics of a given module. For example, both
versions of the sdk have an <code class="language-plaintext highlighter-rouge">unrealsdk</code> module, but it’s structured somewhat differently. Given
a compatibility module which replicates the old structure, we can try the following:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Iterator</span>
<span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>

<span class="kn">import</span> <span class="nn">unrealsdk</span> <span class="k">as</span> <span class="n">new_unrealsdk</span>
<span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">old_unrealsdk</span>

<span class="o">@</span><span class="n">contextmanager</span>
<span class="k">def</span> <span class="nf">legacy_compat</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="bp">None</span><span class="p">]:</span>
    <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"unrealsdk"</span><span class="p">]</span> <span class="o">=</span> <span class="n">old_unrealsdk</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">yield</span>
    <span class="k">finally</span><span class="p">:</span>
        <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"unrealsdk"</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_unrealsdk</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">with</span> <span class="n">legacy_compat</span><span class="p">():</span>
    <span class="kn">import</span> <span class="nn">unrealsdk</span>  <span class="c1"># gives the old sdk compat module
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>Now we could certainly try do this for all the built in modules, and it would work perfectly fine.
However, in our actual use case, we need this to work for <em>all</em> mods. And mods don’t just import sdk
builtins, they import their own submodules, and they import other library mods. We need another
solution to redirect them all programmatically.</p>

<h1 id="your-first-import-hook">Your first import hook</h1>
<p>So, let’s try an import hook. How do you even get started? The relevant docs are the
<a href="https://docs.python.org/3/reference/import.html">import system</a> and
<a href="https://docs.python.org/3/library/importlib.html"><code class="language-plaintext highlighter-rouge">importlib</code></a> - but these are very information
dense, and don’t have great examples. It’s best to keep them open, you’ll probably want to refer
back to them a lot.</p>

<p>To start simple, lets just write a hook which logs when it’s called.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Sequence</span>
<span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">ModuleSpec</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="k">class</span> <span class="nc">LoggingMetaPathFinder</span><span class="p">:</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">find_spec</span><span class="p">(</span>
        <span class="n">cls</span><span class="p">,</span>
        <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">path</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">target</span><span class="p">:</span> <span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">None</span>

<span class="n">sys</span><span class="p">.</span><span class="n">meta_path</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">LoggingMetaPathFinder</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>At it’s most basic, a meta path finder has this single <code class="language-plaintext highlighter-rouge">find_spec</code> method, which attempts to find
and return a <code class="language-plaintext highlighter-rouge">ModuleSpec</code> for the given module. If it doesn’t know how to import it, it should
return <code class="language-plaintext highlighter-rouge">None</code>. All meta path finders in <code class="language-plaintext highlighter-rouge">sys.meta_path</code> are tried in sequence, stopping once the
first one returns a spec.</p>

<p>There’s also a second type of import hook, a path entry finder. I didn’t need to use these, so can’t
really give any guidance, but to my understanding, they’ve given the spec, and are responsible for
“compiling” it.</p>

<p>So we have this finder, let’s try it out.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">xml.etree.ElementTree</span> <span class="k">as</span> <span class="n">ET</span>
<span class="n">xml</span> <span class="bp">None</span> <span class="bp">None</span>
<span class="n">xml</span><span class="p">.</span><span class="n">etree</span> <span class="p">[</span><span class="s">'/usr/lib/python3.12/xml'</span><span class="p">]</span> <span class="bp">None</span>
<span class="n">xml</span><span class="p">.</span><span class="n">etree</span><span class="p">.</span><span class="n">ElementTree</span> <span class="p">[</span><span class="s">'/usr/lib/python3.12/xml/etree'</span><span class="p">]</span> <span class="bp">None</span>
<span class="n">weakref</span> <span class="bp">None</span> <span class="bp">None</span>
<span class="n">xml</span><span class="p">.</span><span class="n">etree</span><span class="p">.</span><span class="n">ElementPath</span> <span class="p">[</span><span class="s">'/usr/lib/python3.12/xml/etree'</span><span class="p">]</span> <span class="bp">None</span>
<span class="n">_elementtree</span> <span class="bp">None</span> <span class="bp">None</span>
<span class="n">copy</span> <span class="bp">None</span> <span class="bp">None</span>
<span class="n">pyexpat</span> <span class="bp">None</span> <span class="bp">None</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">_</span> <span class="o">=</span> <span class="n">importlib</span><span class="p">.</span><span class="nb">reload</span><span class="p">(</span><span class="n">ET</span><span class="p">)</span>
<span class="n">xml</span><span class="p">.</span><span class="n">etree</span><span class="p">.</span><span class="n">ElementTree</span> <span class="p">[</span><span class="s">'/usr/lib/python3.12/xml/etree'</span><span class="p">]</span> <span class="o">&lt;</span><span class="n">module</span> <span class="s">'xml.etree.ElementTree'</span> <span class="k">from</span> <span class="s">'/usr/lib/python3.12/xml/etree/ElementTree.py'</span><span class="o">&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Firstly, we can note that importing a submodule first imports it’s parents - as you could probably
have guessed. When importing a submodule, it’s also passed a list of paths to look for the submodule
in, sourced from the <code class="language-plaintext highlighter-rouge">__path__</code> of it’s parent module. If you reload a module, it’s passed as the
target - though to be honest I have no idea what you’re supposed to do with it.</p>

<h1 id="creating-the-aliases">Creating the aliases</h1>
<p>So we know how to create an import hook. This seems simple enough, since the import system is all
written in Python anyway, let’s just inherit an existing meta path finder, and rename all the mods
modules.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Sequence</span>
<span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">ModuleSpec</span><span class="p">,</span> <span class="n">PathFinder</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="k">class</span> <span class="nc">ModMetaPathFinder</span><span class="p">(</span><span class="n">PathFinder</span><span class="p">):</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">find_spec</span><span class="p">(</span>
        <span class="n">cls</span><span class="p">,</span>
        <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">path</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">target</span><span class="p">:</span> <span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">print</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">super</span><span class="p">().</span><span class="n">find_spec</span><span class="p">(</span><span class="n">fullname</span><span class="p">.</span><span class="n">removeprefix</span><span class="p">(</span><span class="s">"Mods."</span><span class="p">),</span> <span class="n">path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>

<span class="n">sys</span><span class="p">.</span><span class="n">meta_path</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">ModMetaPathFinder</span><span class="p">)</span>  <span class="c1"># This time we append so that it's a fallback
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>To allow you to follow along in a standard repl, we’ll try import <code class="language-plaintext highlighter-rouge">importlib</code> as if it were a mod.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">Mods.importlib</span>
<span class="n">Mods</span> <span class="bp">None</span> <span class="bp">None</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
  <span class="n">File</span> <span class="s">"&lt;stdin&gt;"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
<span class="nb">ModuleNotFoundError</span><span class="p">:</span> <span class="n">No</span> <span class="n">module</span> <span class="n">named</span> <span class="s">'Mods'</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Hmm, of course.</p>

<p>In order for python to even try import <code class="language-plaintext highlighter-rouge">Mods.importlib</code>, it must first successfully import <code class="language-plaintext highlighter-rouge">Mods</code>.
But this module doesn’t exist anymore. Instead, we need to create a fake <code class="language-plaintext highlighter-rouge">sys.modules</code> entry.
Luckily, you can just create a new module using <code class="language-plaintext highlighter-rouge">ModuleType</code>.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"Mods"</span><span class="p">]</span> <span class="o">=</span> <span class="n">ModuleType</span><span class="p">(</span><span class="s">"Mods"</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">Mods.importlib</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
  <span class="n">File</span> <span class="s">"&lt;stdin&gt;"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
<span class="nb">ModuleNotFoundError</span><span class="p">:</span> <span class="n">No</span> <span class="n">module</span> <span class="n">named</span> <span class="s">'Mods.importlib'</span><span class="p">;</span> <span class="s">'Mods'</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">a</span> <span class="n">package</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now this is a more interesting one. This relies on the subtle distinction between a <em>module</em> and a
<em>package</em>.</p>

<p>For an example, let’s look at the layout of <code class="language-plaintext highlighter-rouge">concurrent</code></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="o">&gt;</span> tree <span class="nt">-I</span> __pycache__ /usr/lib/python3.12/concurrent
/usr/lib/python3.12/concurrent
├── futures
│   ├── _base.py
│   ├── __init__.py
│   ├── process.py
│   └── thread.py
└── __init__.py

2 directories, 5 files
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Any folder with an <code class="language-plaintext highlighter-rouge">__init__.py</code> is a <em>package</em>, which you can import using it’s folder name. We can
<code class="language-plaintext highlighter-rouge">import concurrent</code> because <code class="language-plaintext highlighter-rouge">concurrent/__init__.py</code> exists, making <code class="language-plaintext highlighter-rouge">concurrent</code> a <em>package</em>.
Similarly, <code class="language-plaintext highlighter-rouge">import concurrent.futures</code> works because <code class="language-plaintext highlighter-rouge">futures</code> is a package, and because it’s parent
folder is a package.</p>

<p>The other loose python files within the folder are simply <em>modules</em>, which we import using their
filename. We can <code class="language-plaintext highlighter-rouge">import concurrent.futures.process</code> because <code class="language-plaintext highlighter-rouge">concurrent.futures</code> is a package, and
<code class="language-plaintext highlighter-rouge">concurrent/futures/process.py</code> is a file within it. But because <code class="language-plaintext highlighter-rouge">process.py</code> is simply a module, we
can never <code class="language-plaintext highlighter-rouge">import concurrent.futures.process.submodule</code> - which gives the same error we saw.</p>

<p>So how can we turn our fake <code class="language-plaintext highlighter-rouge">Mods</code> module into a package? I’ve kind of already mentioned this, the
only difference between a module and a package is the presence of the <code class="language-plaintext highlighter-rouge">__path__</code> attribute.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">concurrent</span><span class="p">.</span><span class="n">__path__</span>
<span class="p">[</span><span class="s">'/usr/lib/python3.12/concurrent'</span><span class="p">]</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">concurrent</span><span class="p">.</span><span class="n">futures</span><span class="p">.</span><span class="n">__path__</span>
<span class="p">[</span><span class="s">'/usr/lib/python3.12/concurrent/futures'</span><span class="p">]</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">concurrent</span><span class="p">.</span><span class="n">futures</span><span class="p">.</span><span class="n">process</span><span class="p">.</span><span class="n">__path__</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
  <span class="n">File</span> <span class="s">"&lt;stdin&gt;"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
<span class="nb">AttributeError</span><span class="p">:</span> <span class="n">module</span> <span class="s">'concurrent.futures.process'</span> <span class="n">has</span> <span class="n">no</span> <span class="n">attribute</span> <span class="s">'__path__'</span><span class="p">.</span> <span class="n">Did</span> <span class="n">you</span> <span class="n">mean</span><span class="p">:</span> <span class="s">'__name__'</span><span class="err">?</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>To quote the docs:</p>
<blockquote>
  <p><code class="language-plaintext highlighter-rouge">__path__</code></p>
  <blockquote>
    <p>If the module is a package (either regular or namespace), the module object’s <code class="language-plaintext highlighter-rouge">__path__</code>
attribute must be set. The value must be iterable, but may be empty if <code class="language-plaintext highlighter-rouge">__path__</code> has no further
significance. If <code class="language-plaintext highlighter-rouge">__path__</code> is not empty, it must produce strings when iterated over. More
details on the semantics of <code class="language-plaintext highlighter-rouge">__path__</code> are given below.</p>

    <p>Non-package modules should not have a <code class="language-plaintext highlighter-rouge">__path__</code> attribute.</p>
  </blockquote>
</blockquote>

<p>So since there’s no meaningful path we can give, sure sounds like we should make it an empty list?
Unfortuanely, it seems this line is more targeted towards if you’re implementing <code class="language-plaintext highlighter-rouge">find_spec</code>
completely from scratch. When we pass <code class="language-plaintext highlighter-rouge">PathFinder.find_spec</code> an empty list, it fails to find
anything.</p>

<p>For example, trying this on what we observed earlier:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">PathFinder</span><span class="p">.</span><span class="n">find_spec</span><span class="p">(</span><span class="s">"xml.etree.ElementTree"</span><span class="p">,</span> <span class="p">[</span><span class="s">"/usr/lib/python3.12/xml/etree"</span><span class="p">],</span> <span class="bp">None</span><span class="p">)</span>
<span class="n">ModuleSpec</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'xml.etree.ElementTree'</span><span class="p">,</span> <span class="n">loader</span><span class="o">=&lt;</span><span class="n">_frozen_importlib_external</span><span class="p">.</span><span class="n">SourceFileLoader</span> <span class="nb">object</span> <span class="n">at</span> <span class="mh">0x7f585a1f0ec0</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">origin</span><span class="o">=</span><span class="s">'/usr/lib/python3.12/xml/etree/ElementTree.py'</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">PathFinder</span><span class="p">.</span><span class="n">find_spec</span><span class="p">(</span><span class="s">"xml.etree.ElementTree"</span><span class="p">,</span> <span class="p">[],</span> <span class="bp">None</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> 
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Solving this requires some reading between the lines.</p>

<blockquote>
  <p>The <code class="language-plaintext highlighter-rouge">find_spec()</code> method of meta path finders is called with two or three arguments. The first is
the fully qualified name of the module being imported, for example <code class="language-plaintext highlighter-rouge">foo.bar.baz</code>. The second
argument is the path entries to use for the module search. For top-level modules, the second
argument is <code class="language-plaintext highlighter-rouge">None</code>, but for submodules or subpackages, the second argument is the value of the
parent package’s <code class="language-plaintext highlighter-rouge">__path__</code> attribute.</p>
</blockquote>

<p>When importing a submodule, it copies the value of the parent’s <code class="language-plaintext highlighter-rouge">__path__</code>. When importing a top
level module, it uses <code class="language-plaintext highlighter-rouge">None</code>. So what if the parent’s <code class="language-plaintext highlighter-rouge">__path__</code> <em>is</em> <code class="language-plaintext highlighter-rouge">None</code>?</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"Mods"</span><span class="p">].</span><span class="n">__path__</span> <span class="o">=</span> <span class="bp">None</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">Mods.importlib</span>
<span class="o">&gt;&gt;&gt;</span> 
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Success!</p>

<h1 id="do-we-need-an-import-hook">Do we need an import hook?</h1>
<p>So wait a minute, remember that print statement we put in <code class="language-plaintext highlighter-rouge">find_spec</code>? Why didn’t it fire?</p>

<p>Let’s try a simpler example.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="n">Mods</span> <span class="o">=</span> <span class="n">ModuleType</span><span class="p">(</span><span class="s">"Mods"</span><span class="p">)</span>
<span class="n">Mods</span><span class="p">.</span><span class="n">__path__</span> <span class="o">=</span> <span class="bp">None</span>
<span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"Mods"</span><span class="p">]</span> <span class="o">=</span> <span class="n">Mods</span>

<span class="kn">import</span> <span class="nn">Mods.importlib</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>..that also works. If you add the <code class="language-plaintext highlighter-rouge">LoggingMetaPathFinder</code> back, you’ll see it tries to import
<code class="language-plaintext highlighter-rouge">Mods.importlib</code> with a path of <code class="language-plaintext highlighter-rouge">None</code> - which means look for a top level module called <code class="language-plaintext highlighter-rouge">importlib</code>.
And one of the builtin import hooks finds it before it ever gets to ours.</p>

<p>So since we don’t actually want to change the import semantics, we just want to add fake packages to
the chain, it turns out we can just have <code class="language-plaintext highlighter-rouge">__path__</code> do all the heavy lifting for us.</p>

<p>It doesn’t just need to be setting it to <code class="language-plaintext highlighter-rouge">None</code> either, you can use it to alias stuff further down
an import path.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="n">test</span> <span class="o">=</span> <span class="n">ModuleType</span><span class="p">(</span><span class="s">"test"</span><span class="p">)</span>
<span class="n">test</span><span class="p">.</span><span class="n">__path__</span> <span class="o">=</span> <span class="p">[</span><span class="s">"/usr/lib/python3.12/xml/etree"</span><span class="p">]</span>
<span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"test"</span><span class="p">]</span> <span class="o">=</span> <span class="n">test</span>
<span class="kn">from</span> <span class="nn">test</span> <span class="kn">import</span> <span class="n">ElementTree</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Or to join multiple packages in completely different locations together.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="n">merged</span> <span class="o">=</span> <span class="n">ModuleType</span><span class="p">(</span><span class="s">"merged"</span><span class="p">)</span>
<span class="n">merged</span><span class="p">.</span><span class="n">__path__</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s">"/usr/lib/python3.12/concurrent"</span><span class="p">,</span>
    <span class="s">"/usr/lib/python3.12/importlib"</span><span class="p">,</span>
    <span class="s">"/usr/lib/python3.12/xml/etree"</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"merged"</span><span class="p">]</span> <span class="o">=</span> <span class="n">merged</span>
<span class="kn">from</span> <span class="nn">merged</span> <span class="kn">import</span> <span class="n">futures</span><span class="p">,</span> <span class="n">machinery</span><span class="p">,</span> <span class="n">ElementTree</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>These fake modules themselves are of course empty, all they do is allow importing submodules.</p>

<h1 id="well-use-an-import-hook-anyway">We’ll use an import hook anyway</h1>
<p>Now there’s there’s one more issue I ran into, which I couldn’t so easily run into in the toy
examples, and which only really makes sense with the context of <code class="language-plaintext highlighter-rouge">Mods.__path__</code> actually doing most
of the heavy lifting. If you are going to use an import hook, you’ll need to fix this up too.</p>

<p>The old sdk shipped with a <code class="language-plaintext highlighter-rouge">Mods.ModMenu</code> module, which, unsurprisingly, implemented the mod menu. 
The new mod menu works a bit different, so I was developing a compatibility module under
<code class="language-plaintext highlighter-rouge">legacy_compat.ModMenu</code>. So if we can strip out a prefix, surely we can replace one too?</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">if</span> <span class="n">fullname</span><span class="p">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">"Mods.ModMenu"</span><span class="p">):</span>
    <span class="n">new_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">__name__</span><span class="si">}</span><span class="s">.</span><span class="si">{</span><span class="n">fullname</span><span class="p">.</span><span class="n">removeprefix</span><span class="p">(</span><span class="s">"Mods."</span><span class="p">)</span><span class="si">}</span><span class="s">"</span>
    <span class="n">new_path</span> <span class="o">=</span> <span class="nb">tuple</span><span class="p">(</span><span class="n">__path__</span><span class="p">)</span>
    <span class="k">return</span> <span class="nb">super</span><span class="p">().</span><span class="n">find_spec</span><span class="p">(</span><span class="n">new_name</span><span class="p">,</span> <span class="n">new_path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Since this is a submodule, we have to replace the path we’re searching on too.</p>

<p>So, let’s try this out.</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="k">with</span> <span class="n">legacy_compat</span><span class="p">():</span>
<span class="p">...</span>   <span class="kn">import</span> <span class="nn">Mods.ModMenu</span>  <span class="c1"># ok
</span><span class="p">...</span>   <span class="kn">import</span> <span class="nn">Mods.SomeOtherMod.SubModule</span>  <span class="c1"># ok
</span><span class="p">...</span>   <span class="kn">import</span> <span class="nn">Mods.ModMenu.ModObjects</span>
<span class="p">...</span> 
<span class="nb">KeyError</span><span class="p">:</span> <span class="s">'Mods.ModMenu'</span>

<span class="n">At</span><span class="p">:</span>
  <span class="o">&lt;</span><span class="n">frozen</span> <span class="n">importlib</span><span class="p">.</span><span class="n">_bootstrap</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">1314</span><span class="p">):</span> <span class="n">_find_and_load_unlocked</span>
  <span class="o">&lt;</span><span class="n">frozen</span> <span class="n">importlib</span><span class="p">.</span><span class="n">_bootstrap</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">1360</span><span class="p">):</span> <span class="n">_find_and_load</span>
  <span class="o">&lt;</span><span class="n">string</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">6</span><span class="p">):</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Huh. Well, we have a traceback, what’s it line up with in the source?</p>

<div class="language-py no-lineno highlighter-rouge" start-line="1312"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="k">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">:</span>
    <span class="k">return</span> <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
<span class="n">parent_module</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="n">parent</span><span class="p">]</span>
<span class="k">try</span><span class="p">:</span>
    <span class="n">path</span> <span class="o">=</span> <span class="n">parent_module</span><span class="p">.</span><span class="n">__path__</span>
<span class="k">except</span> <span class="nb">AttributeError</span><span class="p">:</span>
    <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s">'</span><span class="si">{</span><span class="n">_ERR_MSG_PREFIX</span><span class="si">}{</span><span class="n">name</span><span class="si">!r}</span><span class="s">; </span><span class="si">{</span><span class="n">parent</span><span class="si">!r}</span><span class="s"> is not a package'</span>
    <span class="k">raise</span> <span class="nb">ModuleNotFoundError</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">)</span> <span class="k">from</span> <span class="bp">None</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<script>
    document.querySelectorAll(".no-lineno[start-line=\"1312\"] span.err").forEach(x => {
        x.classList.replace("err", "si");
        x.nextSibling.classList.replace("n", "si")
    });
</script>

<p>Side note: seems that’s what was throwing that error we saw earlier.</p>

<p>So our module isn’t in <code class="language-plaintext highlighter-rouge">sys.modules</code>. What exactly is?</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">.</span><span class="n">keys</span><span class="p">()</span>
<span class="p">...</span>
<span class="s">'legacy_compat.ModMenu.Options'</span><span class="p">,</span>
<span class="s">'legacy_compat.ModMenu.ModObjects'</span><span class="p">,</span>
<span class="s">'legacy_compat.ModMenu'</span><span class="p">,</span>
<span class="s">'Mods'</span><span class="p">,</span>
<span class="s">'Mods.SomeOtherMod'</span><span class="p">,</span>
<span class="s">'Mods.SomeOtherMod.ModMenu'</span><span class="p">,</span>
<span class="p">...</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>With hindsight, we know <code class="language-plaintext highlighter-rouge">Mods.SomeOtherMod</code> was imported by one of the builtin import hooks, by
checking <code class="language-plaintext highlighter-rouge">Mods.__path__</code>, because <code class="language-plaintext highlighter-rouge">SomeOtherMod</code> is a top level module. <code class="language-plaintext highlighter-rouge">ModMenu</code> isn’t a top level
module, it’s a submodule, so the builtin hook couldn’t find it, and it fell through to our hook,
which is saving it under the renamed name. Because <code class="language-plaintext highlighter-rouge">Mods.ModMenu</code> doesn’t exist in <code class="language-plaintext highlighter-rouge">sys.modules</code>,
trying to import a submodule of it immediately fails.</p>

<p>So let’s ignore the <code class="language-plaintext highlighter-rouge">Mods.__path__</code> trick, let’s put our import hook first in the list. This makes
every single submodule import fail this way. How do we fix it? How do we affect what the name saved
in <code class="language-plaintext highlighter-rouge">sys.modules</code> is?</p>

<p>By logging what the spec returns, we know our modules get a <code class="language-plaintext highlighter-rouge">SourceFileLoader</code> by default. Poking
around the importlib source code I can work out a rough code flow of <code class="language-plaintext highlighter-rouge">SourceFileLoader</code> -&gt;
<code class="language-plaintext highlighter-rouge">_LoaderBasics.load_module</code> -&gt; <code class="language-plaintext highlighter-rouge">_load_module_shim</code> -&gt; <code class="language-plaintext highlighter-rouge">_exec</code>.</p>

<p><code class="language-plaintext highlighter-rouge">/usr/lib/python3.12/importlib/_bootstrap.py</code>:</p>

<div class="language-py no-lineno highlighter-rouge" start-line="867"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre>        <span class="k">finally</span><span class="p">:</span>
            <span class="c1"># Update the order of insertion into sys.modules for module
</span>            <span class="c1"># clean-up at shutdown.
</span>            <span class="n">module</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">.</span><span class="n">pop</span><span class="p">(</span><span class="n">spec</span><span class="p">.</span><span class="n">name</span><span class="p">)</span>
            <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="n">spec</span><span class="p">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">module</span>
    <span class="k">return</span> <span class="n">module</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>So it just copies <code class="language-plaintext highlighter-rouge">spec.name</code>. Could’ve guessed. Let’s just set it back to the original before
returning.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Sequence</span>
<span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">ModuleSpec</span><span class="p">,</span> <span class="n">PathFinder</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="n">Mods</span> <span class="o">=</span> <span class="n">ModuleType</span><span class="p">(</span><span class="s">"Mods"</span><span class="p">)</span>
<span class="n">PATH_SENTINEL</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span>
<span class="n">Mods</span><span class="p">.</span><span class="n">__path__</span> <span class="o">=</span> <span class="n">PATH_SENTINEL</span>  <span class="c1"># Not using the None trick this time
</span><span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"Mods"</span><span class="p">]</span> <span class="o">=</span> <span class="n">Mods</span>

<span class="k">class</span> <span class="nc">ModMetaPathFinder</span><span class="p">(</span><span class="n">PathFinder</span><span class="p">):</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">find_spec</span><span class="p">(</span>
        <span class="n">cls</span><span class="p">,</span>
        <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">path</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">target</span><span class="p">:</span> <span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">fullname</span><span class="p">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">"Mods."</span><span class="p">):</span>
            <span class="k">return</span> <span class="bp">None</span>

        <span class="c1"># When importing from directly under `Mods`, search for top level modules
</span>        <span class="k">if</span> <span class="n">path</span> <span class="ow">is</span> <span class="n">PATH_SENTINEL</span><span class="p">:</span>
            <span class="n">path</span> <span class="o">=</span> <span class="bp">None</span>

        <span class="n">spec</span> <span class="o">=</span> <span class="nb">super</span><span class="p">().</span><span class="n">find_spec</span><span class="p">(</span><span class="n">fullname</span><span class="p">.</span><span class="n">removeprefix</span><span class="p">(</span><span class="s">"Mods."</span><span class="p">),</span> <span class="n">path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">spec</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>

        <span class="n">spec</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">fullname</span>
        <span class="k">return</span> <span class="n">spec</span>

<span class="n">sys</span><span class="p">.</span><span class="n">meta_path</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">ModMetaPathFinder</span><span class="p">)</span>  <span class="c1"># At the front this time
</span></pre></td></tr></tbody></table></code></pre></div></div>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">Mods.importlib</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
  <span class="n">File</span> <span class="s">"&lt;stdin&gt;"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
<span class="nb">ImportError</span><span class="p">:</span> <span class="n">loader</span> <span class="k">for</span> <span class="n">importlib</span> <span class="n">cannot</span> <span class="n">handle</span> <span class="n">Mods</span><span class="p">.</span><span class="n">importlib</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Not a particularly helpful error message. The exact same loader is perfectly happy to load it
normally, so this is interesting. Searching for this error message quickly brings up the following.</p>

<p><code class="language-plaintext highlighter-rouge">/usr/lib/python3.12/importlib/_bootstrap_external.py</code>:</p>

<div class="language-py no-lineno highlighter-rouge" start-line="635"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">_check_name</span><span class="p">(</span><span class="n">method</span><span class="p">):</span>
    <span class="s">"""Decorator to verify that the module being requested matches the one the
    loader can handle.

    The first argument (self) must define _name which the second argument is
    compared against. If the comparison fails then ImportError is raised.

    """</span>
    <span class="k">def</span> <span class="nf">_check_name_wrapper</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">name</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">name</span>
        <span class="k">elif</span> <span class="bp">self</span><span class="p">.</span><span class="n">name</span> <span class="o">!=</span> <span class="n">name</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nb">ImportError</span><span class="p">(</span><span class="s">'loader for %s cannot handle %s'</span> <span class="o">%</span>
                                <span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">name</span><span class="p">),</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">method</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Seems they’ve arbitrarily decided <code class="language-plaintext highlighter-rouge">self.name</code> on the loader needs to match the <code class="language-plaintext highlighter-rouge">name</code> arg to
whatever function this is decorated on, and there are a decent few. We could go change this value
too and keep diving though the code, or maybe inherit a new loader and remove the check, but there’s
a better way.</p>

<p>See so far we’ve been trying to keep our tweaks as minimal as possible, we’ve basically only
intercepted the module name passed to <code class="language-plaintext highlighter-rouge">find_spec</code>. The import system is written to give you far
greater control, you can make far greater reaching changes - and as part of this, <code class="language-plaintext highlighter-rouge">importlib</code>
contains a number of useful helper functions so that you don’t need to write them yourselves. The
one we’re interested in is <code class="language-plaintext highlighter-rouge">importlib.util.spec_from_file_location</code> - a lot of custom import systems
presumably still want to interface with files on disk. This takes a name, which we pass the original
<code class="language-plaintext highlighter-rouge">Mods.abc</code> (which we want to end up in <code class="language-plaintext highlighter-rouge">sys.modules</code>) to, and a location, which we can get from the
spec for the renamed module.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Sequence</span>
<span class="kn">from</span> <span class="nn">importlib.machinery</span> <span class="kn">import</span> <span class="n">ModuleSpec</span><span class="p">,</span> <span class="n">PathFinder</span>
<span class="kn">from</span> <span class="nn">importlib.util</span> <span class="kn">import</span> <span class="n">spec_from_file_location</span>
<span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</span>

<span class="n">Mods</span> <span class="o">=</span> <span class="n">ModuleType</span><span class="p">(</span><span class="s">"Mods"</span><span class="p">)</span>
<span class="n">PATH_SENTINEL</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span>
<span class="n">Mods</span><span class="p">.</span><span class="n">__path__</span> <span class="o">=</span> <span class="n">PATH_SENTINEL</span>
<span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">[</span><span class="s">"Mods"</span><span class="p">]</span> <span class="o">=</span> <span class="n">Mods</span>

<span class="k">class</span> <span class="nc">ModMetaPathFinder</span><span class="p">(</span><span class="n">PathFinder</span><span class="p">):</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">find_spec</span><span class="p">(</span>
        <span class="n">cls</span><span class="p">,</span>
        <span class="n">fullname</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
        <span class="n">path</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
        <span class="n">target</span><span class="p">:</span> <span class="n">ModuleType</span> <span class="o">|</span> <span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span>
    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleSpec</span> <span class="o">|</span> <span class="bp">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">fullname</span><span class="p">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">"Mods."</span><span class="p">):</span>
            <span class="k">return</span> <span class="bp">None</span>

        <span class="k">if</span> <span class="n">path</span> <span class="ow">is</span> <span class="n">PATH_SENTINEL</span><span class="p">:</span>
            <span class="n">path</span> <span class="o">=</span> <span class="bp">None</span>

        <span class="n">spec</span> <span class="o">=</span> <span class="nb">super</span><span class="p">().</span><span class="n">find_spec</span><span class="p">(</span><span class="n">fullname</span><span class="p">.</span><span class="n">removeprefix</span><span class="p">(</span><span class="s">"Mods."</span><span class="p">),</span> <span class="n">path</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">spec</span> <span class="ow">is</span> <span class="bp">None</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">spec</span><span class="p">.</span><span class="n">has_location</span> <span class="ow">or</span> <span class="n">spec</span><span class="p">.</span><span class="n">origin</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>

        <span class="k">return</span> <span class="n">spec_from_file_location</span><span class="p">(</span><span class="n">fullname</span><span class="p">,</span> <span class="n">spec</span><span class="p">.</span><span class="n">origin</span><span class="p">)</span>

<span class="n">sys</span><span class="p">.</span><span class="n">meta_path</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">ModMetaPathFinder</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">Mods.importlib</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">sys</span><span class="p">.</span><span class="n">modules</span><span class="p">.</span><span class="n">keys</span><span class="p">()</span>
<span class="p">...</span>
<span class="s">'Mods'</span><span class="p">,</span>
<span class="s">'Mods.importlib._bootstrap'</span><span class="p">,</span>
<span class="s">'Mods.importlib'</span><span class="p">,</span>
<span class="p">...</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This time, we’ve actually got it all working properly - and with a bunch of extra code specifically
to make sure we’re not taking advantage of the <code class="language-plaintext highlighter-rouge">Mods.__path__</code> trick.</p>

<p>Now in reality, I got this far before I properly worked out how to use the trick. Knowing it, this
is kind of useless, it’s a bunch of extra code and complexity just to do the same thing that you
pretty much have to do anyway. Even in this final version of code, you still need to pass <code class="language-plaintext highlighter-rouge">None</code> to
<code class="language-plaintext highlighter-rouge">super().find_spec</code> so that it searches for top level modules, might as well cut out the middle man.
But it serves as a nice jumping off point for creating a more complex import hook, one that actually
needs to be a hook - you could use any arbitrary logic you want to pick what file to import.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[How to use import hooks to create and debug aliased module names]]></summary></entry><entry><title type="html">Native Python modules using SQLite</title><link href="https://apple1417.dev/posts/2024-02-24-native-python-sqlite" rel="alternate" type="text/html" title="Native Python modules using SQLite" /><published>2024-02-24T00:00:00+00:00</published><updated>2024-02-24T00:00:00+00:00</updated><id>https://apple1417.dev/posts/native-python-sqlite</id><content type="html" xml:base="https://apple1417.dev/posts/2024-02-24-native-python-sqlite"><![CDATA[<p>I had some existing Python code, which performed some SQLite queries inside a hot loop. SQLite
itself was never a bottleneck, but this loop needed optimization, and since there was no algorithmic
way to improve it, I had to port it to a native module.</p>

<h1 id="linking-against-sqlite3dll">Linking against sqlite3.dll</h1>
<p>Before getting into this further: I was targeting a Windows executable, this may be slightly
different if compiling for Linux.</p>

<p>So first step is actually linking against SQLite. There’s an article on
<a href="https://www.sqlite.org/howtocompile.html">How To Compile SQLite</a> already on their site, but it only
really explains statically linking. Python already comes with a <code class="language-plaintext highlighter-rouge">sqlite3.dll</code>, linking against it
would prevent duplicate code, saving on filesize (it’s 1.5mb), and would mean we can guarantee both
Python and the native module are always using the same version. So how can we do that?</p>

<p>If you check the SQLite downloads page, you can find precompiled Windows binaries. These zips
contain a <code class="language-plaintext highlighter-rouge">sqlite3.def</code> and a <code class="language-plaintext highlighter-rouge">sqlite3.dll</code>. You can also grab the amalgamation zip to find the
<code class="language-plaintext highlighter-rouge">sqlite3.h</code>. In theory, this should be everything needed to link with it. However, linking actually
requires an import library file. You <em>can</em> manually generate these, a number of ways:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>lib /def:sqlite3.def /machine:x64 /out:sqlite3.lib
llvm-lib /def:sqlite3.def /machine:x64 /out:sqlite3.lib
llvm-dlltool <span class="nt">-m</span> i386:x86-64 <span class="nt">-D</span> sqlite3.dll <span class="nt">-d</span> sqlite3.def <span class="nt">-l</span> sqlite3.lib 
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Linking using the lib files should then just work. However, I was using CMake to support a number of
different toolchains at once, and could not come up with a way to get it to do this for me - it’d
just be wrong to require LLVM if you’re compiling the rest of the project with MinGW.</p>

<p>So what’s my actual solution? Unfortunately, the best I came up with was just compiling from it as a
shared library from scratch, and then completely ignoring the dll.</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
</pre></td><td class="rouge-code"><pre><span class="nb">include</span><span class="p">(</span>FetchContent<span class="p">)</span>

<span class="nf">FetchContent_Declare</span><span class="p">(</span>
    sqlite3_amalgamation
    URL      https://www.sqlite.org/2023/sqlite-amalgamation-3420000.zip
    URL_HASH MD5=eb9a6e56044bc518e6705521a1a929ed
<span class="p">)</span>
<span class="nf">FetchContent_MakeAvailable</span><span class="p">(</span>sqlite3_amalgamation<span class="p">)</span>

<span class="nb">add_library</span><span class="p">(</span>sqlite3 SHARED <span class="s2">"</span><span class="si">${</span><span class="nv">sqlite3_amalgamation_SOURCE_DIR</span><span class="si">}</span><span class="s2">/sqlite3.c"</span><span class="p">)</span>
<span class="nb">target_include_directories</span><span class="p">(</span>sqlite3 PUBLIC <span class="s2">"</span><span class="si">${</span><span class="nv">sqlite3_amalgamation_SOURCE_DIR</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="nb">if</span><span class="p">(</span>MSVC<span class="p">)</span>
    <span class="nb">target_compile_definitions</span><span class="p">(</span>sqlite3 PRIVATE <span class="s2">"SQLITE_API=__declspec(dllexport)"</span><span class="p">)</span>
<span class="nb">else</span><span class="p">()</span>
    <span class="nb">target_compile_definitions</span><span class="p">(</span>sqlite3 PRIVATE <span class="s2">"SQLITE_API=__attribute__((dllexport))"</span><span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>
<span class="nb">if</span><span class="p">(</span>CMAKE_CXX_COMPILER_ID MATCHES <span class="s2">"Clang"</span><span class="p">)</span>
    <span class="c1"># Sqlite uses a few intrinsics which clang doesn't implement, but it compiles fine ignoring them</span>
    <span class="nb">target_compile_options</span><span class="p">(</span>sqlite3 PRIVATE -Wno-ignored-pragma-intrinsic<span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>

<span class="nb">set_target_properties</span><span class="p">(</span>sqlite3 PROPERTIES
    DEBUG_POSTFIX <span class="s2">"_d"</span>
<span class="p">)</span>

...

<span class="nb">target_link_libraries</span><span class="p">(</span>my_module PRIVATE sqlite3<span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If you’re running a debug build of Python, it looks for <code class="language-plaintext highlighter-rouge">sqlite3_d.dll</code>, hence setting the postfix.
You may not want this if you’re running release Python but a debug native module.</p>

<h2 id="finding-the-right-sqlite-version">Finding the right SQLite version</h2>
<p>When compiling I made sure to link against the exact same version of SQLite my Python install
shipped with. In practice, I expect this isn’t strictly necessary, as long as you’re not doing
anything too advanced with newer functions you can probably get away with downloading the latest
amalgamation.</p>

<p>To start, find the version python’s using:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="nn">sqlite3</span>
<span class="n">con</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="p">.</span><span class="n">connect</span><span class="p">(</span><span class="s">":memory:"</span><span class="p">)</span>
<span class="n">cur</span> <span class="o">=</span> <span class="n">con</span><span class="p">.</span><span class="n">cursor</span><span class="p">()</span>
<span class="n">cur</span><span class="p">.</span><span class="n">execute</span><span class="p">(</span><span class="s">"select sqlite_version()"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">cur</span><span class="p">.</span><span class="n">fetchall</span><span class="p">())</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>[('3.40.0',)]
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Surprisingly, SQLite does not seem to have a good list of previous version downloads. They all still
exist on the server, but I couldn’t find a table of links. The best process I came up with was to
copy one of the links for the existing version, replace the version number, and look up the year of
release in the <a href="https://www.sqlite.org/changes.html">Release History</a>.</p>

<h1 id="prepared-statements-and-file-locking">Prepared Statements and File Locking</h1>
<p>So at the beginning I mentioned I run a bunch of queries in a loop. Originally, the Python code
opened a new database connection every iteration. While this wasn’t the bottleneck, when rewriting
it, this was an obvious situation to use a prepared statement and keep the connection open.</p>

<p>A separate feature in this codebase was resetting the database back to it’s default state. In
Python, this was easy to do by just deleting the file and copying a template back. However, by
keeping the native module’s database connection open the whole time, the database file gets locked.
In order to be able to delete it, we need to close the connection. This also adds an extra
complication when using prepared statements, as they are linked to the connection, and need to be
freed on close and remade after.</p>

<p>So the simple solution to this is just to design your database in such a way that you can just run
<code class="language-plaintext highlighter-rouge">DELETE FROM Table</code> a few times, and never really need to mess with files. I could’ve done this, but
felt a little uneasy about accidentally making permanent changes, or needing to deal with migrations
between versions. I instead still strived to solve this via replacing the file.</p>

<p>The solution I came up with was using a wrapper function to keep a pointer to each statement, and
only using weak pointers externally.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="rouge-code"><pre><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">sqlite3</span><span class="o">&gt;</span> <span class="n">database</span><span class="p">{};</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">sqlite3_stmt</span><span class="o">&gt;&gt;</span> <span class="n">all_statements</span><span class="p">{};</span>

<span class="kt">bool</span> <span class="n">ensure_prepared</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">weak_ptr</span><span class="o">&lt;</span><span class="n">sqlite3_stmt</span><span class="o">&gt;&amp;</span> <span class="n">statement</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">query</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">statement</span><span class="p">.</span><span class="n">expired</span><span class="p">())</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">database</span> <span class="o">==</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Re-open db</span>
    <span class="p">}</span>

    <span class="n">sqlite3_stmt</span><span class="o">*</span> <span class="n">raw_statement</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
    <span class="k">auto</span> <span class="n">res</span> <span class="o">=</span> <span class="n">sqlite3_prepare_v3</span><span class="p">(</span><span class="n">database</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="n">query</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">query</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span>
                                  <span class="n">SQLITE_PREPARE_PERSISTENT</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">raw_statement</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">res</span> <span class="o">!=</span> <span class="n">SQLITE_OK</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">all_statements</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="n">raw_statement</span><span class="p">,</span> <span class="n">sqlite3_finalize</span><span class="p">);</span>
    <span class="n">statement</span> <span class="o">=</span> <span class="n">all_statements</span><span class="p">.</span><span class="n">back</span><span class="p">();</span>
    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="k">const</span> <span class="k">constinit</span> <span class="n">std</span><span class="o">::</span><span class="n">string_view</span> <span class="n">query</span> <span class="o">=</span> <span class="s">"SELECT * FROM Table"</span><span class="p">;</span>
<span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">weak_ptr</span><span class="o">&lt;</span><span class="n">sqlite3_stmt</span><span class="o">&gt;</span> <span class="n">static_statement</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ensure_prepared</span><span class="p">(</span><span class="n">static_statement</span><span class="p">,</span> <span class="n">query</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="p">(</span><span class="s">"Failed to prepare query!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">sqlite3_stmt</span><span class="o">&gt;</span> <span class="n">statement</span><span class="p">{</span><span class="n">static_statement</span><span class="p">};</span>

<span class="n">sqlite3_reset</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">get</span><span class="p">());</span>
<span class="c1">// ...</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If the weak pointer is empty, we’ll create a new statement, and store it in a shared pointer
locally. If we destroy the shared pointer, the weak pointer will be empty again, so on next use
we’ll create a new one.</p>

<p>By giving the shared pointers custom destructors, we can close everything by simply doing:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="nf">close_db</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">all_statements</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
    <span class="n">database</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>I exposed this function to Python, and called it right before deleting the database file. Each
prepared statement gets automatically recreated on next use.</p>

<h1 id="simultaneous-connections-and-file-locking">Simultaneous Connections and File Locking</h1>
<p>During testing of the rewrite, several times I ran into a case where there was a sudden hang for
several seconds around operations which I knew accessed the database. By stopping execution and
looking through the stack trace, I found it was in SQLite’s code, blocking on trying to access the
file. I also noticed this happened both in queries run by Python, as well as those run by the native
module. Even through they’re running in the same process, using the same SQLite dll, the two
connections are clearly blocking each other.</p>

<p>I actually ran into both the reason for this and it’s solution quite quickly. In the default
rollback journal mode, SQLite does not allow readers and writers to access the database at the same
time - and one connection was writing to the database at the same time another was reading. I’m not
entirely convinced that they were truly simultaneous, but it was certainly close enough that
Windows might’ve still had the file locked. It must’ve run into some aggressive retry backoff to
cause a noticeable hang.</p>

<p>So what’s the solution? Simply enable <a href="https://www.sqlite.org/wal.html">Write-Ahead Logging</a>. This
allows for multiple readers to run at the same time as a writer. Multiple writers are still a
problem, but this is never something relevant in my situation (there are only two connections, and
the native module in fact opens the database in read only mode). Enabling it immediately fixed all
the hangs.</p>

<h2 id="wal-checkpointing">WAL Checkpointing</h2>
<p>When using WAL, writes are written to a separate journal at first, and eventually get combined back
into the main database. Another problem with my setup was
<a href="https://www.sqlite.org/wal.html#avoiding_excessively_large_wal_files">Checkpoint starvation</a>.
Checkpoints cannot run while a reader is open, and I always have a connection open in the native
module so that I can use its prepared statements, meaning the journal grew endlessly.</p>

<p>I tried using destructors or atexit handlers to force a checkpoint, but didn’t have much luck.
Instead, I found it simplest to just call <code class="language-plaintext highlighter-rouge">close_db</code> from Python just before running a separate,
semi-frequent query. This wasn’t run often enough to impact my hot loop, but it was still frequent
enough to prevent the journal growing too badly. Once Python finished and closed it’s connection,
all connections were closed, so SQLite would automatically run a checkpoint.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Porting Python code which used SQLite into a native module.]]></summary></entry><entry><title type="html">Linking against Python when Cross Compiling</title><link href="https://apple1417.dev/posts/2023-07-03-python-cross-compiling" rel="alternate" type="text/html" title="Linking against Python when Cross Compiling" /><published>2023-07-03T00:00:00+00:00</published><updated>2023-07-03T00:00:00+00:00</updated><id>https://apple1417.dev/posts/python-cross-compiling</id><content type="html" xml:base="https://apple1417.dev/posts/2023-07-03-python-cross-compiling"><![CDATA[<p><strong>Update 2025-12-14:</strong>
At some point around June 2025, presumably related to <a href="https://peps.python.org/pep-0773/">PEP 773</a>,
python started hosting <code class="language-plaintext highlighter-rouge">https://www.python.org/ftp/python/&lt;version&gt;/python-&lt;version&gt;-&lt;arch&gt;.zip</code>
files, back-added to all versions since 3.11. These hold a full extracted windows install, which
can replace some of the MSI extraction steps detailed in this post. The “legacy” installers I talk
about are deprecated, and are set to stop being released at the start of 2027.</p>

<p><strong>Update 2026-03-2:</strong>
I wrote a <a href="/posts/2026-03-22-python-cross-compiling">new post</a> on how to use the new
release format - it’s a lot nicer than this.</p>

<hr />

<p>So I have a Windows executable (or rather a dll), which uses
<a href="https://github.com/pybind/pybind11">pybind11</a> to embed a python interpreter. How can I compile this
from a Linux host? Without just compiling CPython from source, so we don’t need to worry about
everything it depends on.</p>

<p>To start with, I previously wrote about how I got a project cross compiling, and how I managed to
debug it under proton, <a href="/posts/2023-05-18-debugging-proton">which you can read here</a>.</p>

<h1 id="using-a-windows-install">Using a Windows Install</h1>
<p>The Python installer on Windows downloads everything we need to make native builds. To actually run
your application, you also need to separately download the “Windows Embeddable Package”, and put it
somewhere your executable can find it, so it can load all the separate libraries. At a minimum, you
need <code class="language-plaintext highlighter-rouge">python3.dll</code>, <code class="language-plaintext highlighter-rouge">python311.dll</code>, and <code class="language-plaintext highlighter-rouge">python311.zip</code>, the rest are only required for specific
imports.</p>

<p>So as a first step, given a complete Windows install, can we get it cross compiling? Yes, it’s
actually quite easy.</p>

<p>Our initial cmake file looks something like this.</p>
<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="nb">find_package</span><span class="p">(</span>Python 3.11 COMPONENTS Development<span class="p">)</span>
<span class="nb">add_subdirectory</span><span class="p">(</span>pybind11<span class="p">)</span>

<span class="nb">target_link_libraries</span><span class="p">(</span>my_app PRIVATE
    pybind11::embed
    pybind11::lto
    pybind11::windows_extras
<span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The first step is to remove <code class="language-plaintext highlighter-rouge">FindPython</code>. It really feels like it’s designed around creating a
module, it’s hard to coerce into finding a particular install (e.g. it will pick 64bit installs when
compiling for 32bit), and it will only find the host’s install, it just doesn’t work at all when
cross compiling. If we don’t call it, pybind will for us, so we need to disable that too.</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="nb">set</span><span class="p">(</span>PYBIND11_NOPYTHON True<span class="p">)</span>
<span class="nb">add_subdirectory</span><span class="p">(</span>pybind11<span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Next we point it at the files from the Windows install manually - taking care to switch between
debug/release libraries as required.</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="nb">target_include_directories</span><span class="p">(</span>my_app PRIVATE <span class="s2">"</span><span class="si">${</span><span class="nv">WIN_PYTHON_DIR</span><span class="si">}</span><span class="s2">/include"</span><span class="p">)</span>

<span class="nb">file</span><span class="p">(</span>GLOB py_libs <span class="s2">"</span><span class="si">${</span><span class="nv">WIN_PYTHON_DIR</span><span class="si">}</span><span class="s2">/libs/*.lib"</span><span class="p">)</span>
<span class="nb">foreach</span><span class="p">(</span>lib <span class="si">${</span><span class="nv">py_libs</span><span class="si">}</span><span class="p">)</span>
    <span class="nb">if</span> <span class="p">(</span><span class="s2">"</span><span class="si">${</span><span class="nv">lib</span><span class="si">}</span><span class="s2">"</span> MATCHES <span class="s2">"_d.lib$"</span><span class="p">)</span>
        <span class="nb">target_link_libraries</span><span class="p">(</span>my_app PRIVATE <span class="s2">"$&lt;$&lt;CONFIG:DEBUG&gt;:</span><span class="si">${</span><span class="nv">lib</span><span class="si">}</span><span class="s2">&gt;"</span><span class="p">)</span>
    <span class="nb">else</span><span class="p">()</span>
        <span class="nb">target_link_libraries</span><span class="p">(</span>my_app PRIVATE <span class="s2">"$&lt;$&lt;NOT:$&lt;CONFIG:DEBUG&gt;&gt;:</span><span class="si">${</span><span class="nv">lib</span><span class="si">}</span><span class="s2">&gt;"</span><span class="p">)</span>
    <span class="nb">endif</span><span class="p">()</span>
<span class="nb">endforeach</span><span class="p">()</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And just like that, we’re done already, we can compile.</p>

<h1 id="without-using-windows-or-wine">Without Using Windows (or Wine)</h1>
<p>So all we actually need is these files. How can we get them without needing to transfer them from a
Windows machine, or needing to install Python in a Wine prefix?</p>

<p>Since we know the Windows installer gets all these files, that’s where I started looking. Extracting
it leaves 6 files, a dll, the png displayed on the left side of the installer, and 4 xml config
files. Poking through these, I found a lot of references to the python ftp server. So let’s take a
look there.</p>

<p>Under <code class="language-plaintext highlighter-rouge">https://www.python.org/ftp/python/&lt;version&gt;/&lt;arch&gt;/</code>, there are a bunch of msi files. Seems
the main installer defers to these based on which features are selected. Some of these, such as
<code class="language-plaintext highlighter-rouge">path.msi</code>, only seem to contain a bunch of commands, while others contain actual files. Guess
what’s in <code class="language-plaintext highlighter-rouge">dev.msi</code> and <code class="language-plaintext highlighter-rouge">dev_d.msi</code>? Exactly what we need.</p>

<p>The next problem is extracting them properly. So far, I’d been using 7zip to view inside the MSIs,
but it doesn’t parse the file name quite right, what it lists as <code class="language-plaintext highlighter-rouge">include_abstract.h</code> should
actually be <code class="language-plaintext highlighter-rouge">include/abstract.h</code>. Luckily, there’s an alternative, <code class="language-plaintext highlighter-rouge">msiextract</code> (part of msitools),
which can do this properly for us.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nv">URL</span><span class="o">=</span>https://www.python.org/ftp/python/3.11.4/amd64
wget <span class="nv">$URL</span>/dev.msi <span class="nv">$URL</span>/dev_d.msi
msiextract <span class="nt">-C</span> py_dev dev.msi dev_d.msi
</pre></td></tr></tbody></table></code></pre></div></div>

<h1 id="debug-builds">Debug Builds</h1>
<p>There’s one thing I’ve kind of glossed over so far. I previously mentioned you need to put the
Windows Embeddable Package somewhere your application can find it. This is true - <em>as long as you’re
running a release build</em>. Debug builds expect <code class="language-plaintext highlighter-rouge">_d</code> versions. You can rename <code class="language-plaintext highlighter-rouge">python311.zip</code> to
<code class="language-plaintext highlighter-rouge">python311_d.zip</code> perfectly fine, it only contains python bytecode. But to get <code class="language-plaintext highlighter-rouge">python3_d.dll</code> and
<code class="language-plaintext highlighter-rouge">python311_d.dll</code> (and the pdbs), you’d normally copy them from your Windows install folder.</p>

<p>So how do we get these files without Windows? Same idea as the libraries, except this time we’re
looking at <code class="language-plaintext highlighter-rouge">core</code>. <code class="language-plaintext highlighter-rouge">core.msi</code> and <code class="language-plaintext highlighter-rouge">core_pdb.msi</code> contain the release files, and <code class="language-plaintext highlighter-rouge">core_d.msi</code>
contains the debug files.</p>

<p>Extracting these with 7zip gets you the following files (or their <code class="language-plaintext highlighter-rouge">_d</code> versions):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>python.dll
python.pdb
python_stable.dll
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Not quite what we expected. Turns out this is another artifact of 7zip, it seems the installer
renames <code class="language-plaintext highlighter-rouge">python_stable</code> to <code class="language-plaintext highlighter-rouge">python3</code> and <code class="language-plaintext highlighter-rouge">python</code> to <code class="language-plaintext highlighter-rouge">python311</code>. Again, extracting with
<code class="language-plaintext highlighter-rouge">msiextract</code> handles it properly.</p>

<p>You can then drop these files into your app folder to run the debug version properly.</p>

<h1 id="summary">Summary</h1>
<p>You can download and extract all files needed for python development with the following commands.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="nv">VERSION</span><span class="o">=</span>3.11.4
<span class="nv">ARCH</span><span class="o">=</span>amd64

<span class="nv">EMBED_URL</span><span class="o">=</span>https://www.python.org/ftp/python/<span class="nv">$VERSION</span>/python-<span class="nv">$VERSION</span><span class="nt">-embed-</span><span class="nv">$ARCH</span>.zip
<span class="nv">MSI_URL</span><span class="o">=</span>https://www.python.org/ftp/python/<span class="nv">$VERSION</span>/<span class="nv">$ARCH</span>
wget <span class="nv">$EMBED_URL</span> <span class="nv">$MSI_URL</span>/dev.msi <span class="nv">$MSI_URL</span>/dev_d.msi <span class="nv">$MSI_URL</span>/core_d.msi

msiextract <span class="nt">-C</span> dev dev.msi dev_d.msi
msiextract <span class="nt">-C</span> app core_d.msi
unzip python-<span class="nv">$VERSION</span><span class="nt">-embed-</span><span class="nv">$ARCH</span>.zip <span class="nt">-d</span> app

<span class="nv">zip_name</span><span class="o">=</span><span class="si">$(</span>find app <span class="nt">-type</span> f <span class="nt">-name</span> <span class="s2">"python3*.zip"</span><span class="si">)</span>
<span class="nb">cp</span> <span class="nv">$zip_name</span> <span class="k">${</span><span class="nv">zip_name</span><span class="p">%.*</span><span class="k">}</span>_d.zip
</pre></td></tr></tbody></table></code></pre></div></div>

<p>When compiling, add <code class="language-plaintext highlighter-rouge">dev/include</code> as an include dir, and <code class="language-plaintext highlighter-rouge">dev/libs/*.lib</code> as libraries, taking care
to link debug/release versions as needed.</p>

<p>To run your app, you need to copy at least <code class="language-plaintext highlighter-rouge">app/python3.dll</code>, <code class="language-plaintext highlighter-rouge">app/python311.dll</code>, and
<code class="language-plaintext highlighter-rouge">app/python311.zip</code> to the same dir as it’s executable. In debug mode, you need to copy the <code class="language-plaintext highlighter-rouge">_d</code>
versions instead (and you probably also want the pdbs). You can change where you can put these if
you mess with the
<a href="https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-adddlldirectory">dll search path</a>
and the
<a href="https://docs.python.org/3/library/sys_path_init.html">sys.path initialization</a>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[How to get all the files out of the Python for Windows installer without running it.]]></summary></entry><entry><title type="html">Debugging under Proton</title><link href="https://apple1417.dev/posts/2023-05-18-debugging-proton" rel="alternate" type="text/html" title="Debugging under Proton" /><published>2023-05-18T00:00:00+00:00</published><updated>2023-05-18T00:00:00+00:00</updated><id>https://apple1417.dev/posts/debugging-proton</id><content type="html" xml:base="https://apple1417.dev/posts/2023-05-18-debugging-proton"><![CDATA[<p><strong>Update 2024-09-20:</strong>
Since Proton 9, Valve have added some more official support/documentation on debugging.</p>

<p><a href="https://github.com/ValveSoftware/Proton/blob/proton_9.0/docs/DEBUGGING.md">https://github.com/ValveSoftware/Proton/blob/proton_9.0/docs/DEBUGGING.md</a></p>

<hr />

<p>For the past few months I’ve been working on <a href="https://github.com/bl-sdk/unrealsdk">unrealsdk</a>, a C++
library to interact with Unreal Engine objects, primarily targeting the Borderlands series. One of
my goals the whole time has been to make sure you can compile it from Linux. I was originally doing
so under WSL, but it’s compile times were 120x worse than just using a native install. The latest
versions of the Borderlands games are all Windows-only (there are some native builds, but only for
older outdated versions), so once I ran into a Linux-build-only bug, I had to work out how to
develop and debug under Proton.</p>

<p>This post will be half a guide and half an exploration of my process- in case you’re following along
but need to try something different.</p>

<h1 id="compiling-for-windows-from-linux">Compiling for Windows from Linux</h1>
<p>Compiling is the simple part. The classic way to compile for Windows from a Linux host is to use
MinGW-w64. I built unrealsdk using CMake from the beginning, from experience I knew MSBuild isn’t
great at handling large numbers of targets, it’s very easy for the compiler options to get out of
sync. CMake has the advantage of already being cross platform - I just had to write a small
toolchain file to point it at MinGW. If you’re still using MSBuild, you will need to port your build
system to something else.</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="nb">set</span><span class="p">(</span>CMAKE_SYSTEM_NAME Windows<span class="p">)</span>

<span class="nb">set</span><span class="p">(</span>MINGW_TOOLCHAIN_PREFIX x86_64-w64-mingw32<span class="p">)</span>

<span class="nb">set</span><span class="p">(</span>CMAKE_C_COMPILER <span class="si">${</span><span class="nv">MINGW_TOOLCHAIN_PREFIX</span><span class="si">}</span>-gcc<span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>CMAKE_CXX_COMPILER <span class="si">${</span><span class="nv">MINGW_TOOLCHAIN_PREFIX</span><span class="si">}</span>-g++<span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>CMAKE_RC_COMPILER <span class="si">${</span><span class="nv">MINGW_TOOLCHAIN_PREFIX</span><span class="si">}</span>-windres<span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>So I pointed CMake at this toolchain, started a build… and got treated to a massive wall of
errors. I’ve got to point out my favourite.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre>/mnt/p/unrealsdk/src/unrealsdk/logging.cpp:14:6: error: ‘mutex’ in namespace ‘std’ does not name a type
   14 | std::mutex mutex{};
      |      ^~~~~
/mnt/p/unrealsdk/src/unrealsdk/logging.cpp:8:1: note: ‘std::mutex’ is defined in header ‘&lt;mutex&gt;’; did you forget to ‘#include &lt;mutex&gt;’?
    7 | #include &lt;mutex&gt;
  +++ |+#include &lt;mutex&gt;
    8 |
</pre></td></tr></tbody></table></code></pre></div></div>

<p>To cut a long story short: unrealsdk depends on some “newer” C++ features, which weren’t yet
supported. As far as I understand it, MinGW is downstream of GCC, so it takes a bit to incorporate
newer features, and then it takes your distro’s package maintainers a bit to include new MinGW
releases, so it generally always ends up a bit behind.</p>

<p>Luckily, I found <a href="https://github.com/mstorsjo/llvm-mingw">llvm-mingw</a>, which is a drop in
replacement. Like the name suggests, it uses the LLVM toolchain, including the latest version of
libc++, which supported everything I needed.</p>

<p>Since I’m not sure where else to put this, I’m just going to mention here that I found that
<a href="https://ninja-build.org/">ninja</a> compiled up to 10x faster than makefiles, which CMake generates by
default. My whole motivation for switching to Linux was to avoid WSL’s build times, this is a
significant boost (under WSL I only saw a 2x improvement, it’s constrained by disk access).</p>

<h1 id="debugging-proton">Debugging Proton</h1>
<p>So we can compile from Linux. How do you debug? Compiling’s useless without being able to do that.</p>

<p>To start with, use a known good build, or just don’t use your mod at all, and get the game running
stably under Proton. You don’t want to be messing with it’s options at the same time as you’re
trying to debug it. Once it runs, launch it once using <code class="language-plaintext highlighter-rouge">PROTON_DUMP_DEBUG_COMMANDS=1 %command%</code>.
This will create a bunch of files in <code class="language-plaintext highlighter-rouge">/tmp/proton_$USER/</code>, which dump the entire Proton
configuration. Of these, <code class="language-plaintext highlighter-rouge">gdb_run</code> is closest to what we want, copy it somewhere else, then you can
clean up your launch args and delete the other files. Running this script should launch the game and
put you into a gdb session.</p>

<p>If you’re not using Steam, this step is equivalent to just putting together a shell script which
launches your executable with the correct wine prefix and all required env vars. To get into the
debugger, run:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>wine winedbg <span class="nt">--gdb</span> executable.exe args
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Of course make sure you’re compiling with appropriate debug information. For MinGW, it’s just adding
<code class="language-plaintext highlighter-rouge">-ggdb3</code>.</p>

<p>So this works, but I’ve never found using pure gdb to be the best debugging experience. It’s of
course good to learn how to use it properly, for when your frontend can’t handle everything, but a
good frontend makes standard usage 100x easier. Add <code class="language-plaintext highlighter-rouge">--no-start --port 2159</code> to run it as a server,
and then you can attach to it from your frontend of choice. <code class="language-plaintext highlighter-rouge">--no-start</code> is important in case you
ever need to debug initialization. If you want the game to auto run, let your frontend be what calls
continue, since it (hopefully) will only do so after setting all your breakpoints.</p>

<h2 id="vscode">VSCode</h2>
<p>So my frontend of choice is VSCode - using Microsoft’s propritary C/C++ extension. This is only
available from the Microsoft marketplace, if you’re running VSCodium or Code OSS you’ll have to play
around with some settings or just choose something else.</p>

<p>To start, add the following task, to launch the wine session.</p>
<div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
    </span><span class="nl">"label"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch proton gdb server"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shell"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;path to launch script&gt;"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"hide"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"isBackground"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="c1">// Need a dummy problem matcher to prevent the launch configuration waiting for complete</span><span class="w">
    </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"pattern"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"."</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"file"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"location"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">],</span><span class="w">
            </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"activeOnStart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                </span><span class="nl">"beginsPattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"."</span><span class="p">,</span><span class="w">
                </span><span class="nl">"endsPattern"</span><span class="p">:</span><span class="w"> </span><span class="s2">"."</span><span class="p">,</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>Then the following launch configuration will run the task and connect to the gdb server.</p>
<div class="language-jsonc highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"proton gbd"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cppdbg"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"program"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;path_to_executable&gt;"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"stopAtEntry"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"stopAtConnect"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"MIMode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gdb"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"miDebuggerPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gdb"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"miDebuggerServerAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"localhost:2159"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"preLaunchTask"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch proton gdb server"</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>Just remember that if you want to edit launch args or environment variables, you have to edit the
launch script, not the VSCode settings.</p>

<p>Now with these two added, hit debug… and it works. The game launches, you can set breakpoints, hit
them, step through code, it all just works.</p>

<h2 id="it-doesnt-all-just-work">It doesn’t all just work</h2>
<p>The above being said, there are a number of annoyances, things which don’t prevent debugging, but
don’t work quite like they should.</p>

<p>To start with, since I was building using llvm-mingw and libc++, gdb doesn’t understand any of the
standard library’s types. To address this, you can download <a href="https://github.com/koutheir/libcxx-pretty-printers/">this repo</a>
which has some custom pretty printers.</p>

<p>I could not get it to recognise my types. It knows that an object exists, it knows if it’s a pointer
or value. But I can’t get it show any of it’s fields. It does know the standard library fields, even
without the pretty printers, just not mine. It even lets me step into class methods, it’s not a
source mapping issue it should know they’re there. But it just can’t read any fields off of them.</p>

<p>The call stack is usually weirdly truncated. I’ve never seen it hit a breakpoint and understand more
than two stack levels. If you step into a function while paused, it usually goes up to three - but
not always, sometimes stuff falls off. If you step out of the function (or rather single step
through to the first return statement) it will work, and it will come up with some new stack levels,
it just never manages to show you them all at once.</p>

<p>It doesn’t understand thread names. Usually not a big problem, but sometimes I liked to pause the
game and jump over into my thread to see what it was stuck working on.</p>

<p>And saving the weirdest for last: step instruction does not always step a single instruction. I had
a case where I was stopped on a <code class="language-plaintext highlighter-rouge">sub rsp, 0x48</code> instruction, and even if I ran <code class="language-plaintext highlighter-rouge">si</code> manually through
the gdb console, I’d immediately jump to an exception handler. I believe this comes from winedbg
just pretending it’s gdb compatible, and actually executing a whole line. Breakpoints still work in
the range it jumps over, so to solve it I put a breakpoint as far up the call stack as I could see,
and repeated it a few times until I found the bug which was actually throwing.</p>

<p>Despite all this, it’s still an acceptable debugging experience - it’s no print debugging - but
it just makes it that extra bit more annoying. I’m still going to stick with primarily developing
from Windows using Visual Studio, since its debugger just works better.</p>

<h2 id="lldb">LLDB</h2>
<p>I did try using lldb. I came across <a href="https://werat.dev/blog/debugging-wine-with-lldb-and-vscode/">this post by werat</a>
detailing almost the exact thing I wanted - but I could never get it to work. I can’t remember which
way around exactly it was, but attaching to one process would constantly break on exceptions and
different signals (which I assume are just wine/windows api internals) making it unusable, and the
other wouldn’t load symbols and just generally couldn’t interact with the game process (which I
assume’s the mentioned lack of a dynamic loader). Even just trying to attach to the gdb server
didn’t work properly. If you can get lldb to work, I feel like it will have a far better debugging
experience, especially if you’re using as much of the LLVM toolchain as I already am. You could even
use it from a Window host for a consistent cross platform experience.</p>

<h1 id="not-a-true-windows-build">Not a true Windows build</h1>
<p>Now while we’re able to compile and debug a Windows build, and it runs perfectly fine, it turns out
it’s not a true Windows build. It does not create code completely compatible with code we’d see
compiling natively. The fact that I was using llvm-mingw makes this a lot more obvious.</p>

<p>Windows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>&gt; clang --version
clang version 16.0.3
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: [...]
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Linux:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre>&gt; x86_64-w64-mingw32-gcc --version
clang version 16.0.3 (https://github.com/llvm/llvm-project.git da3cd333bea572fb10470f610a27f22bcb84b08c)
Target: x86_64-w64-windows-gnu
Thread model: posix
InstalledDir: [...]
</pre></td></tr></tbody></table></code></pre></div></div>

<p>On Windows, Clang compiles under the <code class="language-plaintext highlighter-rouge">msvc</code> environment, but under Linux it compiles under <code class="language-plaintext highlighter-rouge">gnu</code>.
I’m pretty sure this is it accurately emulating how MinGW works, it’s not an issue with Clang
itself. But what exactly does this mean for us? To answer this I need to explain a bit more about
the unrealsdk project structure.</p>

<p>For reasons I won’t get into, you can only run one copy of the sdk per game process. But what if you
want to use two separate projects both linking against it? To solve this, instead of linking
statically, I provide an option to build the sdk as a shared library, and link against that, so
there’s ever only one copy, but multiple projects can all use it at once. Because we’re already
using multiple different compilers and standard libraries, all the exported functions use a pure C
ABI, so you can mix and match projects from different compilers. This is important in case you’re
developing one project while still running other precompiled ones. But there’s one thing which we
can’t easily convert to a C ABI: exceptions. And guess what ABI changes between <code class="language-plaintext highlighter-rouge">msvc</code> and <code class="language-plaintext highlighter-rouge">gnu</code>? If
you try mix exception ABIs, the game just crashes when an exception passes between them.</p>

<p>If you’re building a single static project, or if you’re never going to let exceptions cross module
boundaries, sticking with <code class="language-plaintext highlighter-rouge">gnu</code> is completely fine. In my specific weird scenario however, this is a
problem.</p>

<h2 id="why-use-exceptions">Why use exceptions?</h2>
<p>Before moving on, I just want to explain a bit more about why I decided letting exceptions travel
over module boundaries was the right solution. Feel free to jump right to the next heading.</p>

<p>Firstly, one of the main philosophies of the project: We need to be as invisible as possible. Break
the sdk if need be, I don’t care if it means we’re limping along unable to do anything else, we do
not break the game. Crashing the game is completely unacceptable. This means disabling exceptions is
a complete non-starter, since that just turns any exception thrown deep down in some library call
into a crash.</p>

<p>Why not use <code class="language-plaintext highlighter-rouge">noexcept</code>? Because like with disabling exceptions globally, it actually means
“if an exception gets here without being caught, crash the game”. Compilers might give a warning if
they work out a <code class="language-plaintext highlighter-rouge">noexcept</code> function can throw, but it’s no guarantee, they’d have to solve the
halting problem. The sdk in fact makes use of a tonne of templating, which makes it very easy for
exceptions to hide from the compiler.</p>

<p>Why not catch the exception right before the boundary, and re-throw it after? This makes us lose a
lot of information. If you’re using debug versions of both the sdk and your library, the stack
trace will go right back into the sdk, which can often tell you a lot more about the error - we’d
lose that. We’d also lose the exact exception type, or at least limit it to one of a known subset.
Since the exception gets destroyed on leaving the catch block, we’d also need to copy it’s fields -
which means if we don’t know the type we lose any unknown fields. And finally, checking if the other
side threw is just a lot of overhead for a case which is by definition exceptional.</p>

<p>Why can exceptions even happen? Because we can’t stop them. One of the major features of the sdk is
hooks, you can register a callback to be run when an unrealscript function is called. But this means
the sdk is calling user code. And we definitely can’t trust user code to never throw. If it does, we
need to catch it so that we remain invisible, so we can let the unrealscript code continue as if
nothing happened. None of the exported functions actually intentionally throw an exception, they all
prefer returning an appropriate failure value. But it’s not worth searching through the whole call
graph to make sure they <em>never do</em>, because we know hooks will always be able to throw, so we’ll
always need to deal with exceptions passing between modules anyway.</p>

<h1 id="compiling-with-the-msvc-exception-abi">Compiling with the MSVC Exception ABI</h1>
<p>So, how can we compile a MSVC ABI build from Linux. Clang natively supports cross compiling, but it
needs access to the MSVC headers/libs - and unrealsdk depends on a small a handful of things from
<code class="language-plaintext highlighter-rouge">windows.h</code>, so I can’t just point it at libc++. So how can we get these? I found two projects which
can help, <a href="https://github.com/mstorsjo/msvc-wine">msvc-wine</a> (from the same guy as llvm-mingw), and
<a href="https://github.com/Jake-Shadle/xwin/">xwin</a>. The super quick comparison: msvc-wine is better to
install locally on your dev machine; xwin is better to use in CI.</p>

<p>Two extra things I want to quickly note: msvc-wine does also, like the name implies, contain a bunch
of scripts to let your run MSVC under wine. I didn’t explore this, since I figured it’d probably
have worse performance and might not be as stable. And the second: xwin does not (yet) provide the
vc debug dlls, <code class="language-plaintext highlighter-rouge">vcruntime140d.dll</code> and the like, which you’ll need to launch any debug builds. It
does provide everything required to compile a debug build though, so if you can get them from
another source it’ll still work fine.</p>

<p>Once you’ve picked one of the tools and got it downloaded, you can start compiling. I put together
the following cmake toolchain. It requires you pass in the triple you want to compile for, and
either the path to the relevant msvc-wine <code class="language-plaintext highlighter-rouge">msvcenv.sh</code> script, or the path to the xwin install
folder.</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
</pre></td><td class="rouge-code"><pre><span class="nb">set</span><span class="p">(</span>CMAKE_SYSTEM_NAME Windows<span class="p">)</span>

<span class="nb">set</span><span class="p">(</span>CMAKE_C_COMPILER clang<span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>CMAKE_C_COMPILER_TARGET <span class="si">${</span><span class="nv">CLANG_TRIPLE</span><span class="si">}</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>CMAKE_CXX_COMPILER clang++<span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>CMAKE_CXX_COMPILER_TARGET <span class="si">${</span><span class="nv">CLANG_TRIPLE</span><span class="si">}</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span>CMAKE_RC_COMPILER llvm-rc<span class="p">)</span>

<span class="c1"># Problem: CMake runs toolchain files multiple times, but can't read cache variables on some runs.</span>
<span class="c1"># Workaround: On first run (in which cache variables are always accessible), set an intermediary environment variable.</span>
<span class="c1"># https://stackoverflow.com/a/29997033</span>
<span class="nb">if</span><span class="p">(</span>MSVC_WINE_ENV_SCRIPT OR XWIN_DIR<span class="p">)</span>
    <span class="nb">set</span><span class="p">(</span>ENV{_MSVC_WINE_ENV_SCRIPT} <span class="s2">"</span><span class="si">${</span><span class="nv">MSVC_WINE_ENV_SCRIPT</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
    <span class="nb">set</span><span class="p">(</span>ENV{_XWIN_DIR} <span class="s2">"</span><span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="nb">else</span><span class="p">()</span>
    <span class="nb">set</span><span class="p">(</span>MSVC_WINE_ENV_SCRIPT <span class="s2">"$ENV{_MSVC_WINE_ENV_SCRIPT}"</span><span class="p">)</span>
    <span class="nb">set</span><span class="p">(</span>XWIN_DIR <span class="s2">"$ENV{_XWIN_DIR}"</span><span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>

<span class="nb">if</span><span class="p">(</span>EXISTS <span class="si">${</span><span class="nv">MSVC_WINE_ENV_SCRIPT</span><span class="si">}</span><span class="p">)</span>
    <span class="c1"># @brief Extract paths from the env script and pass them to another function</span>
    <span class="c1">#</span>
    <span class="c1"># @param env_var The environment variable to extract</span>
    <span class="c1"># @param prefix A prefix to add to the start of each path (e.g. `-I`)</span>
    <span class="c1"># @param output_function The function to call with the list of extracted paths</span>
    <span class="nb">function</span><span class="p">(</span>_extract_from_env env_var prefix output_function<span class="p">)</span>
        <span class="nb">execute_process</span><span class="p">(</span>
            COMMAND bash -c <span class="s2">". </span><span class="si">${</span><span class="nv">MSVC_WINE_ENV_SCRIPT</span><span class="si">}</span><span class="s2"> &amp;&amp; echo </span><span class="se">\"\$</span><span class="si">${</span><span class="nv">env_var</span><span class="si">}</span><span class="se">\"</span><span class="s2">"</span>
            OUTPUT_VARIABLE env_output
            COMMAND_ERROR_IS_FATAL ANY
        <span class="p">)</span>
        <span class="nb">string</span><span class="p">(</span>REPLACE <span class="s2">"z:</span><span class="se">\\</span><span class="s2">"</span> <span class="s2">"</span><span class="si">${</span><span class="nv">prefix</span><span class="si">}</span><span class="s2">/"</span> env_output <span class="s2">"</span><span class="si">${</span><span class="nv">env_output</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
        <span class="nb">string</span><span class="p">(</span>REPLACE <span class="s2">"</span><span class="se">\\</span><span class="s2">"</span> <span class="s2">"/"</span> env_output <span class="s2">"</span><span class="si">${</span><span class="nv">env_output</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
        <span class="nb">string</span><span class="p">(</span>REGEX MATCHALL <span class="s2">"[^;</span><span class="se">\r\n</span><span class="s2">]+"</span> env_output_list <span class="s2">"</span><span class="si">${</span><span class="nv">env_output</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>

        <span class="nb">cmake_language</span><span class="p">(</span>CALL <span class="si">${</span><span class="nv">output_function</span><span class="si">}</span> <span class="si">${</span><span class="nv">env_output_list</span><span class="si">}</span><span class="p">)</span>
    <span class="nb">endfunction</span><span class="p">()</span>

    <span class="nf">_extract_from_env</span><span class="p">(</span><span class="s2">"INCLUDE"</span> <span class="s2">"-isystem"</span> add_compile_options<span class="p">)</span>
    <span class="nf">_extract_from_env</span><span class="p">(</span><span class="s2">"LIB"</span> <span class="s2">"-L"</span> add_link_options<span class="p">)</span>
<span class="nb">elseif</span><span class="p">(</span>EXISTS <span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="p">)</span>
    <span class="nb">add_compile_options</span><span class="p">(</span>
        <span class="s2">"-isystem</span><span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="s2">/sdk/include/um"</span>
        <span class="s2">"-isystem</span><span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="s2">/sdk/include/ucrt"</span>
        <span class="s2">"-isystem</span><span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="s2">/sdk/include/shared"</span>
        <span class="s2">"-isystem</span><span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="s2">/crt/include"</span>
    <span class="p">)</span>

    <span class="nb">if</span><span class="p">(</span>NOT DEFINED XWIN_ARCH<span class="p">)</span>
        <span class="nb">if</span><span class="p">(</span><span class="si">${</span><span class="nv">CLANG_TRIPLE</span><span class="si">}</span> MATCHES 64<span class="p">)</span>
            <span class="nb">set</span><span class="p">(</span>XWIN_ARCH x86_64<span class="p">)</span>
        <span class="nb">else</span><span class="p">()</span>
            <span class="nb">set</span><span class="p">(</span>XWIN_ARCH x86<span class="p">)</span>
        <span class="nb">endif</span><span class="p">()</span>
    <span class="nb">endif</span><span class="p">()</span>

    <span class="nb">add_link_options</span><span class="p">(</span>
        <span class="s2">"-L</span><span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="s2">/sdk/lib/um/</span><span class="si">${</span><span class="nv">XWIN_ARCH</span><span class="si">}</span><span class="s2">"</span>
        <span class="s2">"-L</span><span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="s2">/crt/lib/</span><span class="si">${</span><span class="nv">XWIN_ARCH</span><span class="si">}</span><span class="s2">"</span>
        <span class="s2">"-L</span><span class="si">${</span><span class="nv">XWIN_DIR</span><span class="si">}</span><span class="s2">/sdk/lib/ucrt/</span><span class="si">${</span><span class="nv">XWIN_ARCH</span><span class="si">}</span><span class="s2">"</span>
    <span class="p">)</span>
<span class="nb">else</span><span class="p">()</span>
    <span class="nb">message</span><span class="p">(</span>FATAL_ERROR <span class="s2">"One of 'MSVC_WINE_ENV_SCRIPT' or 'XWIN_DIR' must be defined, could not find windows headers/libs!"</span><span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>

<span class="nb">add_compile_options</span><span class="p">(</span>-ffreestanding<span class="p">)</span>

<span class="nb">add_compile_options</span><span class="p">(</span><span class="s2">"$&lt;$&lt;CONFIG:DEBUG&gt;:-gdwarf&gt;"</span><span class="p">)</span>
<span class="nb">add_link_options</span><span class="p">(</span><span class="s2">"$&lt;$&lt;CONFIG:DEBUG&gt;:-gdwarf&gt;"</span> <span class="s2">"$&lt;$&lt;CONFIG:DEBUG&gt;:-Wl,/ignore:longsections&gt;"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>While it’s a bit of a long script, what it does is rather simple, if you needed to port it to
another build system. All you need to do is set <code class="language-plaintext highlighter-rouge">--target={triple}</code> and <code class="language-plaintext highlighter-rouge">-ffreestanding</code>, and then
point it at the include/lib dirs. When debugging, you’ll also want <code class="language-plaintext highlighter-rouge">-gdwarf</code> for symbols - and since
this can throw a warning you may want to ignore it with <code class="language-plaintext highlighter-rouge">-Wl,/ignore:longsections</code>.</p>

<p>So using this toolchain, and setting the relevant inputs, everything compiles properly. I built my
exception test dlls, copied them over, and it works, I can catch exceptions across module
boundaries. Remember debug builds will link against the vc debug dlls, so like I mentioned earlier
you’ll need to find a copy of them, and copy them over to the game as well.</p>

<p>Debugging works basically the exact same as with the MinGW build, with the exact same annoyances.
There one more big problem though: If an exception is thrown while the debugger is attached, the
game crashes. Launching the game without debugger, using the exact same build, it runs fine, it’s
not an exception which goes uncaught. I haven’t been able to work out a way around this, I assume
it’s something related to debuggers catching an exception at it’s source, some behaviour winedbg
doesn’t emulate properly. If your project throws a lot, this is probably a dealbreaker, you’ll have
to stick with using GNU ABI builds for debugging, and then only use this method to get a MSVC ABI
build for releases (assuming of course that’s the ABI you’re releasing with).</p>]]></content><author><name></name></author><summary type="html"><![CDATA[How I got a Linux host to compile and debug a dll mod being run under Proton.]]></summary></entry><entry><title type="html">Why BL3’s Maurice Vendor RNG Sucks</title><link href="https://apple1417.dev/posts/2023-05-13-maurice-rng" rel="alternate" type="text/html" title="Why BL3’s Maurice Vendor RNG Sucks" /><published>2023-05-13T00:00:00+00:00</published><updated>2023-05-13T00:00:00+00:00</updated><id>https://apple1417.dev/posts/maurice-rng</id><content type="html" xml:base="https://apple1417.dev/posts/2023-05-13-maurice-rng"><![CDATA[<p>Update 2023-06-11: I wrote an <a href="https://apple1417.dev/bl3/maurice/">online predictor available here</a>.</p>

<p>Maurice’s Black Market is a vendor which spawns in a random location each week, selling a selection
of legendary items. Speedruns were using one particular week to get an overpowered gun, but there
was a question hanging in the air: is there a week we can get it somewhere better? It turns out, no,
the RNG is horribly flawed.</p>

<h1 id="selection-logic">Selection Logic</h1>
<p>I’ll leave out the boring details on how exactly I reverse engineered it, that’s not what this post
is about. A python recreation of the logic is <a href="https://gist.github.com/apple1417/8f1664daa3b2fca3d8705d964a3ce603">available here</a>
- I’ll be copying some snippets from it later.</p>

<p>So broad overview of how it works. There are 52 item pools and 66 locations to choose from. To get a
seed, it simply works out the number of weeks since 1999-02-18 (the Thursday after Gearbox was
founded, incidentally). There’s a weird little extra bit of logic about what time exactly it rolls
over, but we don’t need to concern ourselves with that. To make sure it picks every option once
before repeating, it works out the last multiple of 52 (or 66), and back-calculates what entries it
picked the previous weeks and removes them from the list. It then just does a basic random index to
picks the current week’s entry.</p>

<h1 id="first-signs-of-a-problem">First Signs of a Problem</h1>
<p>Once I’d recreated the logic, I started just running some brute force tests - for every week, are
the pool and location acceptable? In particular, we wanted to see if we could get the Hellwalker
item pool (<code class="language-plaintext highlighter-rouge">ItemPool_BMV_Week30</code>) somewhere on Pandora 1 (<code class="language-plaintext highlighter-rouge">Zone_0</code>). No results in 1,000 weeks.
10,000? None. 100,000? 10,000,000? Nothing. 9999-12-31 is week 417,465. Unreal’s date representation
(u64 of 100ns ticks since 0000-01-01) caps out at approximately week 3,000,000. Three times that,
and still no results.</p>

<p>I next just dumped some results pinning one of the entries. Between weeks 1200-2200, the last
location, <code class="language-plaintext highlighter-rouge">Desolate</code> + <code class="language-plaintext highlighter-rouge">_3</code>, rolls alongside these pools.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre>1207: ItemPool_BMV_Week52
1284: ItemPool_BMV_Week45
1361: ItemPool_BMV_Week51
1438: ItemPool_BMV_Week52
1453: ItemPool_BMV_Week43
1535: ItemPool_BMV_Week47
1612: ItemPool_BMV_Week52
1689: ItemPool_BMV_Week52
1766: ItemPool_BMV_Week42
1838: ItemPool_BMV_Week51
1858: ItemPool_BMV_Week45
1935: ItemPool_BMV_Week52
2012: ItemPool_BMV_Week45
2089: ItemPool_BMV_Week51
2166: ItemPool_BMV_Week52
2181: ItemPool_BMV_Week43
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Those numbers are awfully close together. In the same time range, the last pool,
<code class="language-plaintext highlighter-rouge">ItemPool_BMV_Week52</code>, rolls alongside these locations.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre>1207: Desolate + _3
1279: Desolate + _0
1341: Crypt + _0
1366: MotorcadeFestival + _1
1438: Desolate + _3
1500: Desolate + _1
1530: Crypt + _0
1597: Desolate + _1
1612: Desolate + _3
1689: Desolate + _3
1756: Desolate + _0
1776: Motorcade + _1
1848: Desolate + _1
1915: Crypt + _3
1935: Desolate + _3
2007: Desolate + _0
2069: Crypt + _0
2094: MotorcadeFestival + _1
2166: Desolate + _3
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You need a little more game (+ modding) knowledge to tell, but again this is picking a lot of maps,
and locations within those maps, which are very close together.</p>

<p>Checking across far larger date ranges, the pattern continues - though it does appear to, very
slowly over hundreds of years, drift down through the list. Why will there never be a Hellwalker in
a Pandora 1 vendor? Because somehow, these two “independent” random variables are locked in a very
tight resonance.</p>

<h1 id="historical-data">Historical Data</h1>
<p>Now before I go ahead and reveal the answer, here’s another interesting pattern I found after
working it out. Mental Mars has been listing all the <a href="https://mentalmars.com/guides/maurices-black-market-location-guide-borderlands-3/">historical vendor locations here</a>.
Note that week 32 and earlier were all manually placed by gearbox in weekly hotfixes - they won’t
line up with what the RNG predicts.</p>

<p>So the specific thing I want to draw attention to is the maps picked in weeks 80-109. Scrolling
through them linearly, it seems pretty random. But watch what happens when I plot them on a grid.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">Week</th>
      <th style="text-align: center">0 mod 5</th>
      <th style="text-align: center">1 mod 5</th>
      <th style="text-align: center">2 mod 5</th>
      <th style="text-align: center">3 mod 5</th>
      <th style="text-align: center">4 mod 5</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">80</td>
      <td style="text-align: center">Carnivora</td>
      <td style="text-align: center">Skywell</td>
      <td style="text-align: center">Devil’s</td>
      <td style="text-align: center">Ascension</td>
      <td style="text-align: center">Anvil</td>
    </tr>
    <tr>
      <td style="text-align: right">85</td>
      <td style="text-align: center">Tazendeer</td>
      <td style="text-align: center">Skywell</td>
      <td style="text-align: center">Devil’s</td>
      <td style="text-align: center">Ascension</td>
      <td style="text-align: center">Anvil</td>
    </tr>
    <tr>
      <td style="text-align: right">90</td>
      <td style="text-align: center">Tazendeer</td>
      <td style="text-align: center">Skywell</td>
      <td style="text-align: center">Devil’s</td>
      <td style="text-align: center">Atlas HQ</td>
      <td style="text-align: center">Estate</td>
    </tr>
    <tr>
      <td style="text-align: right">95</td>
      <td style="text-align: center">Tazendeer</td>
      <td style="text-align: center">Outskirts</td>
      <td style="text-align: center">Splinterlands</td>
      <td style="text-align: center">Arterial</td>
      <td style="text-align: center">Floodmoor</td>
    </tr>
    <tr>
      <td style="text-align: right">100</td>
      <td style="text-align: center">Tazendeer</td>
      <td style="text-align: center">Skywell</td>
      <td style="text-align: center">Devil’s</td>
      <td style="text-align: center">Atlas HQ</td>
      <td style="text-align: center">Anvil</td>
    </tr>
    <tr>
      <td style="text-align: right">105</td>
      <td style="text-align: center">Tazendeer</td>
      <td style="text-align: center">Outskirts</td>
      <td style="text-align: center">Cathedral</td>
      <td style="text-align: center">Atlas HQ</td>
      <td style="text-align: center">Anvil</td>
    </tr>
  </tbody>
</table>

<p>Incidentally, week 100 was the start of a new 66-week location cycle, which is why old locations can
repeat again, helping make this pattern stand out as much as it does.</p>

<p>This same pattern exists for item pools, but it’s harder to notice without generating the results
yourself.</p>

<h1 id="investigating">Investigating</h1>
<p>So our randomness isn’t being very random. Let’s make sure Gearbox isn’t using it wrong.</p>

<h2 id="picking-random-entries">Picking Random Entries</h2>
<p>We know the game makes sure to use every location/pool once before repeating. Is this selection
logic introducing bias?</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">_pick_from_arr</span><span class="p">(</span><span class="n">week</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">arr</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="n">arr_copy</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">week</span> <span class="o">-</span> <span class="p">(</span><span class="n">week</span> <span class="o">%</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)),</span> <span class="n">week</span><span class="p">):</span>
        <span class="n">arr_copy</span><span class="p">.</span><span class="n">pop</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">srand_once</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr_copy</span><span class="p">)))</span>

    <span class="k">return</span> <span class="n">arr_copy</span><span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">srand_once</span><span class="p">(</span><span class="n">week</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr_copy</span><span class="p">))]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Rather than thinking about this back-calculating results, we can rearrange it a bit to think about
it sequentially.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">yield</span> <span class="n">arr</span><span class="p">.</span><span class="n">pop</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">srand_once</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)))</span>
<span class="k">yield</span> <span class="n">arr</span><span class="p">.</span><span class="n">pop</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">srand_once</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)))</span>
<span class="k">yield</span> <span class="n">arr</span><span class="p">.</span><span class="n">pop</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">srand_once</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)))</span>
<span class="p">...</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Each week, we have an array of a certain size. We get the week’s seed, use it to pick a random
value, multiply it by the size, and use that as an index. Assuming we have a fairly picked random
number, any index is going to be equally likely to be chosen. We then remove that entry from the
list, and return it from that week. On a week by week basis, there’s obviously a bit of bias here -
you can’t get the same entry two weeks in a row (unless it happens to be on the edge of a cycle).
But this is just standard selection without replacement. On the macro scale however, across two or
more cycles, you’d expect any entry to be equally likely to any other. This means when we combine
the two independent variables, location and item pool, we shouldn’t be seeing patterns.</p>

<h2 id="the-random-number-generator">The Random Number Generator</h2>
<p>If it’s not the selection algorithm biasing our results, is it the RNG?</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">srand_once</span><span class="p">(</span><span class="n">seed</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
    <span class="n">x</span> <span class="o">=</span> <span class="p">(</span><span class="n">seed</span> <span class="o">*</span> <span class="mh">0xbb38435</span> <span class="o">+</span> <span class="mh">0x3619636b</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0xFFFFFFFF</span>
    <span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">&amp;</span> <span class="mh">0x7fffff</span> <span class="o">|</span> <span class="mh">0x3f800000</span>
    <span class="n">f</span> <span class="o">=</span> <span class="n">struct</span><span class="p">.</span><span class="n">unpack</span><span class="p">(</span><span class="s">"&gt;f"</span><span class="p">,</span> <span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">"&gt;I"</span><span class="p">,</span> <span class="n">x</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">f</span> <span class="o">-</span> <span class="nb">int</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This starts with a pretty standard linear congruential generator. A quick google shows it’s the same
constants as those used in the OPUS audio codec (<a href="https://www.rfc-editor.org/rfc/rfc6716">RFC6716 pg 98</a>)
- so they’re probably fine, we can assume it gives us fair random numbers.</p>

<p>It then masks out the top 9 bits, and forces them to <code class="language-plaintext highlighter-rouge">0x3f800000</code>, before re-interpreting the value
as a float. The masking sets the sign to positive and the exponent to \(2^0\), and leaves the
mantissa to the random bits. This means we’re down to just 23 bits of randomness, but it doesn’t
introduce any bias in and of itself. Interpreting it as a float thus gives us a value fairly
distributed over the range \([1.0, 2.0)\).</p>

<p>Finally, it subtracts the integer portion of the float from itself. This will really always just be
a constant subtract 1. It’s not immediately obvious, but this operation is guaranteed not to lose
any precision. The implicit 24th mantissa bit must always be set, and since subtracting 1 will clear
it, the mantissa simply gets left shifted until the most significant set bit ends up in that 24th
slot (and the exponent gets adjusted accordingly). So this conversion does not add any bias either,
we will get a fairly picked random number in the range \([0.0, 1.0)\).</p>

<h1 id="the-problem">The Problem</h1>
<p>So it’s not how we’re picking entries from the list, and it’s not the RNG. You’ve probably already
worked out the problem - it’s how they combine.</p>

<p>While a linear congruential generator outputs a perfectly fine stream of random numbers, it needs to
keep advancing it’s internal state to do so - usually done by keeping it as a private static. If you
seed the state the same, it produces the same stream of numbers - the very definition of a PRNG.</p>

<p>Like the name of my python re-implementation implies, the problem is that they only call <code class="language-plaintext highlighter-rouge">SRand()</code>
once. They then immediately call <code class="language-plaintext highlighter-rouge">SRandInit(seed)</code>, which throws away how the state advanced. And
what are they re-seeding it with? The last seed plus one.</p>

\[\begin{align*}
SRandOnce(w)     &amp;= Aw + C              &amp;\mod &amp;M \\
\\
SRandOnce(w + 1) &amp;= A(w + 1) + C        &amp;\mod &amp;M \\
                 &amp;= Aw + C + A          &amp;\mod &amp;M \\
                 &amp;= SRandOnce(w) + A    &amp;\mod &amp;M
\end{align*}\]

<p>By only calling <code class="language-plaintext highlighter-rouge">SRand()</code> once, with incrementing seeds, all they’re doing is incrementing the
result by \(A \mod M\) each time. What does this mean with our numbers?</p>

<p>Assume \(SRandOnce(w) = 0\).</p>

\[\begin{align*}
SRandOnce(w + 1) &amp;= A &amp;\mod &amp;M \\
                 &amp;= \mathtt{BB38435}_{16} &amp;\mod &amp;\mathtt{FFFFFFFF}_{16} \\
                 &amp;= \mathtt{BB38435}_{16}
\end{align*}\]

<p>Since we know the sign is positive and the exponent is \(2^0\), we only need to care about the
mantissa.</p>

\[\begin{align*}
\mathtt{BB38435}_{16} \mathbin{\&amp;} \mathtt{7FFFFF}_{16} &amp;= [\mathtt{1}.]\mathtt{338435}_{16} \\
                                                        &amp;= 1.4024721384_{10} \\
\\
1.4024721384_{10} - \lfloor1.4024721384_{10}\rfloor     &amp;= 0.4024721384_{10}
\end{align*}\]

<p>For completeness, we can also work out the constant offset, which we’ll see week 0.</p>

\[\begin{align*}
SRandOnce(0) = C \mod M                                  &amp;= \mathtt{3619636B}_{16} \\
\\
\mathtt{3619636B}_{16} \mathbin{\&amp;} \mathtt{7FFFFF}_{16} &amp;= [\mathtt{1}.]\mathtt{19636B}_{16} \\
                                                         &amp;= 1.19834649563_{10} \\
\\
1.19834649563_{10} - \lfloor1.19834649563_{10}\rfloor    &amp;= 0.19834649563_{10}
\end{align*}\]

<h2 id="5-week-pattern">5 Week Pattern</h2>
<p>Remember that 5 week pattern? Every week gets a random index approximately 40% further through the
list than the last, so generally, after 5 weeks the game will pick an entry immediately adjacent to
the one it picked last time. And because the lists are ordered, the adjacent location is typically
on the same or on an adjacent map. The exact same happens with item pools, but it’s harder to
notice.</p>

<p>Now this isn’t a perfect rule. It isn’t a perfect 40% increment so sometimes enough error builds up
to jump two indexes instead of one, and when the 52/66 week cycles reset there can be even larger
ones. We can even see some of these in the example grid. But in the majority of cases, the rule will
work fine.</p>

<h2 id="macro-scale-pattern">Macro Scale Pattern</h2>
<p>So what about the longer term pattern, across hundreds of years, when you pin a location/item pool?</p>

<p>There’s another relatively simple cause contributing to this: the item pool and location simply are
<em>not</em> independent. They’re both generated using the exact same seed, so they both get the exact
random float. If the two arrays were the same size, they’d always pick the exact same index, the
only reason there’s any variation is because they’re different.</p>

<p>The two size cycles line back up after \(\DeclareMathOperator{\lcm}{lcm} \lcm(52, 66) = 1716\)
weeks. This actually removes half of all possible variation to begin with, \(\gcd(52, 66) = 2\).
But this is still far more variation than what we’re seeing. To continue reasoning about it, let’s
move to a smaller scale example, with 4 and 6 entries. Let’s consider splitting the range
\([0.0, 1.0)\) into 60 parts. Based on the array size, we’ll pick indexes as follows.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre>    0         10        20        30        40        50
    -----=====-----=====-----=====-----=====-----=====-----=====
6 | 000000000011111111112222222222333333333344444444445555555555
5 | 000000000000111111111111222222222222333333333333444444444444
4 | 000000000000000111111111111111222222222222222333333333333333
3 | 000000000000000000001111111111111111111122222222222222222222
2 | 000000000000000000000000000000111111111111111111111111111111
1 | 000000000000000000000000000000000000000000000000000000000000
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Given two sizes, we can pick those two rows, and picking a random float is equivalent to picking a
random column and reading off it’s indexes. For example, if we rolled 40/60, we can come up with the
following possible index pairs.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">A Size</th>
      <th style="text-align: left">Index Pairs</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">4</td>
      <td style="text-align: left">(2, 4), (2, 2), (2, 1)</td>
    </tr>
    <tr>
      <td style="text-align: right">3</td>
      <td style="text-align: left">(2, 3), (2, 2), (2, 0)</td>
    </tr>
    <tr>
      <td style="text-align: right">2</td>
      <td style="text-align: left">(1, 4), (1, 2), (1, 1)</td>
    </tr>
    <tr>
      <td style="text-align: right">1</td>
      <td style="text-align: left">(0, 3), (0, 2), (0, 0)</td>
    </tr>
  </tbody>
</table>

<p>Note half the pairs are missing because \(\gcd(4, 6) = 2\), same as in our large range. But we’re
not really interested in this table. What we actually care about is just the fact that, regardless
of circumstance, there are only ever 60 different ranges a float can fall into and make a meaningful
difference. We could do the same to our full range, split it into some far larger number of columns,
where picking a random column would always be equivalent to picking a random float.</p>

<p>Now let’s bring back the constant increment. If it were a perfect 0.4, it’s trivial to see that we’d
only ever pick 5 columns - regardless of the array sizes. If we pin A, this results in a 20 week
cycle, so we can quickly dump them all:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="n">As</span> <span class="o">=</span> <span class="p">(</span><span class="s">"a"</span><span class="p">,</span> <span class="s">"b"</span><span class="p">,</span> <span class="s">"c"</span><span class="p">,</span> <span class="s">"d"</span><span class="p">)</span>
<span class="n">Bs</span> <span class="o">=</span> <span class="p">(</span><span class="s">"u"</span><span class="p">,</span> <span class="s">"v"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="s">"x"</span><span class="p">,</span> <span class="s">"y"</span><span class="p">,</span> <span class="s">"z"</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">pick_perfect_0_4</span><span class="p">(</span><span class="n">week</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">arr</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">rng</span><span class="p">(</span><span class="n">week</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">(</span><span class="mf">0.4</span> <span class="o">*</span> <span class="n">week</span><span class="p">)</span> <span class="o">%</span> <span class="mi">1</span>
    <span class="n">arr_copy</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">week</span> <span class="o">-</span> <span class="p">(</span><span class="n">week</span> <span class="o">%</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)),</span> <span class="n">week</span><span class="p">):</span>
        <span class="n">arr_copy</span><span class="p">.</span><span class="n">pop</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">rng</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">arr_copy</span><span class="p">)))</span>
    <span class="k">return</span> <span class="n">arr_copy</span><span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">rng</span><span class="p">(</span><span class="n">week</span><span class="p">)</span> <span class="o">*</span>  <span class="nb">len</span><span class="p">(</span><span class="n">arr_copy</span><span class="p">))]</span>

<span class="n">selected_a</span> <span class="o">=</span> <span class="p">[</span><span class="n">pick_perfect_0_4</span><span class="p">(</span><span class="n">week</span><span class="p">,</span> <span class="n">As</span><span class="p">)</span> <span class="k">for</span> <span class="n">week</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">20</span><span class="p">)]</span>
<span class="n">selected_b</span> <span class="o">=</span> <span class="p">[</span><span class="n">pick_perfect_0_4</span><span class="p">(</span><span class="n">week</span><span class="p">,</span> <span class="n">Bs</span><span class="p">)</span> <span class="k">for</span> <span class="n">week</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">20</span><span class="p">)]</span>
<span class="n">filtered_b</span> <span class="o">=</span> <span class="p">[</span><span class="n">selected_b</span><span class="p">[</span><span class="n">week</span><span class="p">]</span> <span class="k">if</span> <span class="n">selected_a</span><span class="p">[</span><span class="n">week</span><span class="p">]</span> <span class="o">==</span> <span class="s">"b"</span> <span class="k">else</span> <span class="s">" "</span> <span class="k">for</span> <span class="n">week</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">20</span><span class="p">)]</span>
<span class="k">print</span><span class="p">(</span><span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">selected_a</span><span class="p">)</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span> <span class="o">+</span> <span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">filtered_b</span><span class="p">)</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span> <span class="o">+</span> <span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">selected_b</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>acdbcabdacbddacbbdac
   v  w   v    uw
uxzvywwzuxvyyvxuwzvy
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Look familiar? If we weren’t removing picked entries, we’d always get <code class="language-plaintext highlighter-rouge">b</code> from column 24, which
would always correspond 1-1 with <code class="language-plaintext highlighter-rouge">w</code>. But since we are removing entries, the first time we pick it
in week 3, we’re in column 12, and the B array is down to just <code class="language-plaintext highlighter-rouge">vwy</code>, so we end up with <code class="language-plaintext highlighter-rouge">v</code> instead.
Removing picked entries adds noise, the amount of which is relative to the inverse of how many
entries are left.</p>

<p>So if we get a lot more noise when there are only a few entries left in the array, why didn’t we see
it picking anything further out when looking at <code class="language-plaintext highlighter-rouge">ItemPool_BMV_Week52</code> back at the beginning? Because
the order each entry is picked is also relatively constant. If we’re looking for an entry 3 indexes
away from one of the “center lines” we’d pick at the start of the cycle, it will always take 15
weeks after picking the center line’s entry, or 15-20 weeks from the start. This isn’t a perfect
rule again, epecially for the later entries, sometimes it’s a few more 5-week cycles out. But it
still means, generally, an entry which appears at the start of a cycle will continue to appear near
the start of following ones - and thus will continue to have relatively low noise. Where does our
example appear? About week 30.</p>

<p>The last thing to explain is the drift. This is relatively simple: we’ve been assuming everything
increments by exactly 0.4, when in fact there’s a slight error. It takes 405 weeks for the error to
grow larger than 1. Because the error applies to both the item pool and location equally, we need to
include their array size cycles alongside this new 405 week cycle, for a total length of
\(\DeclareMathOperator{\lcm}{lcm} \lcm(52, 66, 405) = 231660\) weeks to get back to the start. Of
course, the error isn’t exactly \(\frac{1}{405}\), so we could delve even deeper until it actually
resets to 0. But this value already tells us what we want: if it takes 231,660 weeks for the
locations’ center points to cycle though all 66 indexes (given a fixed item pool), advancing just
one will take \(\frac{231660}{66} = 3510\) weeks. And remember that we easily have 5-10 indexes
worth of noise, so it takes even longer to become recognisable.</p>

<h1 id="conclusion">Conclusion</h1>
<p>So to summarize our findings: The game puts an incrementing random seed through a linear
congruential generator, and only calls it once before re-seeding. This removes the randomness,
turning it into just a linear function, always spitting out a float about 0.4 bigger than the last.
This means after 5 weeks, it generally picks a location or item pool immediately adjacent to the
first. The game also uses the exact same seed for both the location and item pool. The only reason
we see variation is because the two lists are different sizes. But because the sizes have a common
multiple of 2, we already lose out on half the possible variation. Then because of that 0.4
increment again, we will pick entries in roughly the same order every single 52/66 week cycle, just
with a little bit of noise mixing them up, killing most of the remaining variation. And finally,
because the increment isn’t perfectly 0.4, there’s a slight error, it eventually, over hundreds of
years, causes a slight shift in the results.</p>

<p>Now what have we learnt?</p>
<ul>
  <li>Assumptions about randomness depend on you using it correctly.</li>
  <li>Don’t only call your random function once.</li>
  <li>Don’t feed incrementing seeds into a LCG.</li>
  <li>And for crying out loud, <em>don’t use the same seed for two independent variables</em>.</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[How reasonable seeming code violated enough assumptions to completely nullify the RNG.]]></summary></entry></feed>