<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brian Hartnett</title>
    <description>The latest articles on DEV Community by Brian Hartnett (@bphdev).</description>
    <link>https://dev.to/bphdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4007049%2Fa3fdb120-6798-4873-a79f-d91f79d8f2a0.jpg</url>
      <title>DEV Community: Brian Hartnett</title>
      <link>https://dev.to/bphdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bphdev"/>
    <language>en</language>
    <item>
      <title>One Byte Tells Friend From Foe: Building a KH3 Trainer in Python</title>
      <dc:creator>Brian Hartnett</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/bphdev/one-byte-tells-friend-from-foe-building-a-kh3-trainer-in-python-5gi4</link>
      <guid>https://dev.to/bphdev/one-byte-tells-friend-from-foe-building-a-kh3-trainer-in-python-5gi4</guid>
      <description>&lt;p&gt;Being able to &lt;a href="https://www.bphdev.com/writing/kh3-trainer-code-injection" rel="noopener noreferrer"&gt;face-tank bosses in Kingdom Hearts 3&lt;/a&gt; is a great start, but in order to really practice these bosses efficiently I need to be able to advance phases faster. Doing that is going to take a damage multiplier cheat, which is my next target for the trainer. I had two main theories when considering my approach. The first: Sora has an attack stat that’s more than likely used for damage calculations, so wiring a multiplier here may work well. The second: previous work on my HP cheat gave me the read/write instruction for every NPC in the game. I could re-use my findings from the reverse engineering we did on HP and inject our multiplier there. &lt;/p&gt;

&lt;h1&gt;
  
  
  Targetting Attack
&lt;/h1&gt;

&lt;p&gt;I genuinely assumed writing Sora’s attack stat would be the easiest approach, as the value is easily accessible in the menu, and easy to shift around. So finding a candidate address in Cheat Engine wasn’t a problem at all. The real problem was, this address’ value shifted to nonsense after closing the menu. It was being used by the game temporarily to only display the attack stat. While I was able to change it in the menu, which would visibly reflect my changes, the game wasn’t re-writing this attack value back to wherever damage calculations live when the menu closed. Put more simply, this wasn’t as simple of an approach as I originally expected.&lt;/p&gt;

&lt;h1&gt;
  
  
  Shifting to the HP Write
&lt;/h1&gt;

&lt;p&gt;So instead of spending more time with Sora’s attack stat I shifted to reusing the work we already had. To freeze Sora’s HP we’re already hijacking the HP write location in-game memory, we’re just gating it on Sora’s vtable. So, if we just inverse the gate and match everyone who isn’t Sora we have a means to apply our multiplier. There’s a catch with this problem though. This’ll apply to all our buddies: Donald, Goofy, and any guest characters from the current world. It’s not detrimental, but the cheat isn’t “make everything that isn’t Sora get one shot,” it’s a damage multiplier, which most people recognize as increasing outgoing damage to enemies. So, I needed to do a bit more revving to find a stable way to separate enemy NPCs from friendly NPCs. &lt;/p&gt;

&lt;h1&gt;
  
  
  The Allegiance Gate
&lt;/h1&gt;

&lt;p&gt;Fortunately, games like Kingdom Hearts typically store this value somewhere in memory, in a boolean or similar. I already had a script, &lt;code&gt;identify.py,&lt;/code&gt; which read and captured all health writes and values that occurred while it was running. This was previously used to identify which actors were which in a game scene, so it was natural to extend it to identify a stable value we could use to determine NPC allegiance. &lt;/p&gt;

&lt;p&gt;The extension was simple: have the script output all actors with health values and vtables, and then feed the enemy and actor vtables back into the script. The script then ran a diff against each group, pruning anything that appeared on both an enemy and a friendly NPC, as well as all pointer, float, and handle values, because we ideally want a boolean. After a couple of runs I was able to identify &lt;code&gt;0x514&lt;/code&gt;, which read &lt;code&gt;0&lt;/code&gt; for allies and &lt;code&gt;0xFF&lt;/code&gt; for enemies, which acts as a clean &lt;code&gt;isHostile&lt;/code&gt; boolean for the multiplier. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Actor&lt;/th&gt;
&lt;th&gt;&lt;code&gt;0x514&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Multiplier applies?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sora (player)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Donald (ally)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goofy (ally)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shadow (enemy)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0xFF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;0x514&lt;/code&gt; acts as a stable gate to seperate enemies and allies. Friendlies read &lt;code&gt;0x00&lt;/code&gt; while enemies read &lt;code&gt;0xFF&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With a way to separate friendly NPCs from enemy NPCs, my next move was working on a test harness, &lt;code&gt;mult_test.py.&lt;/code&gt; The test first outputs health, vtables, and allegiance status so I could confirm that allegiance flags lined up with the actors on screen. After confirming, the script then enables the multiplier. To make it as obvious as possible, I set the multiplier to x40, and as you’d expect, enemies get one shot. Not only that, but gating seems to work. Donald and Goofy are immune to the multiplier. This was clear not because they survived (they almost never do on Critical), but because a small Shadow’s swipe attack’s damage remained small even with the multiplier enabled. Just to be certain guest characters didn’t break any of my assumptions, I stopped by Olympus and Toy Box to see how Hercules, Woody, and Buzz handled the multiplier, and they’re all immune to it as well. &lt;/p&gt;

&lt;h1&gt;
  
  
  The Catch
&lt;/h1&gt;

&lt;p&gt;And with that I have a working damage multiplier cheat. I’m able to one-shot small enemies and clear rooms in an instant, and I’m able to whittle boss health down lighting fast so I can practice different phases without wasting time, but there’s a major flaw with the approach I landed on. Both cheats use the same instruction, and both cheats inject their code at the same location. I accidentally made two cheats that stomp on each-others toes. Whichever one gets loaded first is the one that works, and the second politely says “hey boss, there’s something I don’t expect at our injection site.” The two cheats work independently, but the value is really when they’re both enabled at once (so I can just turn my brain off and hit a boss until it reaches the phase I want). Which really means these two cheats both still have some work that needs to be done, and I’ll have to spend some more time in the lab.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.bphdev.com/writing/kh3-trainer-actor-gating" rel="noopener noreferrer"&gt;bphdev.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>gamedev</category>
      <category>reverseengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>Behavior Determines the Resolution: Building a KH3 Trainer in Python</title>
      <dc:creator>Brian Hartnett</dc:creator>
      <pubDate>Tue, 07 Jul 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/bphdev/behavior-determines-the-resolution-building-a-kh3-trainer-in-python-2ec6</link>
      <guid>https://dev.to/bphdev/behavior-determines-the-resolution-building-a-kh3-trainer-in-python-2ec6</guid>
      <description>&lt;p&gt;With a finished &lt;a href="https://www.bphdev.com/writing/kh3-trainer-code-injection" rel="noopener noreferrer"&gt;Kingdom Hearts 3 HP Freeze Cheat&lt;/a&gt;, it’s time to circle back to Munny. Where I left off: filtering by an address-suffix scan that specifically looked for &lt;code&gt;(addr &amp;amp; 0xFFFF) == 0x8674&lt;/code&gt; on the address tail. The HP cheat made it clear this wasn’t a stable method and typically only worked on restarts. Any other transition would cause the game state to change and my filter would no longer match. However, Munny behaves differently than HP: its address tail is save-specific. This meant that warping or dying didn’t cause the Munny address tail to shift, but changing saves did. All in all, the Munny address tail is actually more stable than HP, but address-suffix filtering still isn’t a valid path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instruction hooks and their UX problem.
&lt;/h2&gt;

&lt;p&gt;Knowing for certain that filtering isn’t the solution, I shifted targets to write and access instructions. After poking around in Cheat Engine I discovered the transaction read operation, &lt;code&gt;mov eax,[rax+0x7E04]&lt;/code&gt;. This worked, but needed a purchase or sale to warm the hook before any read or write was possible, which is awful UX. I found a more easily accessible trigger, via the menu, where opening it would warm the hook, but I still don’t love this solution. I feel any user action to warm the hook is unnecessary and inconvenient, especially considering that WeMod’s cheat is perfectly capable of writing to Munny without any warming actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov eax,[rax+0x7E04]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Munny read, only fires when the game explicitly accesses the wallet. A user would need to buy or sell an item so it's a lousy hook.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How does WeMod do it?
&lt;/h2&gt;

&lt;p&gt;So I took some more time to think about how WeMod’s Munny cheat most likely works. They’re able to write to Munny eagerly, with no player action, which means it must resolve through a static chain into the wallet rather than waiting on the game to execute an instruction. So a static chain exists somewhere, but when I ran a CE pointer scan nothing surfaced. This led me to my next train of thought: why wasn’t my pointer scan able to find anything?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the scan found nothing
&lt;/h2&gt;

&lt;p&gt;The issue was that the CE scan targeted the Munny value address, not the wallet struct base, so the final +0x7E04 hop exceeded CE’s default max-offset threshold. Cheat Engine has a ceiling on how large each hop’s offset can be, and I was overshooting it, so the real chain was never in the search space. The fix: point the scan directly at the base (munny - 0x7E04) and let the terminal offset be the known +0x7E04.&lt;/p&gt;

&lt;h2&gt;
  
  
  Porting the scan to Python
&lt;/h2&gt;

&lt;p&gt;After getting a bit frustrated with Cheat Engine’s UI, I decided to port the scan over to a python script just to keep things where I’m more comfortable. I created &lt;code&gt;ptr_chain_scan.py&lt;/code&gt;, which is a two-phase script that first runs a scan that walks up from the live base through writable memory to any module-static root, saving each candidate on the way (as module, rva, and offsets). Phase two runs a verification step, which re-resolves every saved chain against the new base after a cold restart, keeping only the survivors.&lt;/p&gt;

&lt;p&gt;I started the scan with a &lt;code&gt;0x100&lt;/code&gt; up-window. This means the script only accepts parent relationships where the offset is less than or equal to &lt;code&gt;0x100&lt;/code&gt;, but this wasn’t a large enough search space to find anything useful. I bumped the window up to &lt;code&gt;0x1000&lt;/code&gt; which covers more ground while ensuring the number of results doesn’t explode. Despite the window increasing 16-fold, random memory usually doesn’t accidentally look like a valid pointer parent, so the number of valid results stayed manageable. One caveat: the scanner initially only targeted the root module &lt;code&gt;KINGDOM HEARTS III.exe&lt;/code&gt;. A static pointer chain could very well live inside the base exe, some runtime DLL, a CRT DLL, an engine DLL, or a middleware DLL. So instead of assuming the static root lived inside the main game executable, I let the scanner consider any loaded module as a possible stable anchor. &lt;/p&gt;

&lt;p&gt;Next, I needed to ensure the scanner wouldn’t run forever and output garbage. A pointer chain has depth. The deeper you let a scan go, the more possible parents it finds. At each layer, each candidate may have hundreds of possible parents, so as the depth gets larger, the number of possible candidates increases exponentially. There are two problems with this, and the obvious one is speed: a larger search space takes longer to go through. The bigger problem, however, is false positives. The deeper I go, the more likely I am to find coincidental chains. I don’t want the scanner to “win” by finding some absurdly long chain that only works incidentally, so I bound the search space to a &lt;code&gt;MAX_DEPTH&lt;/code&gt; of 2, which fits the kind of chain that actually makes sense for a stable game structure like a wallet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MODE&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;        &lt;span class="c1"&gt;# "scan", then after a cold restart "verify"
&lt;/span&gt;&lt;span class="n"&gt;VALUE_ADDR_HEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;117CCB18674&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# the VALUE address straight from CE. Update each run.
&lt;/span&gt;
&lt;span class="n"&gt;WINDOW&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x1000&lt;/span&gt;               &lt;span class="c1"&gt;# max offset per hop
&lt;/span&gt;&lt;span class="n"&gt;MAX_DEPTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Config constants that drive the search space. A larger WINDOW offset widens each hop and a larger MAX_DEPTH lengthens the chain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What survived the restarts
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;ptr_chain_scan.py&lt;/code&gt; complete, all that was left to do was to run it. After multiple rounds of cold restarts and verifying, I was left with three surviving chains, all rooted in &lt;code&gt;KINGDOM HEARTS III.exe&lt;/code&gt;, and all three landed on &lt;code&gt;+0x870 -&amp;gt; base -&amp;gt; +0x7E04&lt;/code&gt;. Two roots, &lt;code&gt;0x9DE3B68, 0x9DE3B98&lt;/code&gt;, sit &lt;code&gt;0x30&lt;/code&gt; apart which leads me to believe these are adjacent fields in one global. The third, &lt;code&gt;0x9DE7378&lt;/code&gt;, is a separate global reaching the same struct through some other route. All three chains survived three more cold restarts, which likely means these are real aliases into the same player/save structure. &lt;/p&gt;

&lt;p&gt;With three survivors, I decided the most robust solution would be to use all three aliases deliberately, rather than culling to one. If a future build shifts one global, the other two still resolve and the disagreement is detectable rather than silently ignored. The cheat resolves with pure reads on attach, there’s no hook, no cave, no warm. No action the user needs to take for the tool to work. A quick smoke test confirmed that all three agreed on the base, &lt;code&gt;+0x7E04&lt;/code&gt; held perfectly, and a reversible write landed on the authoritative field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[+] attached — module 0x7FF6D63D0000
    (in-world, nothing opened — testing eager resolution)
[1] eager resolve: OK
[2] per-chain bases:
    ["KINGDOM HEARTS III.exe"+0x9DE7378] +0x158 +0x870  -&amp;gt;  0x117CCB10870
    ["KINGDOM HEARTS III.exe"+0x9DE3B68] +0x60 +0x870  -&amp;gt;  0x117CCB10870
    ["KINGDOM HEARTS III.exe"+0x9DE3B98] +0xE30 +0x870  -&amp;gt;  0x117CCB10870
    consensus base = 0x117CCB10870   [all agree]
[3] munny @ 0x117CCB18674 = 16394
    addr - base = 0x7E04 (expect 0x7E04)
    -&amp;gt; does 16394 match your on-screen munny? OK
[4] run reversible write test (+7 then restore)? (y/N): y
    16394 -&amp;gt; wrote 16401 -&amp;gt; read 16401  [WRITE OK]
    restored -&amp;gt; 16394  [restored OK]
[+] smoke test done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Smoke-test output that successfully resolves all three chains to one shared base and triggers a test read and write to ensure the cheat works end to end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This little adventure showed me something I’ll be sure to take forward: the right resolution strategy should be determined by how the value behaves in memory. ExecHook capture for values that relocate mid-session (like HP), and static-pointer chains for session-stable values (like Munny). So on top of being able to face-tank any enemy attack, I’m now the richest person in the Kingdom Hearts universe too.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.bphdev.com/writing/kh3-trainer-pointer-scanning" rel="noopener noreferrer"&gt;bphdev.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>gamedev</category>
      <category>reverseengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Data Moves, The Code Doesn't: Building a KH3 Trainer in Python</title>
      <dc:creator>Brian Hartnett</dc:creator>
      <pubDate>Tue, 30 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/bphdev/the-data-moves-the-code-doesnt-building-a-kh3-trainer-in-python-11hj</link>
      <guid>https://dev.to/bphdev/the-data-moves-the-code-doesnt-building-a-kh3-trainer-in-python-11hj</guid>
      <description>&lt;p&gt;I recently started tinkering with making cheats for video games, but before you get mad at me, I don’t mean scumbag cheats, I mean trainers for singleplayer games. No multiplayer, no online, no leaderboards, and no ruining anyone else’s day. Reason being: I’m fighting the most unfair bosses in maybe the entire Kingdom Hearts franchise, the KH3 Data Org bosses (on Critical Mode, with max PRO codes), and I am losing my mind fighting already-mastered phases, just to see 3 seconds of the next phase &lt;em&gt;just&lt;/em&gt; to be one-shot. I need a more efficient way to practice: invincibility so I can spend more time seeing unlearned phases, or else I’ll spend the rest of my summer on like 13 bosses, and I say no. I did find software that'll do this for me, but I'm not a big fan of the UI, they want to charge me, and honestly this stuff is just fun to me anyway, so I’d rather see what I can do on my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chasing the Data
&lt;/h2&gt;

&lt;p&gt;I started with Munny, Kingdom Hearts’ in-game currency. I figured this would be an easier starting point than HP, and after a first attempt it seemed I was right. I scanned for a byte signature to find candidate addresses, and noticed something convenient: the real Munny address always ended in the same 16 bits across restarts. I used that to relocate the value and write to Munny even after cold-restarting, which seemed to be a valid solution. In hindsight I was definitely wrong. There’s a lot more going on under the hood of this game.&lt;/p&gt;

&lt;p&gt;After wrongfully calling Munny complete, I moved on to HP, where I noticed a similar pattern. HP's address also ended in the same 16 bits across restarts. My initial solution was the same as before: target that signature, isolate it by the address tail, and write. This worked, but only in the instance of a cold restart. Die, load a different save, warp, or do anything else to change the game’s state and poof: the struct gets reallocated and the address tail I was keying on is gone. I no longer have a matching candidate, and worse, this will definitely write values to some random part of the game that could cause a crash. Considering the solution for Munny is relying on the same logic, it’s safe to assume that’s borked and future me has a new chore, but regardless, we continue.&lt;/p&gt;

&lt;p&gt;From here, I added a value filter, basically filtering out candidates to only pick values that make sense as HP. This looked like it did the trick on the first save I tried. I had just one value that was definitely HP. But then I loaded a save with a food buff, and saw two valid values for HP. One with the food buff, and one without. Both are valid HP values, so they’ll pass any filter I write. With no obvious way to distinguish the two, this path was dead. Next, I tried using an array-of-bytes signature to identify the real HP. Look at the byte pattern around the HP, and find a consistent fingerprint we can use to identify the real value. Guess what? This matched a look-alike. At this point it felt personal: two methods to filter out candidates both leaving me with a phantom second match. As a last-ditch effort I cracked open Cheat Engine to see if a pointer scan backed up what I was experiencing. After multiple attempts at culling, the scan still returned around 1000 matches. Running it across restarts and reloads to eliminate false values was doing virtually nothing. The scanner effectively proved there’s not a single stable pointer chain to be found. This is because UE4 reallocates objects dynamically, a fun engine quirk that makes my life more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hijacking the Write
&lt;/h2&gt;

&lt;p&gt;At this point, certain the game had no stable data path for me to access HP, I decided to pivot. The data moves, but y’know what doesn’t? The code. Instead of focusing on where the data’s being stored, I decided to look at what’s writing to it. I grabbed the live HP address in Cheat Engine and scoped out the situation. What I found was something like &lt;code&gt;mov [rbx+offset], value&lt;/code&gt;, and I made that the new target. &lt;code&gt;rbx&lt;/code&gt; is holding the base of the live HP struct, so wherever the game just moved it, this instruction is pointing right at it. I’d overwrite the instruction at its fixed code site, bounce it out to my own code written somewhere in the game’s free memory, and then send it back from whence it came. This approach seemed great until I realized I’m looking at &lt;em&gt;every single character’s HP write&lt;/em&gt;. That means enemies, Sora, party members, and whatever else has a health bar, so hijacking just based off of the instruction would cause everything to be invincible, completely defeating the purpose of the tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov [rbx+0x5FC], r8d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The HP store instruction. rbx holds the live struct base, so wherever the game just moved Sora, this points right at it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Isolating Sora
&lt;/h2&gt;

&lt;p&gt;Naturally, my next target was finding some way to consistently identify Sora and ensure we only hijack his HP write. This is where C++ does me a solid. Every actor’s struct starts with a pointer to a vtable, a table of function pointers that every object of the same class shares. Put simply, this’ll do exactly what we need it to: serve as a stable way to identify writes to only Sora’s HP. By setting up a watchdog on the HP write instruction, I was able to identify Sora’s vtable value, &lt;code&gt;0x696B930&lt;/code&gt;, and use it to set up a filter that ensures the injected code only runs when Sora is the target for the HP write. This ended up being the correct solution. Across all transitions, I was able to consistently target Sora’s HP. However, this was done in Cheat Engine, and I’d really like a Python port.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cmp dword [rbx], 0x696B930   ; is this Sora's vtable?
jne  do_store                ; not Sora - let the write through untouched
; Sora-only code runs here
do_store:
mov  [rbx+0x5FC], r8d        ; original instruction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The vtable pointer at [rbx] is the same for every Sora and different for everything else. Gate on it and only our code — between the branch and the store — ever sees him.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Porting to Python
&lt;/h2&gt;

&lt;p&gt;Porting the Cheat Engine AOB injection over to Python was a bit complicated, as Cheat Engine handles a lot of the complex stuff out of the box. Specifically, I needed to recreate Cheat Engine’s cave allocation and trampolining. In other words, I needed to overwrite an instruction in the game and tell it to go somewhere else to run my code (the trampoline jump). This code needs to be within 2GB of the location I overwrote, so I needed to find an open spot in memory within this range (the cave). Lastly, after the game runs my injected code, it has to return right back to where it started (falling back down from the trampoline jump). This ended up being a majority of the work, but it laid some solid, reusable groundwork for future cheats and games.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;disp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cave_addr&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hook_addr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mh"&gt;0x80000000&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;disp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0x7FFFFFFF&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cave out of rel32 range - allocate nearer or jump absolute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;jmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\xE9&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;disp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;little&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A jmp rel32 encodes the distance as a signed 32-bit offset from the end of the jump. Land your cave more than 2GB out and it doesn't fit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Writing as the Game, Not After It
&lt;/h2&gt;

&lt;p&gt;Finally, I started working on freezing the HP value. I started with a naive implementation using polling, which technically leaves room for a fast enough combo to still kill you. When fighting bosses that are known to do twenty-eight-move combos in 3-second windows it’s probably best to not leave margin for error. So I shifted from a polling strategy to co-opting the HP write. My cave already runs at the game’s own HP-store instruction, so instead of correcting HP after the game's already lowered it, the cave just overwrites the value the game is about to store to 9999. The low value never lands, and there’s no window for Sora’s death because we don’t write after the game, we write as the game. I’m now successfully facetanking Data Org bosses on Crit Mode with all PRO codes enabled, and I’ll have every single one of these phases mastered well before the end of the summer. PRO Code merit rank A, here we come.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.bphdev.com/writing/kh3-trainer-code-injection" rel="noopener noreferrer"&gt;bphdev.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>gamedev</category>
      <category>cpp</category>
      <category>reverseengineering</category>
    </item>
  </channel>
</rss>
