Archivy MPQ

Neúplné archivy

Základní informace

Neúplné soubory umožňují mít na disku pouze malou část většího, existujícího souboru, a chybějící části stáhnout až v okamžiku, kdy jsou potřeba. Formát neúplných souborů není vázán na archivy MPQ, ale slouží jako médium pro ukládání obsahu archivu. Neúplné soubory mohou být použity pro jakýkoliv typ souboru nebo archivu.

Neúplné soubory byly poprvé použíty ve zkušební verzi hry World of Warcraft. Protože množství dat použitých ve hře je obrovské, trvalo by neúnosně dlouhou dobu stáhnout celou hru. Pro zkrácení doby stahování je vytvořen neúplný archiv, a chybějící části jsou dotaženy podle potřeby, jak hráč postupuje hlouběji do hry.

Neúplné archivy MPQ mají obvykle dvojitou příponu .MPQ.part, např. interface.MPQ.part.

Struktura neúplného souboru

Obecná struktura neúplného souboru je tato:

Hlavička neúplného souboru

Hlavička neúplného souboru použítá společností Blizzard obsahuje signaturu, číslo sestavení hry, velikost jedné části souboru a celkovou velikost souboru. Hlavička neúplného souboru je popsána následující strukturou v jazyce C:

// 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;

Důležitými členy struktury jsou FileSizeLo, FileSizeHi a PartSize. Tyto jsou použity pro vypočítání počtu částí na které je archiv MPQ rozdělen. Jsou také použity ke spočítání vypočítání počtu položek v mapě částí, která následuje za hlavičkou.

Mapa částí souboru

Mapa částí souboru následuje za hlavičkou neúplného souboru. Každá část má odpovídající položku v mapě částí. Celkový počet položek v mapě je spočítán podle vzorce:

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

kde FullFileSize a PartSize jsou uloženy v hlavičce neúplného souboru.

Položka mapy je popsána následující strukturou v jazyce C:

// 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;

Data souboru

Za mapou částí souboru následují bloky dat. Každý blok představuje část vlastního souboru, jako např. archivy MPQ. Velikost každé části je uložen v hlavičce neúplného souboru, s vyjímkou poslední části, která může mít velikost menší. Části souboru jsou uloženy v pořadí, v jakém byly načteny, a nemusejí následovat jedna za druhou.

Čtení z neúplného souboru

Následující příklad demonstruje čtení hlavičky MPQ z neúplného souboru. Pro ujasnění používá termín "virtuální pozice" pro aktuální pozici v uložených datech a "pozice v neúplném souboru" pro označení pozice v souboru na disku. Předpokládáme, že pozice hlavičky archivu MPQ je na virtuální pozici 0. Příklad je napsán pro platformu Win32.

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, dwBumberOfBytesToRead, &dwNumberOfBytesRead, NULL);