<?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://darkatra.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://darkatra.dev/" rel="alternate" type="text/html" /><updated>2026-07-03T16:54:27+00:00</updated><id>https://darkatra.dev/feed.xml</id><title type="html">darkatra.dev</title><author><name>Tommy Schmidt</name><email>darkatra@gmail.com</email></author><entry><title type="html">Sage Engine INI File Language Support for IntelliJ IDEA</title><link href="https://darkatra.dev/2026/06/27/jetbrains-idea-plugin-for-sage-ini-files.html" rel="alternate" type="text/html" title="Sage Engine INI File Language Support for IntelliJ IDEA" /><published>2026-06-27T00:00:00+00:00</published><updated>2026-06-27T00:00:00+00:00</updated><id>https://darkatra.dev/2026/06/27/jetbrains-idea-plugin-for-sage-ini-files</id><content type="html" xml:base="https://darkatra.dev/2026/06/27/jetbrains-idea-plugin-for-sage-ini-files.html"><![CDATA[<p>It’s been a while since I last posted here, and looking back, I’m surprised by how quickly the time has passed. Since then, I’ve been working on a few
BfME-related projects, but my most recent one felt worth writing about. So here we are: this post is about a plugin for
<a href="https://www.jetbrains.com/idea/">IntelliJ IDEA</a> that adds syntax highlighting, code formatting, completion, and navigation features for Sage Engine INI files.</p>

<p>The plugin is called <a href="https://github.com/DarkAtra/bfme2-idea-plugin">Sage Engine INI</a>, and while I mostly built it with
<a href="https://en.wikipedia.org/wiki/The_Lord_of_the_Rings:_The_Battle_for_Middle-earth_II">The Lord of the Rings: The Battle for Middle-earth II</a> in mind, it
probably works for other Sage Engine games as well.</p>

<h2 id="motivation">Motivation</h2>

<p>IntelliJ IDEA has been my primary IDE for years, and I’ve spent countless hours refining my keybindings and customizing it so that the whole setup feels like my
own. Whenever I worked on BFME-related projects, though, I found myself switching to a plain text editor instead. Without proper language support for Sage
Engine INI files, most of IntelliJ’s advanced features simply weren’t useful, and the IDE felt unnecessarily heavy compared to lightweight editors like VS Code
or Notepad++. Yet those editors weren’t the ideal solution either. What I really wanted was an IntelliJ plugin that brought first-class support for Sage Engine
INI files, complete with syntax highlighting, code navigation, and - most importantly - a formatter. A few weeks ago, I finally decided it was time to build
exactly that.</p>

<h2 id="use-of-ai">Use of AI</h2>

<p>Before looking at the features, it’s worth mentioning that this project was intentionally built with the assistance of AI. One of the goals was to better
understand where AI is genuinely useful and where its limitations become apparent. Rather than attempting to fully understand IntelliJ’s Platform API upfront, I
used AI to build the initial version of the plugin, explore unfamiliar extension points, generate tests, and refactor repetitive code.</p>

<p>This also influenced how I approached code quality. Instead of reviewing every change line by line, I took a more pragmatic approach and focused on whether the
implementation behaved correctly, primarily through automated tests. As a result, the current codebase is not polished and does not meet my usual standards for
production-quality code. At this point, it’s better viewed as a proof-of-concept.</p>

<h2 id="the-features">The features</h2>

<p>The project is still in its early stages, and there’s plenty of room for improvement, but it already includes a number of features that make working with Sage
Engine INI files much more convenient. These include syntax highlighting, code formatting for files such as <code class="language-plaintext highlighter-rouge">armor.ini</code>, <code class="language-plaintext highlighter-rouge">upgrade.ini</code>, <code class="language-plaintext highlighter-rouge">weapon.ini</code>, and object
definition files, code folding, as well as navigation and autocompletion for <code class="language-plaintext highlighter-rouge">#include</code> directives.</p>

<p>Let’s take a closer look at some of the most notable features.</p>

<h3 id="syntax-highlighting">Syntax Highlighting</h3>

<p><a href="/assets/bfme2-ini-syntax-highlighting.png"><img src="/assets/bfme2-ini-syntax-highlighting.png" alt="A screenshot of syntax highlighting" /></a></p>

<h3 id="formatting">Formatting</h3>

<p>This is probably my favorite feature. There’s something incredibly satisfying about pressing a single button and watching the entire file snap into a clean,
consistent format. It might not seem particularly exciting at first glance, but the formatter already handles a surprising number of small details that make
Sage Engine INI files much easier to read and maintain. Among other things, it:</p>

<ul>
  <li>indents most blocks consistently</li>
  <li>aligns property values for improved readability</li>
  <li>removes unnecessary whitespace throughout the file, including inside comments</li>
  <li>strips redundant empty comments at the end of a line</li>
  <li>replaces tabs with spaces (although support for tabs might be added in the future for all the tab enjoyers)</li>
</ul>

<p>Here’s a side-by-side comparison showing the changes for a section of <code class="language-plaintext highlighter-rouge">aragorn.ini</code>:</p>

<div style="display: flex; gap: 1rem;">
  <div>
    <h5 style="margin-top: 0; margin-bottom: .5rem;">Before</h5>
    <a href="/assets/bfme2-aragorn-before-formatting.png" target="_blank">
      <img alt="A screenshot of Aragorn code before formatting" src="/assets/bfme2-aragorn-before-formatting.png" />
    </a>
  </div>
  <div>
    <h5 style="margin-top: 0; margin-bottom: .5rem;">After</h5>
    <a href="/assets/bfme2-aragorn-after-formatting.png" target="_blank">
      <img alt="A screenshot of Aragorn code after formatting" src="/assets/bfme2-aragorn-after-formatting.png" />
    </a>
  </div>
</div>

<h3 id="include-navigation-and-completion">Include Navigation and Completion</h3>

<p>Another useful quality-of-life improvement is support for <code class="language-plaintext highlighter-rouge">#include</code> macros. When an INI file contains an <code class="language-plaintext highlighter-rouge">#include</code> macro such as:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#include "data/ini/object/goodfaction/units/men/aragorn.inc"
</span></code></pre></div></div>

<p>the plugin can resolve the referenced file, jump to it via the usual “go to declaration” action, and offer path completion based on the current file’s
directory.</p>

<h2 id="whats-next">What’s Next?</h2>

<p>There’s still a lot of room for improvement. Some of the next ideas on the roadmap include richer inspections, smarter completion for known property values, and
a deeper semantic understanding of the underlying game data.</p>

<p>For example, navigation support could be extended further to support jumping to related game definitions for armors, weapons, and special powers. On top
of that, additional inspection rules could catch invalid or misspelled values. The parser itself also still has room to evolve, especially as more real-world
INI files reveal edge cases and inconsistencies that need to be handled more robustly.</p>

<p>For now, though, I’m already happy with the result. Editing Sage Engine INI files in IntelliJ IDEA feels much nicer than before, and that was exactly the goal.</p>

<p>If this blog post sparked your interest, feel free to try it out - the <a href="https://github.com/DarkAtra/bfme2-idea-plugin">source code is available on GitHub</a>.</p>

<iframe width="245px" height="48px" style="border: none" src="https://plugins.jetbrains.com/embeddable/install/32514"></iframe>]]></content><author><name>Tommy Schmidt</name><email>darkatra@gmail.com</email></author><summary type="html"><![CDATA[It’s been a while since I last posted here, and looking back, I’m surprised by how quickly the time has passed. Since then, I’ve been working on a few BfME-related projects, but my most recent one felt worth writing about. So here we are: this post is about a plugin for IntelliJ IDEA that adds syntax highlighting, code formatting, completion, and navigation features for Sage Engine INI files.]]></summary></entry><entry><title type="html">The magic behind restoring multiplayer for Battle for Middle-Earth 2 - Part 1</title><link href="https://darkatra.dev/2023/10/28/fixing-online-gameplay-for-bfme2.html" rel="alternate" type="text/html" title="The magic behind restoring multiplayer for Battle for Middle-Earth 2 - Part 1" /><published>2023-10-28T00:00:00+00:00</published><updated>2023-10-28T00:00:00+00:00</updated><id>https://darkatra.dev/2023/10/28/fixing-online-gameplay-for-bfme2</id><content type="html" xml:base="https://darkatra.dev/2023/10/28/fixing-online-gameplay-for-bfme2.html"><![CDATA[<p><a href="https://en.wikipedia.org/wiki/The_Lord_of_the_Rings:_The_Battle_for_Middle-earth_II">The Lord of the Rings, The Battle for Middle Earth II</a> is one of many
real-time strategy games released in 2006, yet it’s something special, for me at least.
Maybe because it’s set in the world of JRR Tolkien or perhaps just because it reminds me of my childhood.
Either way, it’s one of the few games that I can motivate myself to write software for.</p>

<h2 id="the-issue-with-online-gameplay">The issue with online gameplay</h2>

<p>If you’ve ever tried to play the game in multiplayer, you’ve probably noticed that <strong>EA has shut down the online servers</strong>.
While the community has found many ways to continue playing against each other, for example via <a href="https://en.wikipedia.org/wiki/GameRanger">GameRanger</a>,
only <a href="https://t3aonline.net">T3A:Online</a> has managed to restore the game’s original online feature.
They achieved this by building their own GameSpy servers and then modified the game to use these instead of the original ones.</p>

<p>As a software developer, I have always wondered how exactly stuff like this works. To my surprise, the source code was not available.
Neither for the t3a online servers, nor for the client side modifications to the game.
The reasoning seemed to be simple: they didn’t want the community to fragment any further.
The risk of introducing additional servers that could compete with T3A:Online seemed quite high.
Obviously this was quite disappointing for me as I had no reference to learn from.
But there is another problem with that: the service could simply die if the maintainers lose interest.
Not to mention that it also hinders others in the community from contributing bugfixes and features.</p>

<h2 id="the-solution">The solution</h2>

<p>A few months ago I stumbled upon <a href="https://github.com/chc/openspy-core-v2">OpenSpy, an open source project</a> that aims to restore the online functionality for
GameSpy based games. Unfortunately, The Battle for Middle Earth II was not yet supported.
Well, let’s <a href="https://github.com/anzz1/openspy-client/issues/3">attempt to change that</a>.</p>

<h3 id="the-client-side">The client side</h3>

<p>It’s easier to start with the client side as it’s fairly simple to verify by just testing if a connection to the existing T3A:Online servers can be established.
By looking at
how <a href="https://github.com/anzz1/openspy-client/blob/f18d410fc0cfe2e69ec32e93f088209527093749/include/game_cry.h#L90-L95">the online functionality for other games is restored</a>,
I assumed that there’s only two things to implement:</p>

<ol>
  <li>a hook for the <code class="language-plaintext highlighter-rouge">gethostbyname</code> function to redirect GameSpy/EA specific DNS queries</li>
  <li>a patch so that the game skips the certificate validation when it connects to the online servers</li>
</ol>

<p>I found the hosts that I had to redirect DNS queries for by looking at the <code class="language-plaintext highlighter-rouge">t3aonline.dll</code> with <a href="https://github.com/NationalSecurityAgency/ghidra">Ghidra</a>,
a software reverse engineering framework created and maintained by the NSA Research Directorate.</p>

<p><a href="/assets/bfme2-ghidra-t3a-online.png"><img src="/assets/bfme2-ghidra-t3a-online.png" alt="A screenshot of the decompiled t3aonline.dll" /></a></p>

<p>The screenshot above only shows a few lines of the decompiled function, and it was a real mess to untangle all the nested if statements and loops but here is
what it looks like:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">hostent</span> <span class="o">*</span><span class="n">WSAAPI</span> <span class="nf">Hooked_gethostbyname</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"gpcm.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"gpcm.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"peerchat.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"peerchat.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"psweb.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"arenasdk.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbfme.arenasdk.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"ingamead.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"master.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"master.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme.available.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"master.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme.master.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"master.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme.ms13.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"master.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme2r.available.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"master.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme2r.master.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"master.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme2r.ms9.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"master.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"gamestats.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"gamestats2.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme.gamestats.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"gamestats2.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme2r.gamestats.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"gamestats2.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"lotrbme2wk.gamestats.gamespy.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"gamestats2.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"servserv.generals.ea.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"http.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"na.llnet.eadownloads.ea.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"http.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"bfme.fesl.ea.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"login.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"bfme2.fesl.ea.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"login.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="s">"bfme2-ep1-pc.fesl.ea.com"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="s">"login.server.cnc-online.net"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">gethostbyname</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I quickly added the EasyHook hooking code and then validated that my dll was indeed redirecting dns queries
using <a href="https://gitlab.com/wireshark/wireshark/-/tree/master">wireshark</a>, another great open source tool that allows you to inspect network traffic.</p>

<p><a href="/assets/bfme2-wireshark-dns-capturepng.png"><img src="/assets/bfme2-wireshark-dns-capturepng.png" alt="A screenshot of wireshark showing successfully redirected dns queries" /></a></p>

<p>At this point, I was able to view the login mask in game, but it didn’t let me connect to the servers as the certificate was still considered invalid.
After searching for a fix for a few days, I accidentally stumbled across <a href="https://github.com/Aim4kill/Bug_OldProtoSSL">this GitHub repository</a> which
demonstrates a bug in EA’s certificate validation based on <a href="https://github.com/xebecnan/EAWebkit">the source code for EA’s webkit</a> used in Need for Speed World.
I managed to locate the exact same bug in The Battle for Middle Earth II.</p>

<p><a href="/assets/bfme2-ghidra-cert-validation-bug.png"><img src="/assets/bfme2-ghidra-cert-validation-bug.png" alt="A screenshot of the certificate bug in battle for middle earth 2" /></a></p>

<p>I looked where I could insert a jump instruction so that the game would always think the certificate had an unknown signature.
The easiest way I’ve found was to insert it in line 228, as shown in the following screenshot:</p>

<p><a href="/assets/bfme2-ghidra-cert-validation-bug-jump-instruction.png"><img src="/assets/bfme2-ghidra-cert-validation-bug-jump-instruction.png" alt="A screenshot of the location of the jump instruction to abuse the certificate bug in battle for middle earth 2" /></a></p>

<p>Using <a href="https://github.com/cheat-engine/cheat-engine">CheatEngine</a> I was able to quickly validate that my change would work.
I attached myself to the game process and then jumped to <code class="language-plaintext highlighter-rouge">game.dat+00a8d096</code>, which is the address of the if statement on line 228.
Then I changed the statement so that it jumps to <code class="language-plaintext highlighter-rouge">00A8D0DE</code>, which is line 237. I was now able to successfully log in to the T3A:Online servers.</p>

<p>Now all that was left was to write code that would do all of this automatically. This is what it looks like:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">HANDLE</span> <span class="n">currentProcess</span> <span class="o">=</span> <span class="n">GetCurrentProcess</span><span class="p">();</span>

<span class="c1">// address    bytes           assembly instruction</span>
<span class="c1">// 00a8d093   89 55 88        MOV        dword ptr [EBP + local_7c],EDX</span>
<span class="c1">// 00a8d096   83 7d 88 08     CMP        dword ptr [EBP + local_7c],0x8</span>
<span class="c1">// 00a8d09a   74 08           JZ         LAB_00a8d0a4</span>
<span class="n">BYTE</span> <span class="n">search</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0x55</span><span class="p">,</span> <span class="mh">0x88</span><span class="p">,</span> <span class="mh">0x83</span><span class="p">,</span> <span class="mh">0x7D</span><span class="p">,</span> <span class="mh">0x88</span><span class="p">,</span> <span class="mh">0x08</span><span class="p">,</span> <span class="mh">0x74</span><span class="p">,</span> <span class="mh">0x08</span> <span class="p">};</span>

<span class="c1">// jmp 00A8D0DE</span>
<span class="n">BYTE</span> <span class="n">patch</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span> <span class="mh">0xEB</span><span class="p">,</span> <span class="mh">0x46</span><span class="p">,</span> <span class="mh">0x90</span><span class="p">,</span> <span class="mh">0x90</span> <span class="p">};</span>

<span class="n">BYTE</span><span class="o">*</span> <span class="n">addressToModify</span> <span class="o">=</span> <span class="n">findPatternInProcessMemory</span><span class="p">(</span><span class="n">search</span><span class="p">,</span> <span class="n">search</span> <span class="o">+</span> <span class="mi">8</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">addressToModify</span><span class="p">)</span> <span class="p">{</span>

    <span class="n">SIZE_T</span> <span class="n">bytesWritten</span><span class="p">;</span>
    <span class="kt">bool</span> <span class="n">certPatchSuccessful</span> <span class="o">=</span> <span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">currentProcess</span><span class="p">,</span> <span class="n">addressToModify</span> <span class="o">+</span> <span class="mi">3</span><span class="p">,</span> <span class="n">patch</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">patch</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">bytesWritten</span><span class="p">);</span>
    <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">certPatchSuccessful</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">MessageBoxW</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="s">L"Failed to patch certificate."</span><span class="p">,</span> <span class="s">L"Error"</span><span class="p">,</span> <span class="n">MB_OK</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The full version of the
code <a href="https://github.com/DarkAtra/bfme2-patcher/blob/daf27730f295be06b931995545b0c1738dd15ec3/game-patcher/src/main/cpp/dllmain.cpp">can be found here</a>.</p>

<p>Here’s a video of the final result:</p>

<video controls="" style="max-width: 100%;">
    <source src="/assets/bfme2-online-video.mp4" type="video/mp4" />
</video>

<h3 id="the-server-side">The server side</h3>

<p>I haven’t managed to connect to the OpenSpy servers yet, but I’ll post a part 2 as soon as I find something new.</p>]]></content><author><name>Tommy Schmidt</name><email>darkatra@gmail.com</email></author><summary type="html"><![CDATA[The Lord of the Rings, The Battle for Middle Earth II is one of many real-time strategy games released in 2006, yet it’s something special, for me at least. Maybe because it’s set in the world of JRR Tolkien or perhaps just because it reminds me of my childhood. Either way, it’s one of the few games that I can motivate myself to write software for.]]></summary></entry></feed>