-
Notifications
You must be signed in to change notification settings - Fork 0
Asobo DPC File Format Specification
Treat this document as if it were a tattered notebook you found while rummaging through my desk drawers; just because it’s formatted well doesn’t mean it’s true. Any information on this page is subject to change. Variable and structure names are completely arbitrary and may not properly represent the purpose of the field, read the comments under each element for a more complete understanding of what it does and what values are legal.
It is strongly recommended that you read this document in its entirety before using it as a reference because the minimal comments and arbitrary names are better understood in the context of the whole specification. I often exclude the full sequence of structs containing a variable for the sake of brevity; this is most common with size and count variables. If you are unsure of where a variable is oncoming from, it is most likely in the struct's associated header member or a description struct, such as Block Description, that is not directly in its ancestry. For this reason, a passing familiarity with the structures specified in this document is required and I would encourage you to read the whole document.
The Data PC files used in FUEL are asset archives that contain serialized class objects such as textures, models, animations, sounds, particle systems, etc. Internally these types of files are called BigFiles.
A fully specified older version of the BigFile format from the TotemTech/ToonTech engine can be found on the Chum World wiki. A more recent, but still old, version of the BigFile format can be found at zouna-templates-docs/templates/rat/Asobo DXX v1.06.63.0X.bt.
The DPC file extension is used across Asobo’s catalog liberally; the structure of these files may change dramatically from title to title or not at all for several years. The DPC files found in FUEL have special considerations for the asset streaming capabilities of ACE (Asobo Conception Engine), such as lazy loading of object data and parallel loading procedures at launch. The in-house engine was updated to ACE in anticipation of the development of Grand Raid Off-road, the original title of what would eventually become FUEL, and as a result similarities between FUEL’s DPCs and DPCs from games released before 2006 are expected to be minimal. ACE is the name given to the updated version of the engine previously known as Zouna, and TotemTech before that; it is not a new engine, just a major update to the existing one. FUEL is not the only game developed with ACE; any game released during or after 2006 would have been built with the most up to date version of the in-house engine which was ACE. The change in version scheme visible between 2005 and 2006 in the list of Asobo games using the BigFile format is evidence of this. While FUEL was released in 2009, its development began in 2004~05 as evidenced by many date artifacts in the DPCs and the project being referred to as “offroad” throughout the codebase and assets. Many of the assets were created and compiled around 2007. The PC release gold master was built on 5/29/2009 at 6:14 PM. According to the version string present in the language files the released version is VERSION: 01/12/2008 (11.1001)
, keep in mind that this string remained unchanged since the Xbox 360 demo. Keep this timeline in mind if you intend to investigate other Asobo games for additional information.
An ImHex pattern for the DPC format can be found in the ImFUEL repository.
I have created a tool to work with DPC files: https://github.com/widberg/dpc.
File types related to DPC files in FUEL and other Asobo games are specified at the end of this document.
This page only specifies DPC versions v1.220.50.07 to v1.381.67.09 inclusive.
Other versions of the DPC format are partially specified on the XeNTaX Wiki.
All references in this specification to the CRC32 hashing function refer to the Asobo CRC32 implementation. Similarly, all references in this specification to the LZ compression algorithm refer to the Asobo LZ implementation.
For more information about what work still needs to be done, see the BigFile Needs Work entry.
A path to an object in a DPC.
The projects crc32
and crc32gpu
are being used to research possible ways to recover the Uniform Object Locator from the CRC32 hash.
[a-zA-Z0-1_]+|DB:>[a-zA-Z0-1_>\-]+[.][a-zA-Z0-1_\-]+
The word class is used to refer to one of the classes defined on the Asobo Classes page.
All fields are little-endian unless specified otherwise.
All top level structures (PrimaryHeader, Block, Pool Manifest, and Object) are padded with either 0x00 or 0xFF to a size that is a multiple of 2048. Blocks are padded with 0x00 and all other top level structures are padded with 0xFF. The functions to calculate the padded size of a section and the size of the padding are:
std::uint32_t calculatePaddedSize(std::uint32_t unpaddedSize)
{
return (unpaddedSize + 0x7ff) & 0xfffff800;
}
std::uint32_t calculatePaddingSize(std::uint32_t unpaddedSize)
{
return calculatePaddedSize(unpaddedSize) - unpaddedSize;
}
See the BigFile Load Procedure entry for more information about how the game loads BigFiles.
struct DPCFile
{
PrimaryHeader primaryHeader;
// Block Sector
Block blocks[primaryHeader.blockCount];
// The following fields only appear if primaryHeader.poolManifestPaddedSize is not 0
// The pool is exclusive to BigFiles opened with the OpenBFS command; this is the streaming version of the OpenBF command.
// Pool Sector
PoolManifest poolManifest;
PoolObject objects[poolManifest.objectsCRC32s.size];
};
struct PrimaryHeader
{
char versionString[256];
// Human readable version information
// The length of this string MUST match the same format as bellow
// It is better to use the latest version string than make your own
// If this is an invalid value, the game will get stuck in a loop
// Values used in FUEL:
// * "v1.381.67.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.66.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.65.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.64.09 - Asobo Studio - Internal Cross Technology"
// * "v1.379.60.09 - Asobo Studio - Internal Cross Technology"
// * "v1.325.50.07 - Asobo Studio - Internal Cross Technology"
// * "v1.220.50.07 - Asobo Studio - Internal Cross Technology"
std::uint32_t isNotRTC;
// RTC = real time cinematics
// 1 if the DPC file is not located in the RTC directory
// 0 if the DPC file is located in the RTC directory
std::uint32_t blockCount;
// Number of blocks in the DPC file
// This field cannot be 0
// Empty DPCs are illegal and when one is encountered an error screen is displayed and the game is hard locked
std::uint32_t blockWorkingBufferCapacityEven;
// Used as the capacity of an internal byte vector for BigFileLoader_Z
// uses raw malloc
// A heap allocated buffer of size blockWorkingBufferCapacityEven is used to load even indexed blocks to be operated on
// This is effectively the padded size of the largest even indexed block with the workingBufferOffset added to it
// The pool might borrow this buffer for processing the pool manifest
// Must be a multiple of 2048
// This field cannot be 0
std::uint32_t blockWorkingBufferCapacityOdd;
// Same as above but for odd indexed blocks
// The buffer is created with a wrapper vector class around it
// 0 when there are no odd indexed blocks
// can be 0 even with a pool
std::uint32_t paddedSize;
// Sum of padded sizes specified in the block descriptions
// sizeof(PrimaryHeader) + paddedSize will be the starting address of the pool manifest
std::uint32_t versionPatch;
// Unused
// Corresponds to the patch version in the version string
// versionPatch values and the corresponding versionString values:
// * 272
// * "v1.381.67.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.66.09 - Asobo Studio - Internal Cross Technology"
// * 271
// * "v1.381.65.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.64.09 - Asobo Studio - Internal Cross Technology"
// * 269
// * "v1.379.60.09 - Asobo Studio - Internal Cross Technology"
// * 262
// * "v1.325.50.07 - Asobo Studio - Internal Cross Technology"
// * "v1.220.50.07 - Asobo Studio - Internal Cross Technology"
std::uint32_t versionMinor;
// Unused
// Corresponds to the minor version in the version string
// versionMinor values and the corresponding versionString values:
// * 380
// * "v1.381.67.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.66.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.65.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.64.09 - Asobo Studio - Internal Cross Technology"
// * "v1.379.60.09 - Asobo Studio - Internal Cross Technology"
// * 326
// * "v1.325.50.07 - Asobo Studio - Internal Cross Technology"
// * 221
// * "v1.220.50.07 - Asobo Studio - Internal Cross Technology"
std::uint32_t versionMajor;
// Unused
// Values used in FUEL:
// * 144
// * "v1.220.50.07 - Asobo Studio - Internal Cross Technology"
// * 146
// * "v1.325.50.07 - Asobo Studio - Internal Cross Technology"
// * 211
// * "v1.379.60.09 - Asobo Studio - Internal Cross Technology"
// * 249
// * "v1.381.64.09 - Asobo Studio - Internal Cross Technology"
// * "v1.381.65.09 - Asobo Studio - Internal Cross Technology"
// * 252
// * "v1.381.66.09 - Asobo Studio - Internal Cross Technology"
// * 253
// * "v1.381.67.09 - Asobo Studio - Internal Cross Technology"
BlockDescription blockDescriptions[64];
// Array of BlockDescriptions
// Only the first blockCount number of block descriptions are populated with information
// The remaining descriptions are filled with null bytes
std::uint32_t poolManifestPaddedSize;
// poolManifestPaddedSize << 0xB is the size of the pool manifest
// Equals 0 when there is no pool manifest
std::uint32_t poolManifestOffset;
// poolManifestOffset << 0xB is the offset from the beginning of the file to the beginning of the pool manifest
// Always equals primaryHeader.paddedSize + sizeof(PrimaryHeader) when there is a pool manifest
// Equals 0 when there is no pool manifest
std::uint32_t poolManifestUnused0;
// NEEDS WORK
std::uint32_t poolManifestUnused1;
// NEEDS WORK
std::uint32_t poolObjectDecompressionBufferCapacity;
// Always equals calculatePaddedSize(max(poolObjects.decompressedSize)) >> 11
// Regardless of whether the object is compresssed
// If this value is larger than the existing pool object decompression buffer, the buffer is resized to poolObjectDecompressionBufferCapacity << 11 bytes
// Equals 0 when there is no pool manifest
std::uint32_t blockSectorPaddingSize;
// Unused
// Number of pad bytes in the block sector
// 0xFFFFFFFF if there is no incredibuilder data
std::uint32_t poolSectorPaddingSize;
// Unused
// Number of pad bytes in the pool sector
// 0xFFFFFFFF if there is no incredibuilder data
std::uint32_t fileSize;
// Unused
// Size of the file on disk
// 0xFFFFFFFF if there is no incredibuilder data
char incrediBuilderString[128];
// https://en.wikipedia.org/wiki/Incredibuild
// Filled with 0xFF if there is no incredibuilder data
// Irrelevant to decoding the DPC format
// All variables prefixed by incrediBuilder may safely be filled with null bytes
std::uint8_t padding[64];
// 64 0xFF bytes
};
struct BlockDescription
{
std::uint32_t objectCount;
// Number of objects in the object array
// This field cannot be 0
std::uint32_t paddedSize;
// Size of the block data including padding
std::uint32_t dataSize;
// Size of the block data excluding padding
std::uint32_t workingBufferOffset;
// NEEDS WORK
std::uint32_t crc32;
// CRC32 hash of the name of the first object in the block
std::uint32_t zero;
// Always 0
};
struct Block
{
BlockObject blockObjects[objectCount];
std::uint8_t padding[paddedSize - dataSize];
};
struct BlockObject
{
ObjectHeader objectHeader;
std::uint8_t data[dataSize];
};
struct ObjectHeader
{
std::uint32_t dataSize;
// Size of the object in the file excluding padding
std::uint32_t classObjectSize;
// Size of the class object data from the beginning of the data
// Offset from the beginning of the data to the beginning of the object data
// Always 0 in non-block objects
std::uint32_t decompressedSize;
// Size of the object data after decompression
// Equal to 0 when object data is stored in the object sector
std::uint32_t compressedSize;
// Size of the compressed object data
// Equal to dataSize when data is LZ compressed
// Equal to 0 when data is not LZ compressed
std::uint32_t classCRC32;
// CRC32 value of the class name
// See the appendix CRC32 Reverse Lookup table
std::uint32_t crc32;
// CRC32 hash of the object name string
};
struct PoolManifest
{
PoolManifestHeader poolManifestHeader;
PascalArray<std::uint32_t> objectsCRC32s;
// Index in crc32s of the CRC32 hash of the object at the current index
// Not unique from 0 to count-1
// Same size as object array
PascalArray<std::uint32_t> crc32s;
// Array of the CRC32 values of the object name strings
PascalArray<std::uint32_t> referenceCounts;
// Number of times the CRC32 hash at the same index in crc32s is referenced in the pool sector
PascalArray<std::uint32_t> objectPaddedSize;
// objectPaddedSize << 0xB is the padded size of the object
PascalArray<std::uint32_t> referenceRecordsIndices;
// Each value is unique between 1 and count inclusive
// Values of 0 are skipped in processing
// 1 is subtracted from the value and used as an index in referenceRecords
PascalArray<ReferenceRecord> referenceRecords;
// Describes relationships between objects
// It is safe to remove duplicates and reorder this array as long as referenceRecordsIndices is updated accordingly
ReferenceRecord terminal;
// The non-placeholder value fields are always 0
std::uint8_t padding[poolManifestPaddedSize - sizeof(/* other fields */)];
};
struct PoolManifestHeader
{
std::uint32_t equals524288;
// Unused
// Always 524288 = 256 << 11
// Must be a multiple of 2048
std::uint32_t equals2048;
// Unused
// Always 2048 = 1 << 11
// Could be related to the address of the first block or size of the primary header or padding
// Must be a multiple of 2048
std::uint32_t objectsCRC32CountSum;
// Unused
// Sum of the objectsCRC32Count values of all the reference records
};
struct ReferenceRecord
{
std::uint32_t startChunkIndex;
// Equals poolManifestOffset + poolManifestPaddedSize + sum(objectPaddedSize[all objects before the one referenced by objectsCRC32StartingIndex])
// Often equal to the endChunkIndex of the structure right before this one
// The objectPaddedSize is added to this to get a temporary value
// Idk how the first startChunkIndex index is calculated but its probably related to the number of objects
std::uint32_t endChunkIndex;
// Unused
// Equals poolManifestOffset + poolManifestPaddedSize + sum(objectPaddedSize[all objects before the one referenced by objectsCRC32StartingIndex + objectsCRC32Count])
// Often equal to the startChunkIndex of the structure right after this one
// End chunk index
std::uint32_t objectsCRC32StartingIndex;
// Corresponds to an index of objectsCRC32s in the pool manifest
// Overwritten at runtime with a calculated value
// Some number is added to each objectsCRC32index
// The same number is added to all objectsCRC32indexs in the same DPC
// The added number is constant across multiple runs
std::uint16_t placeholderDPCIndex;
// Always 0
// Over written at runtime with the DPC's index
// A DPC's index represents when it was loaded in the load order
// First DPC to load will have 0, second will have 1, and so on
std::uint16_t objectsCRC32Count;
// Never 0
// Number of elements in objectsCRC32 to iterate over starting at objectsCRC32StartingIndex
std::uint32_t placeholderTimesReferenced;
// Always 0xFFFFFFFF
// Over written at runtime to the total number of times this structure has been referenced
std::uint32_t placeholderCurrentReferencesShared;
// Always 0xFFFFFFFF
// Overwritten at runtime to the number of shared current references to this structure
std::uint32_t placeholderCurrentReferencesWeak;
// Always 0xFFFFFFFF
// Overwritten at runtime to the number of weak current references to this structure
};
struct PoolObject
{
ObjectHeader objectHeader;
// If objectHeader.crc32 is identical to an objectHeader.crc32 in another DPC then the two objectHeaders MUST be identical.
std::uint8_t data[dataSize];
std::uint8_t padding[calculatePaddingSize(sizeof(ObjectHeader) + dataSize)];
};
Names PC
Plaintext table of Asobo CRC32 hashes and their human readable strings. CRC32 Hashes are represented as signed 32bit integers despite internally being used as unsigned 32bit integers. The distinction between signed and unsigned is important because the hashing algorithm makes use of the right shift operation which compiles to different machine code instructions for signed and unsigned integers. It is unclear why the NPC format uses signed integers when the DPC format uses unsigned integers but my guess is that fprintf
is used with the %d
format specifier in their internal build tools because somebody didn’t want to lookup the unsigned integer format specifier.
An NPC file consists of a list of names formatted as follows:
<std::int32_t hash> "<std::string name>"<\n>
Sample:
0 ""
349401345 "DB:>SOUNDS>SFX>MENU>I_ME_CHANGE.WAV"
1202944770 "DB:>LEVELS>FONTES>FONTES.TOTEMBITMAP"
-681630461 "DB:>TEXTURES>FONTES>FNT_FONT1.TGA"
1223335941 "SMALL_FONT"
<...>
1404141044 "DB:>SOUNDS>SFX>MENU>I_IT_2.WAV"
-683453185 "DB:>LEVELS>FONTES>FONTES>FONTES_001.TBITMAP"
Layout PC
Describes the contents of a DPC archive. Analogous to a PDB file for an EXE.
BlockSectorPaddingSize 1344
PoolSectorPaddingSize 0
BigFileSize 59392
ObjectsSize 55904
NbObject 4
EndDiskSize
SortByBlock
0/ 0 55498 RTC_Z "DB:>RTC>TRAFFIC2_RTC.TRTC"
0/ 1 272 NODE_Z "DB:>RTC>TRAFFIC2_RTC.TRTC_BF_NODE_Z_-33686019"
0/ 2 146 CAMERA_Z "DB:>RTC>TRAFFIC2_RTC.TRTC_BF_CAMERA_Z_0"
0/ 3 84 LIGHTDATA_Z "DEFAULTSUNLIGHT"
EndSortByBlock
SortByDiskSpace
55498 "DB:>RTC>TRAFFIC2_RTC.TRTC"
272 "DB:>RTC>TRAFFIC2_RTC.TRTC_BF_NODE_Z_-33686019"
146 "DB:>RTC>TRAFFIC2_RTC.TRTC_BF_CAMERA_Z_0"
84 "DEFAULTSUNLIGHT"
EndSortByDiskSpace
SortByClassSize
CLASS RTC_Z 55498
55498 "DB:>RTC>TRAFFIC2_RTC.TRTC"
CLASS NODE_Z 272
272 "DB:>RTC>TRAFFIC2_RTC.TRTC_BF_NODE_Z_-33686019"
CLASS CAMERA_Z 146
146 "DB:>RTC>TRAFFIC2_RTC.TRTC_BF_CAMERA_Z_0"
CLASS LIGHTDATA_Z 84
84 "DEFAULTSUNLIGHT"
EndSortByClassSize
SortByInfos
"DB:>RTC>TRAFFIC2_RTC.TRTC" Static 4 Stream 55470 FriendlyName "TRAFFIC2"
"DB:>RTC>TRAFFIC2_RTC.TRTC_BF_NODE_Z_-33686019" Static 4 Stream 244 FriendlyName "ASOBO001"
"DB:>RTC>TRAFFIC2_RTC.TRTC_BF_CAMERA_Z_0" Static 110 Stream 12 FriendlyName "TRAFFIC2"
"DEFAULTSUNLIGHT" Static 4 Stream 56 FriendlyName "DEFAULTSUNLIGHT"
EndSortByInfos
SortDependencies
1 "DB:>RTC>TRAFFIC2_RTC.TRTC"
"DB:>RTC>TRAFFIC2_RTC.TRTC_BF_NODE_Z_-33686019"
EndSortDependencies
SortShaders
EndSortShaders
Data PC Layout
Older format of LPC files.
Sample:
00 O 0 S 18414 P 0 [DB:>SOUNDS>SFX>MENU>I_IT_1.WAV]
00 O 18430 S 44814 P 28118 [DB:>SOUNDS>SFX>MENU>I_IT_3.WAV]
00 O 46564 S 65550 P 32458 [DB:>SOUNDS>SFX>MENU>I_ME_INVALID.WAV]
00 O 79038 S 106510 P 42542 [DB:>SOUNDS>SFX>MENU>I_ME_VALID.WAV]
01 O 0 S 53614 P 34344 [DB:>SOUNDS>SFX>MENU>I_IT_GO.WAV]
01 O 34360 S 90126 P 26922 [DB:>SOUNDS>SFX>MENU>I_ME_SHIFT.WAV]
02 O 0 S 51214 P 18247 [DB:>SOUNDS>SFX>MENU>I_ME_BACK.WAV]
02 O 18263 S 65550 P 9590 [DB:>SOUNDS>SFX>MENU>I_ME_CHANGE.WAV]
02 O 27869 S 48814 P 31391 [DB:>SOUNDS>SFX>MENU>I_IT_2.WAV]
Sort by Disk Space
42542 [DB:>SOUNDS>SFX>MENU>I_ME_VALID.WAV]
34344 [DB:>SOUNDS>SFX>MENU>I_IT_GO.WAV]
32458 [DB:>SOUNDS>SFX>MENU>I_ME_INVALID.WAV]
31391 [DB:>SOUNDS>SFX>MENU>I_IT_2.WAV]
28118 [DB:>SOUNDS>SFX>MENU>I_IT_3.WAV]
26922 [DB:>SOUNDS>SFX>MENU>I_ME_SHIFT.WAV]
18414 [DB:>SOUNDS>SFX>MENU>I_IT_1.WAV]
18247 [DB:>SOUNDS>SFX>MENU>I_ME_BACK.WAV]
9590 [DB:>SOUNDS>SFX>MENU>I_ME_CHANGE.WAV]
Information World Reference (I could stand for anything. Internal/Input/Include)
Describes TWORLDREF dependencies. Only USA1.DPC in FUEL has one of these that isn't empty.
An IWR file consists of a list of entries as follows:
<std::string friendlyName><\n>
<std::string worldReferncePath><\n>
<for each dependency>
<\t><std::string dependencyPath><\n>
<end for each>
Sample:
CR2
DB:>LEVELS>HUBS>QUARTIER_1>3DNODEGEOMETRY>CR2.TWORLDREF
DB:>LEVELS>COURSES3>QUARTIER_1>3DNODEGEOMETRY>PROJECTEURROT_007.TROTSHAPE
CR36
DB:>LEVELS>HUBS>QUARTIER_1>3DNODEGEOMETRY>CR36.TWORLDREF
DB:>LEVELS>COURSES1>QUARTIER_1>3DNODEGEOMETRY>PROJECTEURROT_005.TROTSHAPE
DB:>LEVELS>HUBS>QUARTIER_1>3DNODEGEOMETRY>EVENT5_001.TVOLUME
DB:>LEVELS>HUBS>QUARTIER_1>3DNODEGEOMETRY>EVENT4_001.TVOLUME
DB:>LEVELS>HUBS>QUARTIER_1>3DNODEGEOMETRY>EVENT3_001.TVOLUME
DB:>LEVELS>HUBS>QUARTIER_1>3DNODEGEOMETRY>EVENT2_001.TVOLUME
<...>
CR69
DB:>LEVELS>COURSES1>QUARTIER_1>3DNODEGEOMETRY>CR69.TWORLDREF
DB:>LEVELS>COURSES1>QUARTIER_1>3DNODEGEOMETRY>PROJECTEURROT_013.TROTSHAPE
CR04
DB:>LEVELS>COURSES1>QUARTIER_1>3DNODEGEOMETRY>CR04.TWORLDREF
It's just a jpg image file. Idk why it's there. Identical except for the filename which changes to match the DPC it goes to. Only in the world folder for Monopoly.
Sample:
A 2048px by 2048px black square image with a 1px thick white outline of a 601px by 1862px rectangle 93px away from the edge of the image top, left, and bottom.
For FMTK Users and Mod Developers
For FMTK Developers
Asobo BigFile Format Specification
Asobo Classes
Animation_Z
Binary_Z
Bitmap_Z
Camera_Z
CollisionVol_Z
Fonts_Z
GameObj_Z
GenWorld_Z
GwRoad_Z
Keyframer*_Z
Light_Z
LightData_Z
Lod_Z
LodData_Z
Material_Z
MaterialAnim_Z
MaterialObj_Z
Mesh_Z
MeshData_Z
Node_Z
Omni_Z
Particles_Z
ParticlesData_Z
RotShape_Z
RotShapeData_Z
Rtc_Z
Skel_Z
Skin_Z
Sound_Z
Spline_Z
SplineGraph_Z
Surface_Z
SurfaceDatas_Z
UserDefine_Z
Warp_Z
World_Z
WorldRef_Z
Asobo File Format Idioms
Asobo CRC32
Asobo LZ Compression
Asobo Arithmetic Coding Compression
Asobo Save Game File Format Specification
Asobo Audio Formats
TotemTech/ToonTech/Zouna/ACE/BSSTech/Opal Timeline
Zouna Modding Resources
Miscellaneous