Took a deep dive into the sample archive you provided.
First off, to address the initial assumption: these files are not encrypted as a class.
Most of the .bin files are standard little-endian serialized C++ object graphs and raw PICA200 texture payloads without headers. A specific subset is indeed whitened/encrypted, but the rest of the game's data is completely readable once you know the container layout.
Here is a full breakdown of the formats, how they work, and working extraction/repacking tools.
1. Forensics & Triage (Debunking the "100% Encrypted" Claim)
A quick triage checking Shannon entropy vs. delta entropy (entropy of b[i] ^ b[i+1]) and byte distribution makes the structure immediately clear:
File
Entropy
Delta Entropy
Zero Bytes
Verdict
Text_English.bin
4.38
5.87
42.8%
Structured table
GameConstant.bin
3.13
4.32
63.4%
Structured (float/int table)
Material_Test.bin
1.59
1.73
79.7%
Structured object graph
etc_logo.bin
1.92
1.68
60.7%
Raw block data
Mesh_Test.bin
6.30
7.22
22.1%
Structured geometry
Level_Test.bin
7.18
7.74
< 5%
Whitened / Encrypted
Level_BarrageMulti.bin
7.24
7.82
< 4%
Whitened / Encrypted
Encrypted ciphertext exhibits delta entropy around 8.0 and a flat byte distribution. Files like Text_English.bin (delta 5.87, over 40% zero bytes) or etc_logo.bin are entirely unencrypted. Furthermore, human-readable strings like CMeshId_ExitFlash, LightId_Decor_000, and dependency paths like rom:/SkinMesh/Hero/SkinAnim_Hero_AttenteCockpitArriere.bin are visible in plain ASCII.
2. Text_English.bin (Fully Decoded + 1:1 Byte-Exact Repacker)
The text archive contains 1,178 localized strings. It does not use standard ASCII/UTF-8 directly; instead, glyph indices are stored with a constant offset shift:
Container layout:
u32 count // 1178 strings
u32 offsets[count] // Absolute record offsets
// data base = 4 + (4 * count) // 4716
Record:
u32 keyRel // (recordStart - base) + 4 + 2*ncodes (points to key string)
u16 codes[ncodes] // Glyph stream (terminated by 0xFFFF)
char id[] // Null-terminated ASCII key
padding // Aligned to 4 bytesGlyph stream header:
The first 6 u16 values inside codes[] act as per-string metadata:
[0] style: 255 = dialog/subtitles, 0..3 = control scheme variants, 511 = item/system names
[1] speaker: 255 = none, 0/1/2 = character slot
[2] always 0
[3] glyphCount: verified matches exact string length (1178 / 1178 records)
[4] metric: layout/timing hint (0 for UI, >0 for subtitles)
[5] always 0
The encoding:
Standard dialog (style != 511): Unicode = code + 30
Item names / system text (style == 511): Unicode = code + 32
In-stream control codes: 0xFFFF = End of string, 0xFFFE = line break (\n), 0x0001 = page break.
3. Textures & Materials (etc_*.bin & Material_<level>.bin)
The reason texture files do not have magic bytes or headers is because their header is external: it lives inside the corresponding Material_*.bin file!
Texture metadata table (starts at toff in the material header, 156-byte stride):
+0: char[96] Payload filename
+97: u8 Format (12 = ETC1, 13 = ETC1A4)
+98: u16 Width
+100: u16 Height
+102: u8 Mip levels (halvings = value + 1)
+103: u8 Flag (0 = raw payload, 1 = trailer payload)
Byte size rule:
Every clean texture payload on disk perfectly matches:
Total Size = Sum of all mip levels [ ceil(W/4) * ceil(H/4) * 8 * (1 if ETC1 else 2) ]
(e.g., etc_logo 256x256 ETC1 mip 4 = exactly 43,648 bytes).
PICA200 Swizzle:
Blocks are arranged in 2x2 block (8x8 pixel) tiles in standard raster order. UI and HUD textures (etc_logo, etc_divers, etc_divers normal, etc_neutre normal) decode cleanly into PNG/DDS with sharp text, gauges, and reticles.
4. Structural Assets (Light, Mission, GameConstant, Mesh)
Light_*.bin: 240-byte records containing RGBA intensity, truncated s16 coords, float32 world transforms (f32[3]), and ASCII IDs (LightId_Decor_000).
GameConstant.bin: Flat array of 4-byte values (floats and gameplay ints) followed by animation state identifiers (AttenteCockpitAvant, TrouveArme, etc.).
Mission_*.bin: Level load lists and dependency tables with 163 rom:/ paths and object IDs.
Mesh_Test.bin: First two u32 integers add up to the exact file size (1560 + 13320 = 14880). Object graph definitions up to ~0x610, followed by float vertex streams and a u16 index buffer at the end.
5. What IS Protected (The Encrypted Subset)
Only a specific subset is protected:
Geometry/Level blobs: Level_Test.bin, Level_BarrageMulti.bin
Shadow maps: ShadowMap/etca_Shad l0 salle_001.bin
6 photographic surface textures: etc_sol, etc_porte, etc_rouge, etc_jaune, etc_plafond blanc (+normal)
Evidence:
Flat byte histogram, but the file length is strictly equal to the uncompressed size (or matches declared dimensions). No compression padding.
Level_*.bin and ShadowMap contain an 8-byte trailer at EOF:
[u32: filesize | (method << 24)] followed by [u32: decompressed_size].
Standard compression (zlib, gzip, bzip2, lzma, 3DS LZ11/LZSS) and trivial byte/XOR transforms have been tested and ruled out. This is a custom cipher or whitening routine.
6. Tools
I wrote a small Python toolchain to handle extraction, repacking, and inspecting:
ifall_text.py: Extracts Text_English.bin to TXT/JSON and repacks it byte-identically. Supports arbitrary string lengths.
ifall_tex.py: Resolves Material_*.bin metadata and batch-converts raw ETC1/ETC1A4 payloads to PNG and uncompressed RGBA DDS.
ifall_struct.py: Dumps Materials, Lights, Missions, Constants, and Mesh identifiers to clean JSON.
picatex.py: Standalone PICA200 ETC1/ETC1A4 tile & swizzle decoder library.
(Tool scripts are attached to this post).
Next Step for the Protected Files:
To defeat the cipher on the Level_*.bin files and the 6 surface textures, we need the game executable (code.bin / decompressed ExeFS or .cro modules). The loader function that parses the 8-byte trailer and decrypts the payload in ARM assembly will give away the algorithm and key in a few minutes.
If you can dump and share the decrypted code.bin, I can reverse the decryption routine and finish the remaining formats.
IronFall_tools.zip
By
mohebbi ·