Skip to content

Archives

ShadelessFox edited this page Mar 26, 2024 · 9 revisions
Game Format
Horizon Zero Dawn Packfile Archive
Death Stranding Packfile Archive (Encrypted)
Horizon Forbidden West (PC) Data Stream Archive

Packfile Archive

typedef struct {
    uint64 offset;
    uint32 size;
    uint32 key;
} Span <read=Str("[%u .. %u] (%u bytes)", this.offset, this.offset + this.size, this.size)>;

typedef struct {
    uint32 id;
    uint32 key;
    uint64 hash;
    Span span;
} File;

typedef struct {
    Span span;
    Span compressed_span;
} Chunk;

typedef struct {
    uint32 magic; Assert(magic == 0x20304050);
    uint32 key;
    uint64 file_size;
    uint64 data_size;
    uint64 file_entry_count;
    uint32 chunk_entry_count;
    uint32 chunk_entry_size;
} Header;

LittleEndian();

Packfile packfile <open=true>;
File files[file_entry_count];
Chunk chunks[chunk_entry_count];

Packfile Archive (Encrypted)

@@ -17,7 +17,7 @@ typedef struct {
 } Chunk;
 
 typedef struct {
-    uint32 magic; Assert(magic == 0x21304050);
+    uint32 magic; Assert(magic == 0x20304050);
     uint32 key;
     uint64 file_size;
     uint64 data_size;

Decryption keys

Name Key
HEADER_KEY 43 94 3A FA 62 AB 1C F4 1C 81 76 F3 3E 9E A8 D2
DATA_KEY 37 4A 08 6C 95 9D 15 7E E8 F7 5A 3D 3F 7D AA 18

Decoding the header

Routine for decoding a 32-byte header buffer:

void decrypt_header(ubyte data[32], uint32 key_0, uint32 key_1) {
    ubyte buffer[16];
    ubyte hash[16];

    // Decode the first part of the buffer
    buffer[0:15] = HEADER_KEY;
    buffer[0:3] = key_0;
    data[0:15] = data[0:15] ^ murmurhash3(buffer[0:15], 42);

    // Decode the second part of the buffer
    buffer[0:15] = HEADER_KEY;
    buffer[0:3] = key_1;
    data[16:31] = data[16:31] ^ murmurhash3(buffer[0:15], 42);
}

When decoding the Header, fields magic and key are skipped. The key + 1 is used as the second key:

Header header = ...;
uint32 key_0 = header.key;
uint32 key_1 = header.key + 1; // No tricks here, just increment the key by 1

decrypt_header(&header.file_size, key_0, key_1);

Other structures, such as Chunk and File, are 32-bytes long, so they are processed from the start:

Chunk chunk = ...;
uint32 key_0 = chunk.span.key;
uint32 key_1 = chunk.compressed_span.key;

decrypt_header(&chunk, key_0, key_1);

// Restore keys so we can reuse them later when encrypting the data back
chunk.span.key = key_0;
chunk.compressed_span.key = key_1;

Decoding the data

Routine for decoding an arbitrary size data buffer:

void decrypt_data(ubyte data[], Span span) {
    ubyte hash[16];

    // Generate a hash for decoding the data buffer
    hash[0:7] = span.offset;
    hash[8:11] = span.size;
    hash[12:15] = span.key;
    hash[0:15] = md5(murmurhash3(hash[0:15], 42));
    
    // Decode the data buffer
    for (uint32 i = 0; i < span.size; i++) {
        data[i] = data[i] ^ hash[i % 16];
    }
}

The actual data is decrypted the entire chunk at once:

Chunk chunk = ...;
ubyte data[] = read(offset = chunk.compressed_span.offset, size = chunk.compressed_span.size);

// Decode using the decompressed span
decrypt_data(data, chunk.span);

Data Stream Archive

typedef struct {
    uint32 magic; Assert(magic == 0x52415344);
    uint16 version_major; Assert(version_major == 3);
    uint16 version_minor; Assert(version_minor == 1);
    uint32 chunk_count;
    uint32 first_chunk_offset;
    uint64 total_size;
    char   padding[8];
} Header;

typedef enum <ubyte> {
    Compression_LZ4 = 3
} Compression;

typedef struct {
    uint64 offset;
    uint64 compressed_offset;
    uint32 size;
    uint32 compressed_size;
    ubyte  type; // 3 - LZ4
    ubyte  padding[7];
} Chunk;

LittleEndian();
Header header;
Chunk chunks[header.chunk_count];

Clone this wiki locally