Return To Winx
Matt Hurd / February 2024 (1122 Words, 7 Minutes)
It’s been a while. Winx Club is a project that keeps coming back up for me though, so I know I’ll be making some good progress this time. Last we left off, I was bashing my head against figuring out how to manage partially decompiled files. Today’s not the day for solving that. Instead, we’re going to be spending some time in IDA. Fleshing out our IDB will drastically help down the line.
Investigating the crash
Debugging
Last time I was working through this game crash, I was using no$gba. This emulator is made for debugging, and provides a ton of info. The issue is that the emulator itself is rough to work with, and I’d much rather work using IDA. Thankfully, mGBA can actually host a GDB server. We can connect to it with IDA and use it instead.
IDA has a decent memory view, but I’m most used to using Cheat Engine for actually searching through memory. I’ve always wanted to learn some more advanced Cheat Engine usage, so today seems like a great time. CE uses LUA for it’s scripting language, so we can dig into it.
Cheat Engine
Cheat Engine is a great tool for any sort of memory work. Attaching it to mGBA should let us take a look at the GBA’s memory. It seems like a pretty safe assumption that mGBA will store IWRAM and EWRAM in their own memory regions, so we’ll need to find those. mGBA has a built-in memory viewer, so we can take a look at what each region looks like.
It looks like EWRAM starts with the bytes 10 00 00 02 00 00 04 02 45 00 00 00 D0 3C 03 00
. After a bit of testing, it seems like 10 00 00 02 00 00 04 02 ?? ?? 00 00 ?? ?? 03 00 1C 00 00 02 00 00 00 02"
is a consistent AoB pattern. We can repeat the same for both IWRAM and ROM. We end up with the following patterns:
1
2
3
4
5
local patterns = {
EWRAM = "10 00 00 02 00 00 04 02 ?? ?? 00 00 ?? ?? 03 00 1C 00 00 02 00 00 00 02",
IWRAM = "10 40 2D E9 00 40 B0 E1 05 00 00 1A 5C 00 A0 E3 30 04 00 EB",
ROM = "3E 00 00 EA 24 FF AE 51 69 9A A2 21 3D 84 82 0A",
}
Now I need to create a LUA script that will do an AoB scan for these addresses, and export some symbols that I can use in my Cheat Table. Thankfully, ChatGPT has apparently scraped all of Cheat Engine Forums, and is able to give us some (mostly) working LUA. After fixing up the issues, we end up with this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local patterns = {
EWRAM = "10 00 00 02 00 00 04 02 ?? ?? 00 00 ?? ?? 03 00 1C 00 00 02 00 00 00 02",
IWRAM = "10 40 2D E9 00 40 B0 E1 05 00 00 1A 5C 00 A0 E3 30 04 00 EB",
ROM = "3E 00 00 EA 24 FF AE 51 69 9A A2 21 3D 84 82 0A",
}
local baseAddresses = {}
for region, pattern in pairs(patterns) do
local results = AOBScan(pattern)
if (results and results.Count > 0) then
baseAddresses[region] = results[0]
registerSymbol(region, results[0])
print(string.format("%s Base Address found at: %s", region, baseAddresses[region]))
else
print(string.format("Pattern not found for %s.", region))
end
end
After executing this script, we get the following output:
1
2
3
ROM Base Address found at: 1C3E0000
EWRAM Base Address found at: 06970000
IWRAM Base Address found at: 069B0000
To actually use these, let’s take an example. I’m curious about looking into gUnknown_030034F8
. I can find it in CE with the following entry:
Since the value is in the format of 0x02000000
, we can assume that it’s pointing to somewhere in EWRAM. We can make it into a pointer and see what data there is:
Now I can see that at the current moment, the value stored is equal to 0
. This isn’t exactly useful, but we can take a look in CE’s Memory Viewer to get some surrounding information:
This will be very useful in the future. When values change, they will flash in red. This makes it really easy to see when an action ingame causes a value to change.
Now let’s take a look at what’s happening when we normally hit an enemy. To do this, let’s first find where an enemy is stored in RAM. For me, the easiest place to start is finding their coordinates. We’ll start with a screen with a single enemy, take a savestate, and pause the game. A search for an unknown 4 Byte value. Advance frames, increased/decreased value, rinse and repeat until we’re down to a handful of values. Add them all the to address list, freeze half, see if the enemy is unable to move. Essentially do a binary search until you find the true value that determines the position. Taking a look at the memory viewer around that position (with the output changed to 4 byte hex), we see this:
Looking above at nearby values, we can see one that is equal to 0803E950
. This sticks out to me as a ROM address. Taking a look at this address in IDA, we can see the dword_803E950
is defined and has references to it. Jumping to the code, we see it’s referenced here:
Amazing, this looks like some sort of initializer. If we use IDA to convert v1
to a new struct type, we get this:
1
2
3
4
5
6
v1->dword0 = dword_803E950;
v1->dword4C = "Monsters Script Group";
v1->word4 = 10;
v1->byte97 = 2;
v1->byteB6 = -1;
v1->dword84 = (v1->dword84 & 0xF000FFFF) + 0x2000000;
This looks suspiciously like a C++ class that starts with a vtable, but let’s just hope that we’re not having to touch C++ for this project. I’m scared to try out a 2001 C++ compiler. Let’s move forward assuming that this is some sort of basic function table that we’ll have to look into later. It does seem like this is the beginning of the struct though, so let’s move forward on that.