0x0 Background
The FPD packet encoding algorithm and epk decryption algorithm have both been reverse engineered, but the FCD audio has not been reverse engineered.
List of games using this engine: https://vndb.org/r?f=fwAGES_0Mk2-
Identify (Windows)
Identify (Android)
No iOS version found.
0x1 Parsing FCD header
-
Validate magic
The loader checks the first 3 bytes:
buf[0..2] == b"FCD"
Header fields:
-
Processing and parsing file headers
Swap the 16-bit version at offset
0x04Swap the 16-bit count at offset
0x06 -
Then it decides how to decode payload offset (the 4 bytes at
0x08) based on the swapped version:If
version == 1
payloadOffset is stored as 2 bytes:payloadOffset = (buf[0x08] << 8) | buf[0x09]If
version == 3
payloadOffset is stored as 32-bit swapped:payloadOffset = bswap32(*(u32 at 0x08)) -
It writes the result back to
*(a1+8).
Entry table:
Entry i is at: a1 + 0x10*(i+1)
For each entry, it byteswaps the entry type dword (at entry offset +0)
Then, depending on the type (after swap):
-
If
type == 1: byteswap three dwords at offsets+4,+8,+0xC -
If
type == 3: byteswap one dword at offset+4 -
If
type == 2: no swapping of the 12-byte payload/tag
Finally it sets *a1 = 0 as a “normalized already” flag.

0x2 Key derivation
MD5 context is initialized with standard MD5 IVs:
A=0x67452301, B=0xEFCDAB89, C=0x98BADCFE, D=0x10325476
Then it computes:
key16 = MD5( tag12 || md5_addition_blob )
Where:
tag12is exactly 12 bytes from the type=2 entry (no per-word swapping)md5_addition_blobis exactly0x4000bytes
The resulting 16-byte digest is used as the Blowfish key material.

0x3 Blowfish
This section only explains the modified parts.
fz::sound::util::BlowFish::init
-
Load S material from the embedded
blowfish_keytableThe code copies 0x400 words from
blowfish_keystarting at index0x12 * 4, and writes them in 4 passes:pass0 writes to
ctx[0x12 .. 0x12+0xFF]pass1 writes to
ctx[0x16 .. 0x16+0xFF]pass2 writes to
ctx[0x1A .. 0x1A+0xFF]pass3 writes to
ctx[0x1E .. 0x1E+0xFF]
So S0/S1/S2/S3 are not 4 independent arrays; they are 4 “views” into one shared window. -
Fill S, overlapped
rbp = ctx + 0x4C
4 passes; each pass:
does 0x80 iterations, each writes two dwords using a stride of 2 dwords per iteration
after each pass,
rbp += 0x10
sub_180046290
-
S indexing uses the overlapped “window bases”
S0[a] ≈ ctx[0x12 + a]S1[b] ≈ ctx[0x16 + b]S2[c] ≈ ctx[0x1A + c]S3[d] ≈ ctx[0x1E + d] -
F function combine differs
Standard Blowfish:
F = ((S0+S1) ^ S2) + S3
This binary uses:F = (S0+S1) ^ S2 ^ S3
fz::sound::util::BlowFish::decode
-
Tail handling
If
nbytes % 8 != 0:It packs remaining bytes into an integer by shifting left
Decrypts that “padded” block once via
sub_180046290Writes back only the remaining bytes by repeatedly storing the low byte and shifting (
rbx >>= 8) from the end down to the tail start.
MD5:
It's a standard MD5 hash, without any modifications.
0xFF
