BLP

From Civilization VI Wiki
Jump to: navigation, search

A BLP file is a container for binary-serialized assets such as textures and models. It is new to Civilization VI.

Format[edit | edit source]

Header[edit | edit source]

All values in this file are stored little-endian. The file begins with a header:

struct BLPHeader {
    char magic[6];            // 'CIVBLP'
    uint16 version;           // 1
    uint32 packageDataOffset; // Generally 0x200
    uint32 packageDataSize;
    uint32 bigDataOffset;     // packageDataOffset + packageDataSize
    uint32 bigDataCount;
    uint32 fileSize;
};

Package Headers[edit | edit source]

At packageDataOffset bytes in the file, there is a PackagePreamble followed immediately by a PackageHeader:

struct PackagePreamble {
    uint32 uiPackageVersion;        // 5
    uint16 uiSizeOfVoidPointer;     // 8
    uint16 uiAlignOf64BitStructure; // 8
    uint32 uiSizeOfPackageHeader;   // 0x48
    uint32 uiEndianField;           // 1
};
struct PackageHeader {
    struct StripeInfo { // A stripe is a block of serialized data
        uint32 uiStart; // This is an offset from the start of the PackagePreamble value
        uint32 uiSize;
    };
    StripeInfo kResourceLinkerDataStripe;
    StripeInfo kPackageBlockStripe;        // This stripe contains package objects.
    StripeInfo kTempDataStripe;            // This stripe contains linker data (which is information on how to fixup pointers), the root object, and String::Global objects.
    StripeInfo kTypeInfoStripe;            // This stripe contains another package with type information for all the objects serialized in packageData
    StripeInfo kRootTypeName;              // The root object of the package is this type; it is stored at the start of TempDataStripe
    uint32 uiLinkerDataOffset;             // This is an offset from the start of TempDataStripe; it contains an array of PackageAllocation
    uint32 uiResourceListOffset;
    uint32 uiLargestResource;
    uint32 uiSecondLargestResource;
    uint32 uiPackageBlockAlignment;        // 0x10
    uint32 uiSizeOfTypeInfoStripe;         // 0x30
    uint32 uiSizeOfPackageAllocation;      // 0x28
    uint32 uiSizeOfResourceAllocationDesc; // 0x10
};

The TypeInfoStripe contains another PackagePreamble and PackageHeader, in which a TypeInfoStripe struct is serialized.

Pointer Fixup[edit | edit source]

Every pointer value in a BLP file is stored as a uint64. If this value is 0, then the pointer is null. Otherwise, to resolve the pointer to a location in the file, the value minus 1 is used as an index in to the array of PackageAllocation specified by uiLinkerDataOffset in the PackageHeader.

struct PackageAllocation {
    uint8 byStripe;           // 0: PackageBlockStripe; 1: TempDataStripe
    uint8 byAllocType;
    uint8 byPadding[4];
    uint16 wParentAlloc;
    uint32 dwOffset;
    uint32 dwAllocSize;
    uint32 dwElementCount;
    uint64 qwUserData;
    String::Global sTypeName; // String::Global is a pointer to char
};

From the PackageAllocation value, byStripe is used to determine which stripe the pointed-to object is in, and dwOffset is the byte offset in to that stripe to the object.

To do fixup on all pointers in the package, iterate the PackageAllocation array, and do fixup for the values in each allocation, using sTypeName and the TypeInfoStripe to fixup all pointer values in the allocation.

Type Information[edit | edit source]

The TypeInfoStripe contains type information for all objects serialized in the BLP file.

struct LinearAlloc {
    template <typename T>
    struct LinearBlock {
        ptr64<T> pBlock;
        uint32 nElements;
    };
};

namespace Types {
    template <typename T>
    struct Vector { // Note: sizeof(Vector) is 0x18 due to padding
        LinearAlloc::LinearBlock<T> m_kBlock;
        uint32 m_nCurrSize;
    };
};

namespace String {
    struct Global {
        ptr64<char> m_sz;
    };
};

struct TypeInfoStripe {
    struct FieldVersion {
        String::Global m_sFieldName;
        String::Global m_sTypeName;  // type of the field value
        uint32 m_uiVersion;
        uint32 m_uiAddress;          // byte offset of the field in the struct
    };
    struct TypeVersion {
        String::Global m_sTypeName;
        String::Global m_sUnderlyingTypeName;
        // Note:  fields are not ordered as they are defined in the struct.
        // Inherited fields are included in this vector.
        Types::Vector<FieldVersion> m_Fields;
        uint32 m_uiVersion;
        uint32 m_uiSize;
        // 0x01: is array
        // 0x02: is pointer
        // 0x04: is fundamental (scalar -- int, float, etc.)
        // 0x10: is polymorphic (has a virtual table)
        uint32 m_uiTypeTraitFlags;
    };
    struct EnumVersion {
        struct EnumConstant {
            String::Global sName;
            int32 iValue;
        };
        String::Global m_sTypeName;
        Types::Vector<EnumConstant> m_Constants;
    };

    Types::Vector<TypeVersion> m_Types;
    Types::Vector<EnumVersion> m_Enums;
};

Strings[edit | edit source]

String::BasicT has a discrepancy between its type information and its actual layout. Note that it is still a pointer, but it does not point directly to the string data:

namespace String {
template <typename Allocator, typename Encoding>
struct BasicT {
    struct Storage {
        uint32 capacity;
        uint32 length;
        char str[1];
    };
    String::Basic::Storage *m_sz;
    const char *CStr() const {
        return m_sz->str;
    }
};
};