Okay, in the last post I was able to dump Citra’s emulated game memory. Now how am I going to poke around in Rune Factory 4 (Undub)’s memory dump?

Recap

Just as a quick refresher, the reason we can’t just poke around easily with Cheat Engine is due to memory fragmentation. Data beyond a single page in memory might be in a very different location compared to everything else.

I want to play with IPC scripting as one of the main goals, so I need to know exactly where things are in memory in relation to the emulated console’s memory addresses.

There might a way to chase pointers to find the correct address in memory, but that’s frankly just beyond me. Remember we are interested specifically in the emulated device’s memory, not the memory of Citra’s emulator process. Because of this indirection I have my doubts on it being a simple problem to solve directly inside of just Cheat Engine itself.

So in the end I just hacked together a function inside of Citra to dump me everything in Emulated memory’s process as one giant 511MB file (which we compress to a manageable size thanks to Windows OS compression).

Cheat Engine

Cheat Engine allows you to find changes in a process’ memory by comparing the state of old memory values with new ones. The details on how to use Cheat Engine for this is a little out of the scope of this article (and I’m no expert at using it either), however one neat thing is it can load a binary file instead of just processes’ memory. It works just how you would expect however you need to load a new memory dump for every search you want to preform.

So let’s load our memory dump and search for something easy, like Gold. Gold should be at least a 4 byte value, as you might guess from looking at the price of this house for sale on Autumn road. Yikes.

Cheap House

So on a fresh game, we start a new search, and after buying a single turnip and only doing a single comparison search it narrows the possible memory addresses to just five results.

Our first search

That’s pretty good, if we did a memory scan of the emulator process it would normally return pages of results by comparison.

What is great, is the address listed from the memory dumps here also have no offsets, meaning we can use these addresses as is directly in our Citra python scripts, which is my end goal anyway.

Cheat Engine Scripting

Now the only real problem right now is the process is rather slow due to all the steps: run a python script to make a memory dump, deal with the open file dialog in Cheat Engine, and then perform a new search on the value we want to find.

Thankfully Cheat Engine has LUA scripting. Which can be found under the menu option Table > Show Cheat Table LUA Script.

If we want to open a binary file into Cheat Engine via LUA, the function we want is function openFileAsProcess(fileName, is64bit).

So we can do something like this:

local StatesDirectory = os.getenv('APPDATA') .. [[\Citra\states\]]

FileName = "1613770908.mem"
openFileAsProcess(StatesDirectory .. FileName, 0)

And obviously we need the latest memory dump. So let’s expand this. To get this particular file’s file name I can use the LuaFileSystem extension.

local lfs = require( "lfs" )

function GetFileExtension(FileName)
  return FileName:match("^.+(%..+)$")
end

function GetLatestFile( Path, Ext )
  local LatestFile = nil
  local LatestTime = 0
  for f in lfs.dir( Path ) do
    local Attributes = lfs.attributes( Path .. f )
    if Attributes.mode == "file" and GetFileExtension(f) == Ext then
      if Attributes.modification > LatestTime then
        LatestTime = Attributes.modification
        LatestFile = f
      end
    end
  end
  return LatestFile
end

I also want to call my Citra Memory Dump function. I could port the python code over to Lua but I already have everything in python so an os call to the python script is the quickest solution. (I honestly barely understand the networking code inside of Python, so I’d be just as lost in Lua.)

local ScriptPath = [[D:\Projects\Citra Scripting\]]

os.execute('"' .. ScriptPath .. "MemDump.py" .. '"')

This quick solution does unfortunately cause a terminal window to pop up for a second on Windows. I’ll live with this, I don’t see any point in worrying about this right now, if ever.

There is one last bonus feature I want to write. While the memory dumps are relatively small thanks to Windows’ Compression, we don’t need to keep these old memory dumps around for long. A simple purge function would be nice plus.

To make a purge function I can refactor my GetLatestFile function and sort the lfs results by time. Then simply remove the oldest memory dumps.

function PurgeOld( Path, Ext, KeepCount )
  local Files = {}

  for f in lfs.dir( Path ) do
    local Attributes = lfs.attributes( Path .. f )
    if Attributes.mode == "file" and GetFileExtension(f) == Ext then
      table.insert(Files, {time = Attributes.modification, filename = f})
    end
  end

  table.sort(Files, function(a,b) return a.time > b.time end)

  for i, v in ipairs(Files) do
      if i > KeepCount then
         os.remove(Path .. v.filename)
      end
  end
end

Now to create and load a new memory dump (function declarations aside):

local StatesPath = os.getenv('APPDATA') .. [[\Citra\states\]]
local ScriptPath = [[D:\Projects\Citra Scripting\]]

os.execute('"' .. ScriptPath .. "MemDump.py" .. '"')
FileName = GetLatestFile( StatesPath, ".mem" )
openFileAsProcess(StatesPath .. FileName, 0)
PurgeOld(StatesPath, ".mem", 5)

Now I just need to click LUA Script window’s “Execute script” button every time I want a new Memory Dump. I would love to bind this script to the Scan button, but there doesn’t appear to be any hooks in Cheat Engine for this functionality. I’d probably have to rewrite the gui or make my own lua form to save a click. But like the terminal window popup I’ll manage, we are already saving a ton of time now. So it’s not worth the effort to figure out these trivial annoyances.

Back to where we were, we can now narrow down the gold results from above. Which was very simple, after leaving the shop and updating refreshing the memory once, I was able to reduce my options for gold addresses down to just 0x00733944.

I can now access this value in Python like this:

c = MyCitra()

Gold = c.read_value(0x00733944, 4)

print(f"Gold: {Gold}")
# Gold: 190

And obviously we can cheat our Gold value as well:

c = MyCitra()

Gold = c.write_value(0x00733944, 999999999, 4)

I’m Rich!

Awesome, everything works. I consider this all the bare minimumn functionality to start poking around.

I will note that there probably should be some error checking on the network connection, but the provided Citra.is_connected() function doesn’t appear to do much aside from see if a socket object was created. The real network error handling probably should be done whenever socket.sendto() is run, ya know, when the script actually tries to use a network connection. Alas, least of my worries right now. And I’m not sure how best to solve this in python anyway.

There is still some functionality that would be nice to add back to Cheat Engine, namely making the address list a little more useful. Which I hope to look into in the next post.