Memory Manipulation on Windows - nanpuhaha/SerpentAI GitHub Wiki
Included in this fork of SerpentAI (currently in #6) is the ability to directly access the memory of your game, greatly increasing the flexibility of the data you can use and possibly even allowing you to train a model with no visual information at all.
Basic Memory Manipulation
An Analogy
To help you understand what our goal here is, imagine you’re a pirate searching for buried treasure (your data) on an island (the game). You arrive and use all your fancy tools to find its location easily, but there’s too much to bring back with you! You’re forced to jot down its location (the memory address) relative to your ship and go home (closing the game).
When you come back, though, your ship is in a different spot and the notes you made before are useless. After finding the treasure again, you now notice that it’s a little north of a massive discarded anchor (the game’s base address) that can be seen from anywhere on the island. You update your instructions to be relative to the anchor, and can now come back to the treasure whenever you want.
Finding the Address
So, in order to use values in your game’s memory, first you need to find the location of those values — for this, your set of tools will be Cheat Engine. It is recommended you complete the basic interactive tutorial included in CE up to at least step 4.
When you’re ready to try it on the game, click on the button here and open your desired program:
It is also recommended that you double click on the writable box until it shows a square in order to have CE search all addresses and set your value type to ‘All’ so you don’t have to manually check all the different types, at least until you know which your game tends to use. (Note that Cheat Engine's All does not include strings, so you must select that manually.)
Now, you’re ready to get started! Change the scan type to match what you’re looking for: if you know the specific value, enter that, otherwise you can just start with “Unknown initial value”. Then just start changing the value in the game (while occasionally keeping it the same and changing other values instead) and re-scanning in CE with the various scan types, depending on how much information you have available.
Eventually, the addresses should narrow down until you find the one you’re looking for. Double click on it to add it to the address list, and if you want to be sure you got the right one (or you got multiple), you can try changing their values to see what happens in the game. If it also changes there, congratulations, though if it doesn’t that’s fine as long as you don’t need to write to it.
However, you might not be done yet. If the address shows up as “[game name].exe+FFFFFF” in the search list or when you double click on it, you’re good! That means Cheat Engine automatically found its relative location to the game’s anchor, the base address, and you can skip to the implementation section.
Otherwise, the address will change every time the game restarts, or sometimes even while the game is open. That means it’s time for our pirate analogy to get a little more complicated…
Memory Pointers
A More Complicated Analogy
Later, you begin a hunt for treasure on a different island. However, this one is enchanted with strange magic: each time you leave, the chest moves to a completely different spot!
However, the treasure chest you’re looking for isn’t the only one on the island. You once again use your tools to search, and now find that some of the chests contain coordinates to other chests (memory pointers). There are even some chains, one chest leading to another, leading to another, so on and so forth. Struck with an idea, you note all the chains that lead to your treasure before sailing away and coming back.
Many of the chains now lead nowhere useful, but some still point right to your treasure! After a few more tries, you eliminate all but a few paths and pick one of the remaining ones, with its start point always in the same spot. Now, you’re also able to come back to this treasure whenever you please.
Finding the Pointer(s)
Luckily for us, Cheat Engine has a robust set of tools to track down pointers as well. The first step is to find the current location of the address, if you haven’t already, and add it to the address list once again. Then, right click on it and select “Pointer scan for this address,” as shown:
The default options should be fine, but if your first scan turns up nothing then you can try again while raising “Max different offsets per node” or the max level (essentially the max chain length it scans for). Bear in mind that this will make the scan take longer, especially if the game is large.
Then, just hit OK and wait. Once it’s done, close the game and reopen it (leaving the scan window open). You may need to close the game using Task Manager to ensure its memory locations change.
Make sure to reselect the process in the main Cheat Engine menu, then find the new address. (You can skip this if the exact value is available in-game.) Then, in the pointer scan, hit Ctrl+R or open Pointer scanner > Rescan memory, as shown below.
Then, just paste the new address or value at the top, and repeat as many times as you feel are necessary. Additionally, ensure that the pointer exists at all necessary times — the “Only filter out invalid pointers” and “Repeat rescan until stopped” options are particularly useful for this. (For example, some pointers may only exist when the game is paused.)
If you found a pointer that stays, double click it to add it to the address list and move on to implementing it in SerpentAI. Otherwise, try another pointer scan while raising either the max offsets or level.
SerpentAI Implementation
Congratulations, you’re almost done! Now all you need to do is read the bytes from the game and convert those bytes into the correct data type (or the inverse, if you’re writing to memory instead).
In the game plugin, call:
self.window_controller.read_memory(address, size=4, pointer_offsets=None)
Or
self.window_controller.write_memory(value, address, size=4, pointer_offsets=None)
(To call this in the game agent, use self.game.window_controller
instead.)
Address
The address input is the initial offset from the base address, found by double clicking the address in the address list and looking in either of the locations below. Make sure you add 0x to the value in Python so your code knows it’s hex.
Size
The size is simply the length of the data in bytes. Copy this from Cheat Engine’s Type field (4 Bytes is 4, Double is 8, etc.).
Pointer Offsets
The pointer offsets are a list composed of the offsets provided in Cheat Engine, starting from the bottom. This is only used if the initial address is a pointer. Again, add 0x to all the values to represent them being hex.
Value
For the input or output value, simply pass it through struct.pack or struct.unpack or a similar method such as int.from_bytes(), depending on the data type.
Now, you should be done! Repeat for as many values as you’d like; now that you’ve gotten the hang of it, the rest should go by quickly. If you’d like full code examples, they can be found here:
Melty Blood Type Lumina Game Plugin (no pointers)
Jigoku Kisetsukan Code Snippet (pointers)