Secret Files: Tunguska SPR
Back to index | Edit this page
Contents
SPR
- Format type: Archive
- Endianness: Little-endian
Format Specifications
uint32 {4} - Unknown
uint32 {4} - Decryption key seed
uint32 {4} - Unknown
uint32 {4} - Unknown
int32 {4} - Number of strings
int32 {4} - String table offset
int32 {4} - String table size
int32 {4} - Number of files
int32 {4} - File table offset
int32 {4} - File table size
int32 {4} - Number of directories
int32 {4} - Directory table offset
int32 {4} - Directory table size
byte {x} - File data
// string table data (encrypted)
- // for each string
- uint16 {2} - String length
- uint16 {2} - String length
- // for each string
- char {x} - String data
- char {x} - String data
// directory table data (encrypted)
- // for each directory
- int32 {4} - Directory name (index into string table)
- int32 {4} - Parent directory (index into directory table, 0x1effffff for root)
- int32 {4} - Directory name (index into string table)
// file table data (encrypted)
- // for each file
- int32 {4} - Parent directory (index into directory table, 0x1effffff for root)
- int32 {4} - Name prefix (index into string table)
- int32 {4} - Name suffix (index into string table)
- uint32 {4} - CRC32 of uncompressed data (might not be set, zero in that case)
- // file date (as Windows _SYSTEMTIME type)
- uint16 {2} - Year
- uint16 {2} - Month
- uint16 {2} - Day of week
- uint16 {2} - Day
- uint16 {2} - Hour
- uint16 {2} - Minute
- uint16 {2} - Second
- uint16 {2} - Millisecond
- uint16 {2} - Year
- int32 {4} - Size of uncompressed data
- int32 {4} - Compression header offset (0xffffffff if file is not compressed)
- int32 {4} - Compression header size (zero if file is not compressed)
- int32 {4} - File data offset
- int32 {4} - File data size
- int32 {4} - Parent directory (index into directory table, 0x1effffff for root)
Notes and Comments
Table decryption
Each of the three structure tables has been xor-encrypted. The decryption depends on the seed value given in the header as well as a partly fixed key domain value. The decryption key will change for each byte in the buffer (depending on the previously seen bytes), as shown in this pseudo code:
KeyDomain := 0x880ec * Seed;
Key := ((Seed shl 8) or ((not Seed) and 0xff));
for i := 0 to Length(Data) - 1 do
begin
- Key := (Key * Key) mod KeyDomain;
- Buffer[i] := Buffer[i] xor (Key and 0xff);
- Key := Key + Buffer[i] + 1;
end;
Additional note: Other Deep Silver games use exactly the same file structure and encryption algorithm as Secret Files: Tunguska, but the key domain value for these games are different:
- Secret Files 2: Puritas Cordis: 0x880a6
- Lost Horizon: 0x8a0dc
Data decompression
In cases of compressed file data, the compression header offset and size values will be set to appropriate values. Note that the data offset value will then point to the compressed data block. The compression header itself might look different for various compression techniques, but only one is known to be used in the game's archives, while another type can be found in savegame files.
SLZX
The compression header of the SLZX method is constructed by a four-char identifier (SLZX) followed by a int32 value storing the uncompressed data size. The compressed data block is prepended by an int32, also containing the uncompressed size. (Please note that this results in three distinct places where the uncompressed size can be found: The file table entry, the SLZX compression header and the beginning of the compressed data block.)
The compression technique itself is a variant of the common LZSS in the following sense:
- The interpretation of the bit flags is reversed (0 indicates a literal copy, while 1 indicates a buffer reference).
- An offset/length pair is given by a little-endian uint16 value. The uppermost five bits store the length, while the lower 11 bits contain the offset value. After each such offset/length pair there follows exactly one additional literal character.
- The history buffer (2048 bytes in size) is not circular, but slides along the output. All offsets are absolute buffer references. (Please note that the buffer "window" only starts sliding forward as soon as 2048 bytes of data have been output.)
SSHC
The SSHC compression type is mainly (but not only) found in savegame files. The compression technique used is a standard Huffman encoding. The compressed data has the following structure:
uint32 {4} - Uncompressed data size
uint32 {4} - Compressed data size (in bytes)
uint32 {4} - Number of Huffman tree nodes
uint32 {4} - Exact compressed data size (in bits)
// Huffman tree definition
- // for each node
- uint16 {4} - Parent node ID
- uint16 {4} - Left child node ID
- uint16 {4} - Right child node ID
- uint16 {4} - Node ID (equals literal value for leaf nodes)
Each Huffman tree node is defined by one entry, and the tree is usually built bottom-up. A value of 0x0fff is used to denote a null ID. The children node IDs can be used to determine whether the current node is an inner node or a leaf node: For a leaf node, both children node IDs will be null. The tree definition usually ends with a the root node, using null for the parent node ID.
Immediately after the tree definition, the Huffman-encoded data follows. The encoding runs from each byte's LSB to the MSB.
Interestingly, not all files using the SSHC compression method actually get smaller in size. When taking into account the overhead caused by the tree definition, some files get even noticeably larger. In the case of save games, this might be an accepted side-effect in order to hamper save game hacking.
MultiEx BMS Script
None written yet.
Supported by Programs
Links
None
Games