jadon

polyglot plt hacker

Home · Blog · GitHub · Twitter · Email


Reverse Engineering SSF2 Beta v1.1 Resources

Published Friday, August 24, 2018 · 922 words, 5 minute read.

I run 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.

Insepecting these ssfs shows us a couple things:

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.

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:

  1. 4 bytes to indicate the size of the SWF in bytes
  2. 4 bytes to indicate the size of the garbage data in bytes
  3. Any number of random, useless bytes, as indicated by #2
  4. 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.

example image

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!



Home · Blog · GitHub · Twitter · Email