Ripping Sprites From Super Cyborg
Anyone remember that game? I do. In fact, just looking at that image makes me hear that awesome 8-bit theme in my head. If, like me, you enjoyed playing Contra on the NES, Super Cyborg is probably right up your alley. It's a 2D run-and-gun action game with retro-style graphics and music. I think it looks pretty rad. The game is available on Desura (and also has recently been greenlit on Steam), has a free demo and works on Windows. I encourage you to try it. Do try the demo first before paying money, since it seems that some people had trouble running it.
However, this post is not going to be about the game itself. It's going to be about a little bit of reverse engineering I did with it. I don't really have a background in things like this, but I dabbled in it somewhat when I was younger, and I remember it being fun. Let's get started!
I picked up the free demo of Super Cyborg on Desura one day when I was feeling bored. I enjoyed playing through the demo levels, and after a while I decided to take a closer look at the game to figure out what engine it was built with. Looking into the game folder, surprisingly, didn't give me any clues. There was a native Windows executable, a config file and a folder with a couple of small data files. There also didn't seem to be any files that contained resources like music, sprites and other stuff like that. Judging from the size of the executable, I thought that resources were probably baked into it directly. Since apparently I have too much free time, I decided to try and rip the sprites out of it.
I broke out PE Explorer and opened the executable with it. Like I expected, there was an RCDATA resource - typically used to include raw data directly into the executable. I assumed that it contained the things I was after - sprites. My first theory was that the RCDATA resource I was looking at was some kind of a compressed archive. I dumped the contents of the resource into a separate file and tried opening it as a zip, 7zip, rar, tar, etc., but no luck (as it turned out later, it wasn't an archive file at all, and the blob didn't actually contain the data I was looking for, but let's not jump too far ahead). After some more poking around in PE Explorer, the only more or less interesting thing I found out was that the game was built with BlitzBasic:
Surgery With a Debugger
Looking at stuff in PE explorer was getting me nowhere, so I decided it was time to observe our patient in action to figure out what it was doing. OllyDbg is pretty good for such purposes. The thing about it, though, is that, unless you know what you're looking for, it's very daunting to just sit and step through the entire program with a debugger. So I thought it would be a good start to log the system calls that the application was making.
Now, I know for sure that there is some good tool for Windows that does exactly that and more, but I am not currently aware of it. However, Wine has some pretty good debugging facilities that do exactly what I need. So, I just ran the game on Linux in Wine with some logging enabled:
env WINEDEBUG=+relay wine Super\ Cyborg\ demo.exe &> wine.log
This produced a pretty big log. It's still pretty difficult to find something in a gigabyte of text, especially if you don't know exactly what you're looking for. Eventually, after some trial and error, I arrived at the following theory: "since the sprites are packed into the executable, it would make sense to unpack them onto disk during runtime; if that is the case, the application has to call Windows' CreateFile at some point." Grepping through the logs yielded the following extremely promising results:
0009:Call KERNEL32.CreateFileA(0047b829 "Super Cyborg demo.exe", 80000000, 00000003, 0032e850, 00000003, 00000080, 00000000) ret=100a9487 0009:Ret KERNEL32.CreateFileA() retval=000000ec ret=100a9487 0009:Call KERNEL32.CreateFileA(031daef9 "data\\TMPshot_down_left_v2_2.png", 40000000 ,00000003, 0032e850, 00000002, 00000080, 00000000) ret=100a9487 0009:Ret KERNEL32.CreateFileA() retval=000000f0 ret=100a9487 0009:Call KERNEL32.CreateFileA(031d9619 "data\\TMPshot_down_left_v2_2.png", 80000000, 00000003, 0032e664, 00000003, 00000080, 00000000) ret=100a9487 0009:Ret KERNEL32.CreateFileA() retval=000000ec ret=100a9487 0009:Call KERNEL32.CreateFileA(031d9619 "data\\TMPshot_down_left_v2_2.png", 80000000, 00000003 ,0032e660, 00000003, 00000080, 00000000) ret=100a9487 0009:Ret KERNEL32.CreateFileA() retval=000000ec ret=100a9487
See those *.png file names? Those are exactly what we're looking for. The files weren't present while the game was running, so the game was probably deleting them. In that case, it has to call DeleteFile at some point, right?
0009:Call KERNEL32.DeleteFileA(031d5d71 "data\\TMPshot_left_v2_2.png") ret=10052997 0009:Ret KERNEL32.DeleteFileA() retval=00000001 ret=10052997 0009:Call KERNEL32.DeleteFileA(031d82e9 "data\\TMPshot_down_right_v2_2.png") ret=10052997 0009:Ret KERNEL32.DeleteFileA() retval=00000001 ret=10052997 0009:Call KERNEL32.DeleteFileA(031d8379 "data\\TMPshot_down_left_v2_2.png") ret=10052997
Yes, in fact the relevant call happens at virtual address 10052991! Let's open it up in OllyDbg. If we set a breakpoint before the delete call happens, we'll probably be able to see the files. But we can do something better. We can make the program skip the call entirely. Observe what's going on in the picture: first, we put the adress of the string containing the file name into EAX (the instruction at 1005298B), then we push EAX on the stack (this will be the argument to DeleteFile), then we invoke DeleteFile. According to the calling convention, after DeleteFile returns, its result will be stored in EAX, so we check it, and finally, return.
Now we can do a little surgery and replace the whole code that does the function call with a bunch of NOPs (OllyDbg lets you do that, select the instructions that you want to replace, then right click -> edit -> fill with NOP). For those who don't know, NOP is an x86 instruction that does nothing. So, we'll be telling the program to literally do nothing instead of invoking DeleteFile. After we replace the relevant part of code with NOPs, we can apply the patch to the executable, save the patched exe and run it. After that you'll see the "data" folder populated by a bunch of files!
It seems that when the sprite files are already present, the game won't try to overwrite them, so you can actually modify them and see the result of the modification in-game.
A Non-Invasive Method
Seeing that the game stores its sprites in PNG files, I decided to do a little experiment. The first 8 bytes of all PNG images are the same, it's called the PNG signature. The hexadecimal values are as follows: 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a. If the sprites are stored in non-compressed, non-encrypted form, then we'll definitely find something if we search for the PNG signature. Oh, look, what's this?
So, there's a way to rip out sprites without patching the game. It's possible to write something that scans the executable, finds the embedded PNG images and dumps them into separate files. Only a very brief knowledge of the PNG file structure is required. Let's have a look at what the PNG spec has to say:
A PNG file consists of a PNG signature followed by a series of chunks. [...] Each chunk consists of four parts: Length, [...], Chunk Type [...], Chunk Data [and] CRC
I'll let you read the spec if you want more details. For our purposes, the following points are important:
- The start of PNG data is identified with an 8-byte signature.
- The rest of the data is encoded in chunks.
- A chunk has a 4-byte length field, a 4-byte type code field, an arbitrary-length data section and a 4-byte CRC field.
- The end-of-file marker for PNG is a special chunk of type "IEND".
import sys, tempfile data = sys.stdin.buffer.read() pos = 0 while 1: pos = data.find(b'\x89PNG\x0d\x0a\x1a\x0a', pos) if pos == -1: break end = data.find(b'\x00IEND', pos) + 9 if end == -1: break open(tempfile.mkstemp(suffix=".png", dir="./"), "wb").write(data[pos:end]) pos = end
So there you go, two ways of ripping sprites out of that game. I like the first way (with exe patching) a little better though, because it potentially allows "mods", while the second one doesn't :)
Do try the game, and if you like it, support the developer! Hopefully we'll see more good stuff from them in the future.
Like this post? Follow this blog on Twitter for more!