Skip to content

Instantly share code, notes, and snippets.

@Radfordhound
Last active August 30, 2019 23:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Radfordhound/ec23b24251083f76db442406dce7b9e2 to your computer and use it in GitHub Desktop.
Save Radfordhound/ec23b24251083f76db442406dce7b9e2 to your computer and use it in GitHub Desktop.
Tokyo Olympics 2020 .pac format specification
// Tokyo Olympics 2020 pac format specification 2.0
// By: Radfordhound
// Heavily based off of Skyth's Forces pac specification
HeaderV4 Header;
EmbeddedPAC EmbeddedPACs[]; // These are LZ4 compressed and need to be decompressed first
struct HeaderV4
{
char[8] Signature = "PACx402L";
uint PacID; // Random number? Might actually be date created/modified.
uint FileSize;
uint RootOffset;
uint RootCompressedSize;
uint RootUncompressedSize;
PacType Type = 1; // Always 1? Oh gosh can these giant pacs have splits please no
ushort Constant = 0x208; // No idea what this is for
uint ChunkCount;
Chunk Chunks[ChunkCount];
// Pad to a 0x16 offset
}
enum PacType : ushort
{
// PAC has no splits
HasNoSplit = 1,
// PAC is a split
IsSplit = 2,
// PAC has splits
HasSplit = 5
}
struct Chunk
{
// When decompressing the root pac allocate a buffer of
// size Header.RootUncompressedSize, then loop through these
// chunks and decompress each one, one-by-one, into that buffer.
// If you try to decompress all at once instead the data can be corrupted.
uint CompressedSize;
uint UncompressedSize;
}
// Once you've decompressed the root PAC it'll look like this:
struct EmbeddedPAC
{
HeaderV3 PACHeader;
NodeTree TypesTree; // Type nodes point to file node trees
SplitsInfo SplitInfo; // If there are no splits this isn't present
DataEntry DataEntries[]; // If there are no data entries this isn't present
}
struct HeaderV3
{
char[8] Signature = "PACx402L"; // Yes, it's the same as HeaderV4 even though it's a different format.
uint PacID = Header.PacID; // Always same as Header.PacID?
uint FileSize; // Size of the pac.
uint NodesSize; // Size of the section containing all of the NodeTrees and their Nodes.
uint SplitsInfoSize; // Size of the section containing all of the split information.
uint DataEntriesSize; // Size of the section containing all of the DataEntries.
uint StringTableSize; // Size of the string table.
uint DataSize; // Size of the section containing all of the file's data.
uint OffsetTableSize; // Size of the offset table.
PacType Type;
ushort Constant = 0x108;
uint SplitCount;
}
struct NodeTree
{
uint NodeCount;
uint DataNodeCount;
ulong NodesOffset;
ulong DataNodeIndicesOffset;
Node Nodes[NodeCount];
}
struct Node
{
// Either the type name or the file name, depending on what type of node this is. See below.
ulong NameOffset;
// If this node is part of the first NodeTree in the pac, this points to
// another NodeTree which contains files of the type described by this node's name.
// Otherwise, this points to a DataEntry containing information on the file.
ulong DataOffset;
ulong ChildIndexTableOffset;
int ParentIndex;
int GlobalIndex;
int DataIndex;
ushort ChildCount;
bool HasData;
byte FullPathSize; // Not counting this node's name.
}
struct SplitsInfo
{
ulong SplitCount; // Could also be a uint with padding but this makes more sense to me
ulong SplitEntriesOffset; // Yes, literally points to next 8 bytes.
SplitEntry SplitEntries[SplitCount];
Chunk SplitChunks[];
}
struct SplitEntry
{
ulong SplitName; // E.G. ath10n_trr_cmn.pac.000
uint SplitCompressedSize;
uint SplitUncompressedSize;
uint SplitOffset; // Where this split is within the big pac that contains this pac
uint SplitChunkCount;
ulong SplitChunksOffset;
}
struct DataEntry
{
// Random number? Not the same as Header.PacID or PACHeader.PacID, like it was in Forces.
// Might actually be date created/modified.
uint PacID;
ulong DataSize;
char Padding1[4]; // Always 0?
ulong DataPosition;
char Padding2[8]; // Always 0?
ulong ExtensionPosition;
DataType DataType;
}
enum DataType : ulong
{
RegularFile = 0,
NotHere = 1,
BINAFile = 2
}
@Zicheng135
Copy link

Hello Rad, thanks for sharing! What do you use to decompress? I tried https://github.com/lz4/lz4 and always got empty result.

@Radfordhound
Copy link
Author

Hey, np! That's odd; I used lz4 as well actually in my tests and had no issues.
Make sure you're decompressing one "chunk" (the structures in this spec) of data at a time rather than trying to decompress the whole thing at once.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment