-
Notifications
You must be signed in to change notification settings - Fork 1
Unmangling and decoding algorithms
Game files are obfuscated in the release versions of Fatal Racing and Whiplash using one or both of the following processes:
- Mangling: "RLE algorithm on steroids"
- Encoding: Fibonacci-like XOR cipher stream.
No obfuscation is used in the Fatal Racing demo or beta.
The Whiptools unmangler is adapted from this gist by @samunders-core.
The original unmangler (UNMANGLE.C) is part of the Actua Soccer/UEFA Euro 96 England source code which was accidentally distributed on the cover disc of issue 71 (May 1996) of French computer magazine Joystick. The source code was later made available on the Gremlin Archive under a CC BY-NC-ND 4.0 licence.
Note that UNMANGLE.C ignores the 4-byte header containing the unmangled file length and instead terminates the output when a zero control byte is found. However, the header is used by SOUND.C in Fatal Racing/Whiplash (loadfile and getcompactedfilelength).
4-byte header denoting the length of the unmangled file.
One of eight functions is performed depending on the control byte (next unread byte). Gremlin's original unmangler implementation uses a bitwise decision tree to determine the required function as follows:
Literal / Diff / Repeat0xxxxxxx
|
Block1xxxxxxx
|
||||||
|---|---|---|---|---|---|---|---|
Literal00xxxxxx
|
Diff / Repeat01xxxxxx
|
Short block10xxxxxx
|
Medium / Long block11xxxxxx
|
||||
Diff010xxxxx
|
Repeat011xxxxx
|
Medium block110xxxxx
|
Long block111xxxxx
|
||||
Byte diff0100xxxx
|
Word diff0101xxxx
|
Byte repeat0110xxxx
|
Word repeat0111xxxx
|
||||
0x01 to 0x3F
|
0x40 to 0x4F
|
0x50 to 0x5F
|
0x60 to 0x6F
|
0x70 to 0x7F
|
0x80 to 0xBF
|
0xC0 to 0xDF
|
0xE0 to 0xFF
|
0x00 is reserved for signalling the end of the mangled data.
Copy raw bytes from input to output.
- Length: 1 to 63 bytes (value of the control byte).
Generate bytes based on the delta of the last 2 bytes in output. Each next byte = previous byte + delta.
-
Length:
(control & 0x0F) + 3(3 to 18 bytes).
Generate words based on the delta of the last 2 words in output. Each new word = previous word + delta.
-
Length:
(control & 0x0F) + 2(4 to 34 bytes).
Repeat the last byte in output multiple times.
-
Length:
(control & 0x0F) + 3(3 to 18 bytes).
Repeat the last word in output multiple times.
-
Length:
(control & 0x0F) + 2(4 to 34 bytes).
Clone 3 bytes from an earlier position in output.
-
Offset:
(control & 0x3F) + 3(3 to 66 bytes). - Length: 3 bytes.
Clone multiple bytes from an earlier position in output, with offset and length from the next input byte.
-
Offset:
((control & 0x03) << 8) + nextByte + 3(3 to 1026 bytes). -
Length:
((control >> 2) & 0x07) + 4(4 to 11 bytes).
Clone multiple bytes from an earlier position in output, with offset and length from the next 2 input bytes.
-
Offset:
((control & 0x1F) << 8) + nextByte1 + 3(3 to 8194 bytes). -
Length:
nextByte2 + 5(5 to 260 bytes).
The Whiptools mangler achieves a ~0.2% better overall compression ratio than the original mangled files from the US release of Whiplash (282 files):
| Mangler | Total file size (bytes) | Compression ratio |
|---|---|---|
| Whiptools 1.4.5 | 13,454,194 | 40.90% |
| Original mangled files | 13,521,972 | 41.11% |
| Whiptools 1.3.1 | 15,855,211 | 48.20% |
| Unmangled | 32,892,331 | 100.00% |
Similar results are achieved using the English and Brazilian Portuguese installations of Fatal Racing (276 files each).
Encoding is applied to .KC files (cheat audio activated by TOPTUNES), FATAL.INI (configuration file) and PASSWORD.INI (cheat names). To decode these, apply a bitwise XOR against the Fibonacci-like sequence
-
.KC files:
$a_0 = 115$ ,$a_1 = 150$ Note .KC files must be unmangled first, then decoded.
-
FATAL.INI:$a_0 = 77$ ,$a_1 = 101$ -
PASSWORD.INI:$a_0 = 23$ ,$a_1 = 37$ By decoding
PASSWORD.INIin Fatal Racing V2.0 builds or Whiplash we can find two undocumented cheat names:- PROCESS: performs a Pentium FDIV bug check and changes to TYPE A if the bug is found or TYPE B if not.
- CLONES: not implemented.
FRONTEND.C also calls this function with