An XP3 file begins with an 11-byte magic number, followed by an 8-byte offset from the start of the header (usually the start of the file), info_offset. All numbers are little-endian.
| offset | length | contents | notes |
|---|---|---|---|
| 0x00 | 11 | magic | XP3\x0D\x0A\x20\x0A\x1A\x8B\x67\x01 |
| 0x0B | 8 | info_offset | |
| 0x13 | 4 | version | 1 = XP3v2; absent in older files |
| 0x17 | 8 | table_size | present only if version field is present |
| 0x1F | 1 | flags | present only if version field is present |
| 0x?? | ?? | file_data | |
| info_offset | 17 + compressed_size | file_info |
At info_offset there are two possibilities. The first byte may be 0x80 (in case of a file generated by KiriKiri Z), in which case skip 8 bytes and read the true info offset. Jump there and continue.
| info_offset + x | length | contents | notes |
|---|---|---|---|
| 0x00 | 1 | flags | 0x80 bit is set; marks KiriKiriZ compatibility |
| 0x01 | 8 | table_size | size of the file table |
| 0x09 | 8 | info_offset | true info offset |
Otherwise, the first byte should be 0x01:
| info_offset + x | length | contents | notes |
|---|---|---|---|
| 0x00 | 1 | compressed | whether the table is compressed |
| 0x01 | 8 | compressed_size | |
| 0x09 | 8 | original_size | |
| 0x11 | compressed_size | file_table | if compressed, decompress with zlib (LZ77 + Huffman coding) |
The end of the file_table should be the end of the file.
The decompressed file table is a sequence of tagged chunks. Each chunk begins
with a 4-byte magic tag and an 8-byte length field indicating the size of the
chunk's contents.
| offset | length | contents | notes |
|---|---|---|---|
| 0x00 | 4 | tag | e.g. File, eliF, … |
| 0x04 | 8 | length | |
| 0x0C | length | data |
The table consists of eliF ("File" in little-endian, 0x656C6946) entries
paired with File entries.
eliF chunkContains a UTF-16LE encoded filename and a key used to associate it with its
corresponding File entry.
File chunkContains sub-chunks: info, segm, adlr, and time.
info: a flags variable, compressed size, decompressed size, and whatsegm: one or more segments, each containing an offset to the file's dataadlr: contains the key used to match this File entry to its eliFtime: a Unix timestamp for the file's creation date.Some XP3 archives (e.g. Nekopara) encrypt file contents. Encryption is
symmetric XOR. A base key is derived by XORing the game's master key with the
file key from the adlr chunk. A one-byte key is then derived by XORing each
byte of the base key together. For some games, the least-significant byte of
the base key is used to encrypt the first byte of the file, with the derived
one-byte key used for subsequent bytes. Games have default fallback values for
keys that are too simple.
Because most binary files begin with a known magic number, a known-plaintext
attack against the first byte is sufficient to recover the encryption key.
| Journal | Tags | Sources |
|---|---|---|
| Deciphering XP3 files | XP3 archives, reverse engineering | |
| Journal: 2023-10-01 04:08:49+00:00 | XP3 archives |