Space Funky B.O.B.
Space Funky B.O.B. Source Code
This was originally posted on eludevisibility.org when I (Matthew Callis) originally bought these disks off eBay, but I'm consolidating all SNES related things down to superfamicom.org.
Well, well. What have we here? Is this a first? Maybe. Hopefully. So what is it? It's the source code to the Super Nintendo game Space Funky B.O.B.. from Electronic Arts / Gray Matter. This is an early game for the system, and you can really tell with the equipment and software used, more on this in a bit. I haven't tried to compile the game, I don't know if the compiler they used is included. The files included range from the original MIDI version of some songs, to RAW format graphics to various types of sources to some fun developers notes.
Some of the software that was on the disks used is old, but what's odd is that some of it is used to emulate even OLDER hardware, the Apple II. The Apple II emulator includes some files, but never having used that computer, I myself started with Windows 3.1, I'll let some investigative readers figure that out. Some of the other software included is ICE, which I have gotten to work only once, it was an interactive debugger setup. Next, we have SLUGGO. I couldn't find anything online about it in 30 seconds of searching. From the READ.ME included it seems to be the Sluggo III which looks to be a ROM emulator, and not much else.
If you would like to help, the best thing to do as of right now is to please link to this page or the main page and NOT link to other sites; if for whatever reason my link goes down or my site get shut off, this will change (duh) but right now let's stick with this. If you would like to help by donating anything from money to books to software to hardware, contact me over on superfamicom.org. Thank you and as always, enjoi!
Space_Funky_BOB_Source_Code.7z
Space Funky B.O.B. Compression
B.O.B. stores many of its files in a simple LZ77 format. The first byte is a chunk header; 8 bits describing how to process the next 8 pieces of information. If the bit is a 0, the next piece is a literal byte and is copied from the input buffer to the output buffer. If the bit is a 1, the next two bytes are read as a distance/length pair.
The format of the distance/length pair is a 16-bit, unsigned, little-endian number. The first 5 bits of which are the length-3, the remaining 11 bits of which are the distance. Take note that the distance can potentially hold a value of 0. This is an error on the programmer's part, as distance in an LZ77 stream should NEVER be zero, else an error is thrown.
Once all 8 pieces are read, the next byte will be another chunk header and the whole process starts over again.
Turns out B.O.B.'s programmer made a mistake when he was programming his LZ77 compressor. It's a common mistake, but makes a difference. He failed to compensate for the fact that Length can be greater than Distance in a distance/length pair. Since every byte copied increases the size of the output buffer, Length can be an arbitrarily long value regardless of Distance, since it will never run out of bytes to copy. Doing things this way will result in a sequence of bytes repeated a number of times; much like an RLE compression.
When data is compressed using the above more-optimal technique, the B.O.B. programming is still able to decode the data, so it is in fact something the programmer overlooked.
A few things can happen while decompressing data in B.O.B.:
- Distance could be 0, which will render decompression impossible.
- Distance could be greater than the number of bytes in the output buffer, which also renders decompression impossible.
- Length could instruct data to be decompressed past the target file length, which will not cause an error, but might signify corrupted data.
Anyhow, I wrote some code and verified all of this today. Below are two functions, lzDec and lzEnc, which process data streams according to the B.O.B. variant of LZ77. DecLen is the length of the decompressed data (final file size for decompression and original file size for compression), Src is a pointer to the first byte of the input buffer and Dst is a pointer to the first byte in the output buffer.
- lzDec will return 0 if an error occurs, or the size of the compressed file if successful.
- lzEnc will return the number of bytes in the compressed file once it's completed.
Let's say you have the ROM loaded, and have a 0x822
byte file at offset 0x1AD34
... You'd use this syntax:
lzDec(0x822, &(ROMData[0x1AD34]), DecompressedData);
// Decodes a data stream to B.O.B.'s specifications
unsigned int lzDec(unsigned int DecLen, unsigned char *Src, unsigned char *Dst){
unsigned int sOff = 0, dOff = 0, Temp, Dist;
unsigned char cHdr, Bit, BitVal, Leng;
// Keep going 'til it's all decoded
while (dOff < DecLen) {
// Chunk header
cHdr = Src[sOff]; sOff++;
// Cycle through bits
for (Bit = 0; Bit < 8; Bit++) {
BitVal = (cHdr >> (7 - Bit)) & 1;
// Literal
if (BitVal == 0) {
Dst[dOff] = Src[sOff];
sOff++; dOff++;
// Distance/length pair
} else {
Temp = Src[sOff] | (Src[sOff + 1] << 8);
Dist = Temp & 0x7FF;
Leng = (Temp >> 11) + 3;
sOff += 2;
// Prevent out of bounds for bad data
if (Dist > dOff || Dist == 0) return 0;
// Copy Length bytes from output buffer
for (Temp = 0; Temp < Leng; Temp++) {
Dst[dOff] = Dst[dOff - Dist];
dOff++; if (dOff == DecLen) break;
} // for
} // if
if (dOff == DecLen) break;
} // for
} // while
// Return size of packed data
return sOff;
}
// Encodes a data stream to B.O.B.'s specifications
unsigned int lzEnc(unsigned int DecLen, unsigned char *Src, unsigned char *Dst){
unsigned int sOff = 1, dOff = 2, HdrOff = 0, Temp;
unsigned char cHdr = 0, Bit = 1, tLen;
unsigned int mDist, MaxLeng, mOff, LongLeng, LongDist;
// Write the first byte as literal
Dst[1] = Src[0];
// Encode until done
while (sOff < DecLen) {
// Calculate starting distance
if(sOff < 0x7FF) mDist = sOff;
else mDist = 0x7FF;
// Find the longest match
LongLeng = 0;
for(mOff = sOff - mDist; mOff < sOff; mOff++){
// Match was found; find its length
if(Src[mOff] == Src[sOff]){
// Calculate maximum possible length
MaxLeng = DecLen - mOff;
if(MaxLeng > 0x22) MaxLeng = 0x22;
// Scan bytes and check reported length
for(tLen = 1; tLen <= MaxLeng; tLen++)
if(Src[mOff + tLen] != Src[sOff + tLen]) break;
if(tLen > MaxLeng) tLen = MaxLeng; // For loop quirk
// If this is the longest match so-far, record it
if(tLen >= LongLeng){
LongLeng = tLen;
LongDist = sOff - mOff;
} // if
} // if
} // for
// Literal byte
if(LongLeng < 3){
Dst[dOff] = Src[sOff];
dOff++; sOff++;
// Distance/length pair
}
else{
Temp = LongDist | ((LongLeng - 3) << 11);
Dst[dOff] = Temp & 0xFF;
Dst[dOff + 1] = (Temp >> 8);
cHdr |= (0x80 >> Bit);
dOff += 2; sOff += LongLeng;
}
// Write out a chunk header if needed
Bit++;
if(Bit == 8) {
Dst[HdrOff] = cHdr;
cHdr = 0; Bit = 0;
HdrOff = dOff; dOff++;
}
} // while
// Write the last chunk header if needed and return output size
if(Bit > 0) Dst[HdrOff] = cHdr;
return dOff;
}
LZ Decompression reverse engineered by GuyPerfect.