Reverse Engineering Super Smash Flash
!! Note: This blog post is rather old and may be based on outdated information.
I made a Sonic Battle Hacking Discord Server, filled with a bunch of people who like modding Sonic Battle for the GBA. Somebody in this Discord Server asked me to check out Super Smash Flash 2 Beta v1.1.0.1 because their old modding tools didn’t work for it.
The SSF2 developers have had a strong stance against modding, which I personally thing is poor considering they’re making a non-profit game. That of course didn’t stop me from digging into the game.
I used the JPEXS Flash decompiler to inspect the code. SSF2 is written in ActionScript, which is very similar to other OOP languages. The first thing I noticed was that nothing was obfuscated, which made it easy to find some things, except some of the core resource loading was obfuscated (manually?).
The resource files are in a “hidden” ssf
format. The files in the resources
folder are named DAT1.ssf
, DAT2.ssf
, DAT3.ssf
, etc. These files are “encrypted”
SWF files. All we need is to figure out how do decode these into the SWF format
and have JPEXS do the rest.
Inspecting these ssf
s shows us a couple things:
- They’re ZLIB compressed
- There’s a mystery header before the SWF header
- Modifying this header breaks loading the resource
This tells us something in the code has to be decompressing them.
I disassembled the game using JPEXS and grepped the code for ZLIB
.
Instantly had one hit in com/mcleodgaming/ssf2/util/Resource.as
.
wellthatwaseasy.jpg
The code containing the string is:
var c: ByteArray = null;
var l: int = 0;
var n: int = 0;
var x: int = 0;
var b: ByteArray = m_urlLoader.data as ByteArray;
c = new ByteArray();
try {
b.uncompress(CompressionAlgorithm.ZLIB);
l = b.readInt();
n = b.readInt();
for (x = 0; x < n; x++) {
b.readInt();
}
c.writeBytes(b,b.position,l);
b = c;
c = null;
}
Let’s disect this code a bit.
- At the beginning,
b
is our resource file that is loaded into aByteArray
- A new
ByteArray
,c
, is constructed to obfuscate things b
is decompressed using ZLIB- The beginning two ints in
b
are stored inl
andn
- We then read
n
ints out ofb
- The bytes in
b
from[b.position, l)
are copied toc
b
is set to the bytes we just copied tob
, truncating the original array tol
What’s really going on here? b
starts off with the length of the SWF data (l
)
and the length of the mystery header (n
). This mystery header doesn’t actually
contain any information, it’s skipped right over!
Here’s what a decompressed .ssf
file contains:
- 4 bytes to indicate the size of the SWF in bytes
- 4 bytes to indicate the size of the garbage data in bytes
- Any number of random, useless bytes, as indicated by #2
- The SWF itself prefixed by the typical SWF header,
FWS
What’s next? Write a tool to generate the headers!
I chose to write in in Kotlin since it is a language I enjoy. The GUI code doesn’t matter much, only the compressing / decompressing.
Here’s the pseudocode to compress a .ssf
:
def compress(file: SWF):
buffer = new ByteBuffer()
buffer += file.length as Int32 # we want to truncate it to 4 bytes
buffer += 0 as Int32 # 4 bytes for 'n' described above
# we don't need any garbage data since we set
# the length of the garbage data to 0 above
buffer += file.bytes
compressed = zlibCompress(buffer)
write(compressed, file.withExtension(".ssf"))
Pretty simple! Here’s the pseudocode to decompress a .ssf
:
def decompress(file: SSF):
bytes = zlibDecompress(file.bytes)
garbageLength = bytes[1]
swfBytes = bytes[garbageLength..]
write(swfBytes, file.withExtension(".swf"))
And that’s it! We’ve got an SSF2 resource (de)compresser! There are other things
encrypted in the code, like what characters / stages are in what .ssf
files,
but I’ll probably get to that soon.
Here’s an example mod that changes Pichu’s name.
The code is available on GitHub. I made it in one night, so I didn’t take time to make it look good or anything, but it works.
Thanks to the other SSF2 modders who introduced me to the game!