MPQ Archives

Inkrementální soubory (patche)

Úvod

Inkrementální soubory byly poprvé pozorovány v BETA verzi hry World of Warcraft - Cataclysm. Jsou to soubory v archivech MPQ, které maji nastaven atribut MPQ_FILE_PATCH_FILE. Inkrementální soubory mohou být uloženy po jednotlivých sektorech nebo v souvislém bloku (single unit). Některé proměnné v metadatech souboru mají pozměněný význam oproti běžným souborům:

Struktura TPatchInfo

Tato struktura obsahuje velikost inkrementálního souboru, atributy a MD5. V jazyce C je struktura definovaná takto:

// Patch file header
struct TPatchInfo
{
    DWORD dwLength;                     // Length of patch info header, in bytes
    DWORD dwFlags;                      // Flags. 0x80000000 = MD5 (?)
    DWORD dwDataSize;                   // Uncompressed size of the patch file
    BYTE  md5[0x10];                    // MD5 of the entire patch file after decompression

    // Followed by the sector offset table (variable length)
};

TPatchInfo je součástí metadat - obsahuje informace potřebné k přečtení souboru z MPQ.

Struktura TPatchHeader

Každý inkrementální soubor začíná strukturou TPatchHeader. Tato struktura má pravděpodobně proměnnou délku, a v jazyce C je zapsána takto:

// Header for PTCH files 
struct TPatchHeader
{
    //-- PATCH header -----------------------------------
    DWORD dwSignature;                  // 'PTCH'
    DWORD dwSizeOfPatchData;            // Size of the entire patch (decompressed)
    DWORD dwSizeBeforePatch;            // Size of the file before patch
    DWORD dwSizeAfterPatch;             // Size of file after patch
    
    //-- MD5 block --------------------------------------
    DWORD dwMD5;                        // 'MD5_'
    DWORD dwMd5BlockSize;               // Size of the MD5 block, including the signature and size itself
    BYTE md5_before_patch[0x10];        // MD5 of the original (unpached) file
    BYTE md5_after_patch[0x10];         // MD5 of the patched file

    //-- XFRM block -------------------------------------
    DWORD dwXFRM;                       // 'XFRM'
    DWORD dwXfrmBlockSize;              // Size of the XFRM block, includes XFRM header and patch data
    DWORD dwPatchType;                  // Type of patch ('BSD0' or 'COPY')

    // Followed by the patch data
};

Data pro aktualizaci

Data pro aktualizaci následují bezprostředně za hlavičkou. Typ dat závisí na hodnotě proměnné dwPatchType v hlavičce. Velikost dat je uložena taktéž v hlavičce, v proměnné dwXfrmBlockSize. Jsou známy následující identifikátory formátů:

  1. 'BSD0' - Modifikovaná varianta inkrementální aktualizace BSDIFF40
  2. 'BSDP' - Neznámá
  3. 'COPY' - Prosté nahrazení
  4. 'COUP' - Neznámá
  5. 'CPOG' - Neznámá

V následujících odstavcích jsou popsány známé patchovací metody.

BSD0: Aktualizace založená na algoritmu BSDIFF40

Data pro aktualizaci začínají 32-bitovou hodnotou, která obsahuje velikost dat po rozbalení. Následují data BSDIFF40, která jsou zkomprimovaná metodou RLE. Formát modifikované verze BSDIFF40 použité v MPQ je následující:

Pozice Velikost Význam
0x0000 0x08 bytů Signatura 'BSDIFF40'
0x0008 0x08 bytů Velikost bloku CTRL (v bytech)
0x0010 0x08 bytů Velikost bloku DATA (v bytech)
0x0018 0x08 bytů Velikost souboru po aplikaci aktualizace (v bytech)
 
0x0020 Proměnná Blok CTRL. Skládá se z pole trojic, každá trojice má velikost 0x0C bytů (3 DWORDS). Velikost bloku CTRL je uložena v hlavičce BSDIFF. Toto je rozdíl oproti původní verzi BSDIFF40, která používá 64 bitů pro každou hodnotu z trojice.
 
Proměnná Proměnná Blok DATA. Délka bloku je uložena v hlavičce BSDIFF.
 
Proměnná Proměnná Blok EXTRA. Začátek je za blokem DATA, a blok sahá až na konec dat BSDIFF.

COPY: Prosté nahrazení původního obsahu souboru daty z aktualizace

Jak název říká, typ COPY plně nahrazuje původní soubor.

Algoritmy

Dekomprese RLE

void Decompress_RLE(LPBYTE pbDecompressed, DWORD cbDecompressed, LPBYTE pbCompressed, DWORD cbCompressed)
{
    LPBYTE pbDecompressedEnd = pbDecompressed + cbDecompressed;
    LPBYTE pbCompressedEnd = pbCompressed + cbCompressed;
    BYTE RepeatCount; 
    BYTE OneByte;

    // Cut the initial DWORD from the compressed chunk
    pbCompressed += sizeof(DWORD);
    cbCompressed -= sizeof(DWORD);

    // Pre-fill decompressed buffer with zeros
    memset(pbDecompressed, 0, cbDecompressed);

    // Unpack
    while(pbCompressed < pbCompressedEnd)
    {
        OneByte = *pbCompressed++;
        
        // Is it a repetition byte ?
        if(OneByte & 0x80)
        {
            RepeatCount = (OneByte & 0x7F) + 1;
            for(BYTE i = 0; i < RepeatCount; i++)
            {
                if(pbDecompressed == pbDecompressedEnd || pbCompressed == pbCompressedEnd)
                    break;

                *pbDecompressed++ = *pbCompressed++;
            }
        }
        else
        {
            pbDecompressed += (OneByte + 1);
        }
    }
}

Aktualizace dat BSD0

Více informací o aplikaci aktualizace typu BSD0 najdete ve zdrojovém kódu knihovny StormLib, ve funkci ApplyMpqPatch_BSD0()