MPQ Archives

Partial MPQs

Overview

Partial files allow to have only small portion of a large, existing file, and download the rest when needed. Partial file format is not connected to MPQ format, but it rather serves as media for storing a MPQ archive. In fact, it can be used by any file or archive. Partial MPQs were first used by the game of World of Warcraft Trial Version. Because the amount of data used by the game is large, it would take unreasonable amount of time to download the complete MPQs from the internet. To reduce download time, all MPQs are created as partial files and only small portion of them is initially available. As the player progresses in the game, more and more parts of the MPQ archives is downloaded and the file size grows.

Partial MPQs usually have double extension of .MPQ.part, like interface.MPQ.part.

Structure of a PART file

The general layout of a PART file is the following:

PART file header

Header of PART file used by Blizzard contains signature, game build number, size of one file block, and size of the full file. The layout of PART file header is described by the following C structure:

// Structure describing the PART file header
typedef struct _PART_FILE_HEADER
{
    // Always set to 2
    DWORD PartialVersion;
    
    // Game build number as ASCIIZ string
    char  GameBuildNumber[8];
    
    // Unknown
    DWORD Unknown0C;
    
    // Unknown
    DWORD Unknown10;
    
    // Seems to contain 0x1C, which is the size of the rest of the header
    DWORD Unknown14;
    
    // Unknown
    DWORD Unknown18;
    
    // Unknown
    DWORD Unknown1C;
    
    // Unknown
    DWORD Unknown20;
    
    // Seems to always be zero
    DWORD ZeroValue;
    
    // Low 32 bits of the file size
    DWORD FileSizeLo;
    
    // High 32 bits of the file size
    DWORD FileSizeHi;
    
    // Size of one file part, in bytes
    DWORD PartSize;

} PART_FILE_HEADER, *PPART_FILE_HEADER;

The important members of this structure are FileSizeLo, FileSizeHi and PartSize. These members are used to calculate number of parts that the full MPQ is split up to. It is also used to calculate number of entries in the part table, that immediately follows the PART file header.

PART map table

PART map table follows the PART file header. This table has one entry per each file PART. Total number of entries is calculated using the following formula:

PartCount = (DWORD)((FullFileSize + PartSize - 1) / PartSize)

where FullFileSize and PartSize is obtained from the PART file header.

The PART map table is array of the following structures:

// Structure describing the entry in the PART map
typedef struct _PART_FILE_MAP_ENRY
{
    // 3 = the part in present in the file
    DWORD Flags;
    
    // Low 32 bits of the part position in the file
    DWORD BlockOffsLo;                      
    
    // High 32 bits of the part position in the file
    DWORD BlockOffsHi;
    
    // Unknown
    DWORD Unknown0C;

    // Unknown
    DWORD Unknown10;

} PART_FILE_MAP_ENRY, *PPART_FILE_MAP_ENRY;

File Data

Following the PART map table, there is array of file parts. Each file part represents a certain amount of the file (such as MPQ archive). The size of each part is in the PART file header, except the last part, that may be less size. The parts are stored in order how they were downloaded, and don't necessarily follow each other.

Reading from PART file

The following example shows how to read a data from PART file. It demonstrates reading of MPQ header from the MPQ. For clarification, we will use the term "virtual offset" for offset within file data stored in PART file, and raw offset from offset in the PART file itself. We assume that the MPQ header is at virtual offset 0. The example is written for Win32 platform.

LARGE_INTEGER VirtualOffset = {0};
LARGE_INTEGER RawFileOffset;
DWORD PartIndex;

// Verify if the virtual offset doesn't go beyond the virtual file size
if(VirtualOffset.QuadPart >= VirtualSize.QuadPart)
{
    SetLastError(ERROR_HANDLE_EOF);
    return false;
}

// Calculate the part index for the given virtual offset
PartIndex = (DWORD)(VirtualOffset.QuadPart / PartSize);

// Check if that part is available in the file
if(PartMap[PartIndex].Flags != 3)
{
    SetLastError(ERROR_CAN_NOT_COMPLETE);
    return false;
}
  
// Calculate the offset of data within the file part
PartOffset = VirtualOffset.LowPart & (PartSize - 1);

// Calculate raw file offset
RawFileOffset.HighPart = PartMap[PartIndex].BlockOffsHi;
RawFileOffset.LowPart = PartMap[PartIndex].BlockOffsLo;
RawFileOffset.QuadPart += PartOffset;

// Read the data from the file. For simplicity, we will not check bounds
SetFilePointer(hPartFile, RawFileOffset.LowPart, &RawFileOffset.HighPart, FILE_BEGIN);
ReadFile(hPartFile, pvBuffer, dwNumberOfBytesToRead, &dwNumberOfBytesRead, NULL);