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