I currently aspire to create an AI to play the game Majesty: The Fantasy Kingdom Sim.
One important part of building this AI will be allowing it to know what actions it can perform. Almost all meaningful actions in Majesty will consume gold, so knowing how much gold we have in the royal treasury is critical.
I'll be using Cheat Engine to perform this task.
Finding the gold value for a single game
First, let's start a game and see that we have 20000
gold. We can use Cheat Engine to pull up all of the memory addresses that store the value 20000
.
Then, we can buy something in the game and have Cheat Engine filter this list to contain only the memory addresses that now have 19600
.
In this case, there is only one address, with the value 0x107E5CB8
. You can double-click it to preserve the address in Cheat Engine.
One big problem though - this address isn't consistent! Every game of Majesty will store its gold value in a different memory address. Instead of performing this manual scan for every game, it would be great to know ahead of time where the gold value for the game will be located.
Memory offset
You could imagine the gold value for a game being stored in a data structure like this:
class Game {
bool hasEnded;
bool isMultiplayer;
uint32_t questId;
uint32_t timeElapsed;
uint32_t currentGold; // <--- this is the value we want
uint32_t workerCount;
}
The location where Game
is stored in memory will change for every game. However, the currentGold
location inside of the Game
structure will never change - it was compiled with the game's code!
In the above example, if bool
has a size of 1 byte, and uint_32
has a size of 4 bytes, the offset for currentGold
would be 10 bytes.
Cheat Engine has a method for finding this offset. First, right-click the saved memory address and click "Find out what accesses this address":
Resume the game and make another purchase. Notice the opcode accesses:
In this case, that +10
attached to each operation means we have an offset of 10. This will help us later.
Pointer scanning and pointer mapping
To find out how to get to your friends' house, you want a set of directions. Start at your apartment, turn left onto 90, turn left onto 405, exit and Main street, look for house number 1184.
Similarly, what we want is a set of directions for how to find gold value of a Majesty game. Start at the base of Majesty's address space, move down 10,000 bytes, follow the pointer at that address, move 50 bytes, follow the pointer at that address, etc.
Creating this set of instructions for a memory address is called pointer scanning. We'll try it out now.
Right click on the current gold address and select "Pointer scan for this address"
In the next menu, the only value we need to change is to add the offset we discovered earlier. This tells Cheat Engine "you last step is going to be to move 10 bytes down".
In the results, you'll notice two things. One - the number of results is impossibly high! Two - not all of the results make sense. For example, why would a set of directions start with "AquaSnap.dll"?
We need to pare down this list. Close that dialog. Right click the memory address, and click "generate pointer map". This will save a list of all of the possible routes to our gold address.
Now, go back to the Majesty main menu, and start a new game. Use the process from before to find the current gold value, and generate a pointer map for it.
Now, right click on the new gold address and select "Pointer scan for this address". We'll do the same thing as last time, except:
- Check "Use saved pointermap", and input the file from the current game's scan
- Check "Compare results with other saved pointermap(s), input the file from the old game's scan, and make sure to select its address in the dropdown
The result should be a bit more favorable. For me, it was only 189 possible paths:
It worth noting: multiple of these paths could be valid! Similar to how you can take the back roads to get to your friend's house.
What's the best way to my friend's house?
Let's double-click a bunch of the values from this pointer scan that start with "MajestyHD.exe"+...
.
Now, let's go back to the main menu in Majesty and start another new game.
Notice how some of the pointers point to the correct gold value (8500
) and others don't have the correct path (pointing to garbage memory).
If you continue to start new games and remove the invalid pointers, you will eventually find ones that consistently work. For example:
We've reached our goal! For any arbitrary instance of Majesty, we're now reasonably confident that we can follow this specific path of pointers to reach out current gold value.
Just to be sure, we can give ourselves an unreasonable amount of money by manipulating the value stored at the memory address.
That should be enough for plenty of guardhouses.