Skip to content

Archives

ShadelessFox edited this page Oct 31, 2024 · 7 revisions
Game Format
Horizon Zero Dawn PackFile Archive
Death Stranding PackFile Archive (Encrypted)
Horizon Forbidden West (PC) DirectStorage Archive
Horizon Zero Dawn Remastered (PC) DirectStorage Archive

PackFile Archive

All assets are stored inside archives.

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);

DirectStorage Archive

Unlike PackFiles, DirectStorage archives contain raw data split into compressed chunks. The contents can't be extracted without external description or metadata.

Note

In Horizon Forbidden West, the contents of .core and .core.stream files is described by the LocalCacheWinGame\package\streaming_graph.core file stored as a regular core file.

Note

In Horizon Zero Dawn Remastered, the contents of .core and .stream files is described by the LocalCacheDX12\package\PackFileLocators.bin file. For information about its structure, see this paragraph.

typedef struct {
    char magic[4]; Assert(magic == "DSAR");
    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];

PackFileLocators

Used in Horizon Zero Dawn Remastered.

typedef struct {
    uquad Name <format=hex>; // Presumably a MurmurHash3 hash of the name
    uint Offset;
    uint Length;
} PackfileFile <read=Str("%016Lx @ %d (%d bytes)", this.Name, this.Offset, this.Length)>;

typedef struct {
    uint NameLength <hidden=true>;
    char Name[NameLength];
    uint NumFiles <hidden=true>;
    PackfileFile Files [NumFiles];
} Packfile <read=(this.Name)>;

uint NumPackfiles <hidden=true>;
Packfile Packfiles [NumPackfiles] <optimize=false>;