MPQ Archives
Partial MPQs
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.
The general layout of a PART file is the following:
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 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;
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.
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);
Copyright (c) Ladislav Zezula 2010