-
-
Notifications
You must be signed in to change notification settings - Fork 14
Archives
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 |
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];
@@ -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;
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 |
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;
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);
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];
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>;