EFX0011: Libra effect files
- Source
docs/formats/efx.md- SHA-256
b94b3164…7de11eca
Wrapping · Header · Chunks · Resources · How the game reads it · Rebuilding · Leads
An effect: the textures, sound banks and other resources it uses, and its sets of chunks with their data, as a list of chunks after a 16-byte header. Every .efz file under effect/ is one, wrapped in MLZ0 (LZ4); effect/global/common/c_all.efz holds the shared battle effects (655 sets), every other .efz one effect with one or more variants. The game parses it with the effect player of its Libra library (Libra\TsMalloc.cpp); how an effect is picked and loaded is in Effects. This doc names the format after the magic the game checks.
| Based on | Every .efz file under effect/: 2,085 files (global/common/c_all.efz, 51 in global/magic/, 8 in global/chr_make_god/, and 2,025 in the eight language folders <lang>/{chr_make,event,results,title,victory}/); the unpacked executable, SHA-256 7b0230a11498f40396c9048345e7cfe0ef442cb7ca8faffc6f197920d2669599; the LZ4 block format (lz4_Block_format.md, checked 2026-10-11). |
| Checked | 2026-10-11 |
| Method | Single-file C# programs (session scratchpad, effect/scan.cs and effect/recompress.cs): an LZ4 block decoder that unwraps every file and checks the sizes, then a walk of every chunk of every file; re-compressing every payload with K4os.Compression.LZ4 1.3.8. Static analysis with Ghidra 12.1.4 of the loader (0x1404ab460) and the chunk readers, through the decompiled code in local/exe/decomp/; xxd of the executable. Not yet decoded or rebuilt by the toolchain; no in-game tests. |
Wrapping
The file on disk is MLZ0 (containers.md), not RotXor or ZIP.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x0 | 4 | raw[4] | magic |
MLZ0. The game doesn't check it. |
confirmed | C# scan, all 2,085; not read by 0x1401a2a00 (containers.md). |
| 0x4 | 4 | u32 | size |
Size of the effect once unpacked. The game allocates exactly this much and parses that many bytes. | confirmed | C# scan: every file unpacks to exactly this size, all 2,085; 0x1401a29e0, 0x140415190. |
| 0x8 | file size − 8 | raw[n] | block |
One LZ4 block (no frame header, no checksum) that runs to the end of the file. | confirmed | C# scan: the decoder uses up exactly file size − 8 bytes in all 2,085; 0x1401a2a00 (LZ4_decompress_safe of size − 8 bytes). |
- confirmed: LZ4's default compressor (
LZ4_compress_default, acceleration 1) gives back every block byte for byte. Source: K4os.Compression.LZ4 1.3.8 atL00_FAST, 2,085 of 2,085 identical.
Header
The unpacked effect starts with 16 bytes.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x0 | 8 | raw[8] | magic |
EFX0011 and a NUL. The game compares all 8 bytes and loads nothing from a file that differs. |
confirmed | C# scan, all 2,085; 0x1404ab460 (compares with the 8 bytes at 0x140696F90, which xxd shows are EFX0011\0). |
| 0x8 | 4 | f32 | unk_0x8 |
1.0 in every file. The loader doesn't read it. | confirmed | C# scan, all 2,085; not read by 0x1404ab460. |
| 0xC | 4 | u32 | total_size |
The effect's size, equal to the wrapper's size in every file. The loader goes by the buffer's size instead. |
confirmed | C# scan, all 2,085; 0x1404ab460 (ends at buffer + the size it is given). |
Chunks
From 0x10 to the end the file is a list of chunks, each starting with a 12-byte header.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x0 | 4 | u32 | size |
The chunk's size, header included. A multiple of 16 in every chunk. | confirmed | C# scan, all 123,067 chunks. |
| 0x4 | 2 | u16 | unk_0x4 |
0x100 in every chunk. The loader skips a chunk where it isn't. |
— | C# scan; 0x1404ab460. |
| 0x6 | 2 | u16 | type |
What the chunk holds: Chunk types. | confirmed | C# scan; 0x1404ab460, 0x1404ab220 (dispatch on it). |
| 0x8 | 4 | u32 | unk_0x8 |
An offset that the loader hands to the chunk's reader (in the global at 0x140F3B2A0), which uses it to find a second part of the data. size − 16 in most chunks; larger gaps in 0x700, 0x800 and 0xA00 chunks. |
guess | 0x1404ab460; the readers 0x1404a4920, 0x1404a51f0, 0x1404a7250, 0x1404a75b0; C# scan (values). |
| 0xC | size − 12 |
raw[n] | data |
The chunk's data. | confirmed | C# scan. |
- confirmed: the chunks follow each other with no gaps, the first at
0x10and the last ending exactly at the end of the effect. Source: C# scan, all 2,085 files. - likely: they have to: the loader stops only when a chunk ends exactly at the buffer's end, so a size that runs past it makes it read beyond the buffer. Source:
0x1404ab460. - confirmed: the order in every file is the resource chunks (
0xB00, when there are any), then sets (a0x600followed by its0x610/0x620pairs, once per set), then the0x700,0x800and0xA00chunks, each type together. Source: C# scan.
Chunk types
The first bytes of each chunk's data say what it belongs to. Counts are over all 2,085 files.
| Type | Chunks | What it is | First bytes of data |
Confidence | Source |
|---|---|---|---|---|---|
0x600 |
2,873 | A set: one playable effect (a variant). | u16 set ID, 1, 2, 3, … in file order. | confirmed (IDs); likely (role) | C# scan; 0x1404a4a70 (files the set at its ID), 0x1404a49b0 (up to 1,024 sets). |
0x610 |
17,225 | Chunk 0x610: a child of the set before it, in one of the set's 32 slots. |
u16 set ID (always the current set), u8 slot (under 32). | confirmed (set ID); likely (role) | C# scan; 0x1404a4d10, 0x1404a4080 (32 slots). |
0x620 |
35,218 | Chunk 0x620: a child of a 0x610, filed under it by slot. |
u16 set ID, u8 0x610 slot, u8 slot. |
likely | 0x1404a4920, 0x1404a4860. |
0x700 |
2,445 | Chunk 0x700, filed in a table by index; guess: animation or particle data. |
u16 index, u8. | guess | 0x1404a51f0 (table at 0x140F33D00), 0x140503d10. |
0x800 |
26,058 | As 0x700, another table. |
u16 index, u8. | guess | 0x1404a7250 (table at 0x140F33D10), 0x140506e90. |
0xA00 |
25,628 | As 0x700, another table of up to 1,023 entries. |
u16 index, u8. | guess | 0x1404a75b0 (table at 0x140F33D28, cleared over 1,023 entries by 0x1404a72b0), 0x140507240. |
0xB00 |
13,620 | A resource: texture, sound bank or another kind (Resources). | u16 resource ID, u8 kind, u8 index. | confirmed (fields); likely (role) | C# scan; 0x1404a84f0. |
- likely: the loader also reads chunk types
0x500–0x540,0x601,0x611,0x900and0x1000, which no shipped file has.0x1000runs one command of a small command set (0x14050dfe0). Source:0x1404ab220; C# scan (no such chunk). - likely: the game uses each chunk in place, as its live data: the set reader copies the set ID to data
0x38and zeroes data bytes0x3C–0x13Bof a0x600chunk, and the other readers write fields inside their chunks. So each chunk type's data must be at least its full size. Source:0x1404a4a70,0x1404a51f0,0x1404a7250,0x1404a75b0; How the game reads it. confirmed: the files already hold those values: the set ID at data0x38and zeros at0x3C–0x13Bin all 2,8730x600chunks. Source: C# scan.
Resources
A 0xB00 chunk's data starts with a 4-byte key: u16 resource ID (0 in every chunk), a kind letter, and an index from 1. confirmed: no file has two resources with the same key. Source: C# scan.
| Kind | Chunks | What it is | Confidence | Source |
|---|---|---|---|---|
P |
3,374 | Kind P, unknown; guess: a mesh (vertex positions, texture coordinates and a colour). |
guess | Hex inspection; 0x14050c2f0, 0x14050c9a0. |
Q |
251 | Unknown. | — | 0x14050d1a0, 0x14050d200. |
R |
9,637 | A texture, as below. | confirmed | C# scan; 0x140507720, 0x1405077e0; 0x1404a8760 (finds an R resource by name). |
S |
0 | Unknown; read by the game, in no file. | — | 0x14050c0d0, 0x14050c160. |
T |
277 | Kind T, unknown; guess: a mesh with an index count and vertex positions. |
guess | Hex inspection; 0x140508df0, 0x14050bd20. |
W |
81 | A sound bank, as below. | confirmed | C# scan; 0x14050d500. |
- likely: a resource whose key is already loaded is updated rather than added, and any other kind is ignored. Source:
0x1404a84f0.
R: texture
Offsets are from the start of the chunk's data.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x00 | 4 | raw[4] | key |
Resource ID 0, R, index. |
confirmed | C# scan. |
| 0x04 | 4 | u32 | unk_0x04 |
1 in every texture. | confirmed | C# scan, all 9,637. |
| 0x08 | 4 | u32 | width |
Width in pixels; the same as the btx's. |
confirmed | C# scan: equal in all 9,458 with a btx. |
| 0x0C | 4 | u32 | height |
Height in pixels; the same as the btx's. |
confirmed | C# scan, as above. |
| 0x10 | 4 | f32 | unk_0x10 |
1.0 (3,340), 2.0 (9), 4.0 (6,281) or 8.0 (7). | confirmed (values) | C# scan. |
| 0x14 | 4 | u32 | btx_size |
Size of the btx that follows; 0 for a placeholder. The chunk header's unk_0x8 is btx_size + 0x40 in every texture. |
confirmed | C# scan, all 9,637. |
| 0x18 | 32 | raw[32] | name |
The texture's name, each byte XORed with 0xAB, padded with 0xAB (a NUL once decoded): chr_make_word_1_00, card_normal, … |
likely | C# scan (every name read decodes to text); 0x1404a8760. |
| 0x38 | btx_size |
raw[n] | btx |
The texture, a btx (starts btx\0). |
confirmed | C# scan: at 0x38 in all 9,458 with btx_size > 0. |
0x38 + btx_size |
to the chunk end | raw[n] | tail |
12 bytes in 7,588 textures, up to 5,644 in the rest. Unknown. | confirmed (sizes) | C# scan. |
- confirmed: 179 textures have
btx_size0 and no image: 176 named__dummy__card_normal, and__dummy__god_00–02. Source: C# scan. - likely: these are slots the game fills at run time: character creation copies the texture named
card_normalfrom one loaded effect into__dummy__card_normalof another, and builds__dummy__god_%.2dnames the same way. So these names have to stay as they are. Source:0x1404afcc0(0x140412220→0x1404a8760, by name).
W: sound bank
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x00 | 4 | raw[4] | key |
Resource ID 0, W, index. |
confirmed | C# scan. |
| 0x04 | 4 | u32 | bank_id |
5000–5066; 5031 is in 11 files, 5032 in 8, the rest in one. | confirmed (values) | C# scan, all 81. |
| 0x08 | 4 | u32 | unk_0x08 |
0 in the files. The game writes the bank's handle here once it's registered. | likely | C# scan; 0x1404a87e0 (stores the result of 0x140285930). |
| 0x0C | 4 | u32 | sabf_size |
Size of the bank. | confirmed | C# scan: the chunk ends 4 bytes after it in all 81. |
| 0x10 | sabf_size |
raw[n] | sabf |
The sound bank (starts sabf). |
confirmed | C# scan, all 81. |
0x10 + sabf_size |
4 | raw[4] | padding |
To the chunk end. | confirmed (size) | C# scan. |
- likely: the game registers the bank with the sound system (
0x140285930) and keepsbank_idand the handle in a table of 64 entries. Source:0x1404a87e0,0x1404a87b0.
How the game reads it
Every fact here is static analysis of the unpacked executable, so at most likely; the functions are in Effects.
- likely: once the
MLZ0file is unpacked, the game calls the loader (0x1404ab460) with the buffer and the wrapper'ssize. It compares the 8-byte magic and returns without loading anything if it differs; it reads neitherunk_0x8nortotal_size. Source:0x1404141f0(c_all.efz),0x140414fe0(every other effect). - likely: it walks the chunks from
0x10, bysize, until one ends exactly at the end of the buffer. A chunk whoseunk_0x4isn't0x100is skipped. Runs of0x600/0x610/0x620,0x700,0x800and0xA00chunks are each handed to their reader as one block; every other chunk goes to0x1404ab220, which picks the reader bytypeand ignores unknown types. Source:0x1404ab460,0x1404ab220. - likely: both effect loaders set a flag (bit 3 of byte 2 of the effect's resource) that makes the loader use the chunks where they are, in the unpacked buffer, which is kept until the effect is freed. Without the flag it would copy each run into memory of its own, 16 bytes at a time. Source:
0x1404141f0,0x140414fe0,0x1404ab460,0x1404ab1b0. - likely: there are no CRCs or checksums, and no count of chunks or sets in the header: what the game needs is the right sizes (the wrapper's
size, each chunk'ssize) and well-formed chunks. Source: the functions above.
Rebuilding
- confirmed: wrapping the unpacked effect back as
MLZ0(the magic,size, then the block from LZ4's default compressor) gives back every original file byte for byte. Source: C# scan with K4os.Compression.LZ4 1.3.8, 2,085 of 2,085. - likely: the game would also take any other valid LZ4 block, since it decompresses with
LZ4_decompress_safeand checks nothing else; but the file's size inFileTable.binmust be exact, because the game takes file size − 8 as the block's length (filetable.md). - likely: a rebuilt effect keeps the header as it is, every chunk's
sizea multiple of 16 and exact,unk_0x40x100, the set IDs 1, 2, 3, … in order, and the texture names that game code looks up (R: texture).
Leads
- The data of each chunk type: the set (
0x600), its children (0x610,0x620) and the0x700,0x800and0xA00layouts, starting from their readers (0x1404a4a70,0x1404a4d10,0x1404a4920,0x140503d10,0x140506e90,0x140507240) and the frame-timed tracks at0x1404a2d90. Whatunk_0x8in the chunk header points at in each type. - The resource kinds
P,QandT, and what follows a texture'sbtx(tail). - Whether a texture's
unk_0x10(1.0–8.0) is a scale for the image. - Emitters:
bl::efx::Emitteris a game class, but of another system: no code in the Libra regions (0x1404a2c60–0x1404aba48,0x140500d60–0x14050e77e) calls into abl::efxregion, so chunks0x610/0x620keep positional names until their readers show what they are. - "Effect" means two things in the game: visual effects (
effect/,EffectManager.cpp; vfx) and skill effects (EFFECTTEXT_LC_EFFECT_*); this doc is about the first. - The command set of chunk type
0x1000(0x14050dfe0, opcodes 0–5, 8–13, 17, 19–21, 23 and 25–28, one command per call): no shipped effect uses it, so it matters only to a mod that adds one. - The
btxtextures andsabfbanks inside effects are the same formats as elsewhere inData/; their docs cover them.