Line 14: |
Line 14: |
| | | |
| ===Header version 3=== | | ===Header version 3=== |
− | * byte magic[8]: "NadeoPak"
| + | byte magic[8]; // "NadeoPak" |
− | * uint32 version (3)
| + | uint32 version; // 3 |
− | * uint64 headerIV
| + | uint64 headerIV; |
− | * Blowfish encrypted:
| + | Blowfish encrypted: |
− | ** uint128 headerMD5
| + | { |
− | ** uint32 metaDataOffset
| + | uint128 headerMD5; |
− | ** uint32 dataOffset
| + | uint32 gbxHeadersStart; // offset to metadata section |
− | ** if version >= 2
| + | uint32 dataStart; |
− | *** uint32 metaDataUncompressedSize
| + | if version >= 2: |
− | *** uint32 metaDataCompressedSize
| + | { |
− | ** if version >= 3
| + | uint32 gbxHeadersSize; |
− | *** uint128
| + | uint32 gbxHeadersComprSize; |
− | ** uint32 flags
| + | } |
− | ** uint32 numFolders
| + | if version >= 3: |
− | ** FolderEntry folders[numFolders]
| + | uint128 unused; |
− | *** uint32 parentFolderIndex (index into folders; -1 if this is a root folder)
| + | uint32 flags; |
− | *** string name
| + | uint32 numFolders; |
− | ** Set up ivXor
| + | FolderDesc folders[numFolders] |
− | ** uint32 numFiles
| + | { |
− | ** FileEntry files[numFiles]
| + | uint32 parentFolderIndex; // index into folders; -1 if this is a root folder |
− | *** uint32 folderIndex (index into folders)
| + | string name; |
− | *** string name
| + | } |
− | *** uint32
| + | Set up ivXor; |
− | *** uint32 uncompressedSize
| + | uint32 numFiles; |
− | *** uint32 compressedSize
| + | FileDesc files[numFiles] |
− | *** uint32 offset
| + | { |
− | *** uint32 [[Class IDs|classID]] (indicates the type of the file)
| + | uint32 folderIndex; // index into folders |
− | *** uint64 flags
| + | string name; |
| + | uint32 unknown; |
| + | uint32 uncompressedSize; |
| + | uint32 compressedSize; |
| + | uint32 offset; |
| + | uint32 [[Class IDs|classID]]; // indicates the type of the file |
| + | uint64 flags; |
| + | } |
| + | } |
| | | |
| ====Header MD5==== | | ====Header MD5==== |
Line 71: |
Line 79: |
| There is one special exception with .gbx files. If the specific class ID is 0x07031000 (Control::CControlText), 0x07001000 (Control::CControlBase) is used as input instead. | | There is one special exception with .gbx files. If the specific class ID is 0x07031000 (Control::CControlText), 0x07001000 (Control::CControlBase) is used as input instead. |
| | | |
− | ===Header versions 6-8=== | + | ===Header versions 6+=== |
− | byte magic[8]: "NadeoPak" | + | byte magic[8]; // "NadeoPak" |
− | uint32 version (6, 7, 8) | + | uint32 version; |
| if (version >= 6) | | if (version >= 6) |
| { | | { |
− | uint256 ContentsChecksum; // Checksum Sha256 of the pack contents starting at next byte
| + | uint256 ContentsChecksum; // Checksum Sha256 of the pack contents starting at next byte |
− | uint32 DecryptFlags;
| + | struct SHeaderFlagsUncrypt |
− | if (version >= 7)
| |
− | {
| |
− | struct SAuthorInfo | |
| { | | { |
− | uint32 version;
| + | uint32 IsHeaderPrivate : 1; |
− | string Login;
| + | uint32 UseDefaultHeaderKey : 1; |
− | string Nick;
| + | uint32 IsDataPrivate : 1; |
− | string Zone;
| + | uint32 IsImpostor : 1; |
− | string ExtraInfo;
| + | uint32 __Unused__ : 28; |
− | } | + | }; |
− | string Comment;
| + | if (version >= 15) |
− | uint128 unused;
| + | uint32 HeaderMaxSize; // 0x4000 = Small (16 KB), 0x100000 = Big (1 MB), 0x1000000 = Huge (16 MB) |
− | if (version >= 8)
| + | if (version >= 7) |
− | {
| |
− | string CreationBuildInfo;
| |
− | string AuthorUrl;
| |
− | } | |
− | }
| |
− | }
| |
− | | |
− | ===Header versions 9+===
| |
− | byte magic[8]: "NadeoPak"
| |
− | uint32 version (9 or higher)
| |
− | if (version >= 6)
| |
− | {
| |
− | uint256 ContentsChecksum; // Checksum Sha256 of the pack contents starting at next byte
| |
− | uint32 DecryptFlags;
| |
− | if (version >= 15)
| |
− | uint32 HeaderMaxSize; // 0x4000 = small (16 KB), 0x100000 = big (1 MB), 0x1000000 = huge (16 MB)
| |
− | if (version >= 9)
| |
− | {
| |
− | struct SAuthorInfo
| |
− | {
| |
− | uint32 version;
| |
− | string Login;
| |
− | string Nick;
| |
− | string Zone;
| |
− | string ExtraInfo;
| |
− | }
| |
− | string ManialinkUrl;
| |
− | if (version >= 13)
| |
− | string DownloadUrl;
| |
− | uint64 CreationDate;
| |
− | string Comment;
| |
− | if (version >= 12) | |
| { | | { |
− | string Xml;
| + | struct SAuthorInfo |
− | string TitleID;
| + | { |
| + | uint32 Version; |
| + | string Login; |
| + | string Nick; |
| + | string Zone; |
| + | string ExtraInfo; |
| + | }; |
| + | if (version < 9) |
| + | { |
| + | string Comment; |
| + | uint128 unused; |
| + | } |
| + | if (version == 8) |
| + | { |
| + | string CreationBuildInfo; |
| + | string AuthorUrl; |
| + | } |
| + | if (version >= 9) |
| + | { |
| + | string ManialinkUrl; |
| + | if (version >= 13) |
| + | string DownloadUrl; |
| + | uint64 CreationDate; // Win32 FILETIME structure |
| + | string Comment; |
| + | if (version >= 12) |
| + | { |
| + | string Xml; |
| + | string TitleID; |
| + | } |
| + | string UsageSubDir; // to known the kind of pack it is |
| + | string CreationBuildInfo; |
| + | uint128 unused; |
| + | if (version >= 10) |
| + | { |
| + | uint32 NbIncludedPacks; |
| + | struct SIncludedPacksHeaders |
| + | { |
| + | uint256 ContentsChecksum; // Sha256 |
| + | string Name; |
| + | SAuthorInfo AuthorInfo; |
| + | string InfoManialinkUrl; |
| + | uint64 CreationDate; |
| + | string Name; |
| + | if (version >= 11) |
| + | uint32 IncludeDepth; |
| + | } IncludedPacks[]; |
| + | } |
| + | } |
| } | | } |
− | string UsageSubDir; // to known the kind of pack it is | + | Blowfish encrypted: // Unencrypted, if (DecryptFlags & 0x3) == 0 |
− | string CreationBuildInfo;
| |
− | uint128 unused;
| |
− | if (version >= 10)
| |
| { | | { |
− | uint32 NbIncludedPacks;
| + | uint128 Checksum; |
− | struct SIncludedPacksHeaders
| + | uint32 GbxHeadersStart; // Offset to the metadata section |
− | {
| + | if version < 15: |
− | uint256 ContentsChecksum; // Sha256 | + | uint32 DataStart; // If version >= 15: DataStart = HeaderMaxSize |
− | string Name; | + | if version >= 2: |
− | SAuthorInfo AuthorInfo; | + | { |
− | string InfoManialinkUrl; | + | uint32 GbxHeadersSize; |
− | uint64 CreationDate; | + | uint32 GbxHeadersComprSize; |
− | string Name; | + | } |
− | if (version >= 11)
| + | if version >= 14: |
− | uint32 IncludeDepth;
| + | uint128 unused; |
− | } IncludedPacks[];
| + | if version >= 16: |
| + | uint32 FileSize; |
| + | if version >= 3: |
| + | uint128 unused; |
| + | if version == 6: |
| + | SAuthorInfo; |
| + | uint32 Flags; |
| + | uint32 NumFolders; |
| + | FolderDesc Folders[NumFolders] |
| + | { |
| + | int32 FolderIndexParent; |
| + | string FolderName; |
| + | } |
| + | uint32 NumFiles; |
| + | FileDesc Files[NumFiles] |
| + | { |
| + | int32 FolderIndex; |
| + | string FileName; |
| + | uint32 unknown; |
| + | uint32 UncompressedSize; |
| + | uint32 CompressedSize; |
| + | uint32 Offset; |
| + | uint32 [[Class IDs|classID]]; |
| + | if version >= 17: |
| + | uint32 Size; |
| + | if version >= 14: |
| + | uint128 Checksum; |
| + | struct SFileDescFlags |
| + | { |
| + | uint32 IsHashed : 1; |
| + | uint32 PublishFid : 1; |
| + | uint32 Compression : 4; |
| + | uint32 IsSeekable : 1; |
| + | uint32 _Unknown_ : 1; |
| + | uint32 __Unused1__ : 24; |
| + | uint32 DontUseDummyWrite : 1; |
| + | uint32 OpaqueUserData : 16; |
| + | uint32 PublicFile : 1; |
| + | uint32 ForceNoCrypt : 1; |
| + | uint32 __Unused2__ : 13; |
| + | }; |
| + | } |
| } | | } |
− | }
| |
| } | | } |
| | | |
| ===Data=== | | ===Data=== |
− | The content of each file starts at Header.dataOffset + FileEntry.offset in the .pak file. First, an 8-byte plaintext IV is read. Then, FileEntry.compressedSize bytes are read and decrypted using Blowfish in CBC mode, using the same key that was used to decrypt the header. If FileEntry.flags & 0x7C is not zero, the file is compressed and should be decompressed using zlib deflate after decryption (it will end up at FileEntry.uncompressedSize bytes). | + | The content of each file starts at Header.dataStart + FileDesc.offset in the .pak file. From version 15, the data block starts at HeaderMaxSize. First, an 8-byte plaintext IV is read. Then, FileDesc.compressedSize bytes are read and decrypted using Blowfish in CBC mode, using the same key that was used to decrypt the header. If FileDesc.flags & 0x7C is not zero, the file is compressed and should be decompressed using zlib deflate after decryption (it will end up at FileDesc.uncompressedSize bytes). |
| | | |
| The type of the file can be found from the extension in the name, or, if this is not available (many file names are actually just hashes), from the [[Class IDs|class ID]]. | | The type of the file can be found from the extension in the name, or, if this is not available (many file names are actually just hashes), from the [[Class IDs|class ID]]. |