Archivy MPQ

Knihovna StormLib

Historie

Po vydání hry Diablo, mnoho hráčů hledalo nástroj, který by umožnil přehrávat hudbu a zvuky ze hry. Krátce poté se začaly objevovat nástroje, které používaly knihovnu Storm.dll (součást hry) ke čtení dat z archivů hry. Společnost Blizzard si toho byla vědoma a od verze 1.04 obsahovala knihovna Storm.dll triky pro zabránění snadného použití ke čtení Blizzardovských archivů. Přestože tyto triky jsou snadno překonatelné, komunita hledala alternativu pro čtení a vytváření archivů MPQ bez pomoci Blizzardí knihovny.

První knihovna, ktera umožňovala vytváření archivů MPQ, byla LMPQAPI, vytvořena ruským programátorem Andrejem Lelikovem. Tato knihovna interně používala StarEdit.exe a umožňovala volat interní funkce, které uměly vytvářet archivy MPQ a přidávat soubory.

Tom Amigo byl první, kdo uveřejnil Stormless MPQ Editor, který uměl číst (a později i zapisovat) archivy MPQ bez použití Storm.dll. Zhruba ve stejné době (rok 2000) jsem začal reverzovat Storm.dll, abych získal funkční kód napsaný v C++, ktery umí číst archivy MPQ. Výsledkem byla knihovna StormLib v 1.0. Ke dnešnímu dni (rok 2010) je knihovna pořád udržovaná, a umí číst a přidávat soubory do archivů MPQ.

Knihovna StormLib

Knihovna StormLib je skupina modulů, napsaných v C++, které umí číst a také zapisovat soubory z archivů MPQ. Původní verze byla napsaná pro platformu Win32. Existuje i verze pro Linux, napsaná Markem Friedemannem a verze pro Mac, kterou napsal Sam Wilkins. Knihovna je volně ke stažení, a k použití ve vlastních projektech není třeba licence. Stáhnout se dá v sekci Ke stažení. Pokud byste našli jakoukoliv chybu, nebo odhalili něco nefunkčního, dejte mi prosím vědět, a také mi pošlete archiv MPQ, který problém způsobil.

Funkce knihovny StormLib

Veškeré potřebné funkce jsou k dispozici v hlavičkovém souboru StormLib.h. Dokumentaci je také možné stáhnout.

Manipulace s archivy MPQ

Funkce Popis
SFileOpenArchive Otevře archiv MPQ
SFileCreateArchive Vytvoří nový archiv MPQ
SFileCreateArchive2 Vytvoří nový archiv MPQ. Umožňuje více nastavení než SFileCreateArchive.
SFileCreateArchiveEx Funkce odstraněna
SFileAddListFile Přidá další listfile do otevřeného archivu MPQ pro zlepšení vyhledávání
SFileSetLocale Změní nastavení jazyka pro přidávání nových souborů
SFileGetLocale Vrací aktuální jazyk nově přidávaných souborů
SFileFlushArchive Uloží všechna data MPQ archivu na disk
SFileCloseArchive Uzavře archiv MPQ
SFileSetMaxFileCount Změní limit pro počet souborů v archivu
SFileSignArchive Podepíše archiv slabou digitální signaturou
SFileCompactArchive Defragmentuje (přebalí) celý archiv. Slouží k uvolnění všech mezer, které vznikly ukládáním do archivu
SFileSetCompactCallback Umožňuje nastavit callback funkci, která informuje o průběhu defragmentace archivu MPQ

Použití archivů s aktualizacemi

Funkce Popis
SFileOpenPatchArchive Přidá aktualizaci k otevřenému archivu
SFileIsPatchedArchive Zjistí, zda otevřený archiv obsahuje aktualizace

Čtení souborů

Funkce Popis
SFileOpenFileEx Otevře soubor v archivu MPQ
SFileGetFileSize Vrací velikost souboru v MPQ archivu
SFileSetFilePointer Nastaví nový ukazatel pozice v souboru.
SFileReadFile Čte data ze souboru
SFileCloseFile Uzavře soubor
SFileHasFile Rychlé ověření, zda soubor v archivu existuje
SFileGetFileName Zjistí jméno otevřeného souboru
SFileGetFileInfo Zjistí jméno otevřeného souboru
SFileVerifyFile Ověří integritu souboru oproti rozšířeným atributům
SFileVerifyArchive Ověří digitální podpis archivu
SFileExtractFile Vybalí soubor z archivu MPQ

Vyhledávání souborů

Funkce Popis
SFileFindFirstFile Najde první soubor, který vyhovuje specifikaci
SFileFindNextFile Najde další soubor, který vyhovuje specifikaci
SFileFindClose Ukončí hledání v MPQ
SListFileFindFirstFile Najde první soubor v listfile, který vyhovuje specifikaci
SListFileFindNextFile Najde další soubor v listfile, který vyhovuje specifikaci
SListFileFindClose Ukončí hledání v listfile
SFileEnumLocales Enumeruje všechny jazykové verze daného souboru

Přidávání souborů do MPQ

Funkce Popis
SFileCreateFile Vytvoří nový soubor v archivu a připraví jej pro zápis dat
SFileWriteFile Zapíše data do souboru v archivu
SFileFinishFile Dokončí vytváření souboru v archivu
SFileAddFileEx Přidá soubor do archivu
SFileAddFile Přidá datový soubor do archivu (zastaralá funkce)
SFileAddWave Přidá zvukový soubor VAWE do archivu (zastaralá funkce)
SFileRemoveFile Odstraní soubor z archivu MPQ
SFileRenameFile Přejmenuje soubor v archivu MPQ
SFileSetFileLocale Změní jazyk souboru v archivu
SFileSetDataCompression Nastaví kompresní metodu pro soubory přidávané pomocí SFileAddFile
SFileSetAddFileCallback Nastaví ukazatel na funkci, pomocí které je aplikace informována o postupu přidávání souboru do MPQ

Kompresní funkce

Funkce Popis
SCompImplode Komprimuje blok dat metodou IMPLODE (Pkware Data Compression Library)
SCompExplode Dekomprimuje block dat zkomprimovaný funkcí SCompImplode
SCompCompress Komprimuje blok dat použitím některé z kompresních metod použitých v MPQ
SCompDecompress Dekomprimuje blok dat zkomprimovaný funkcí SCompCompress

Příklad

Zde je příklad funkce, která extrahuje jeden soubor z archivu MPQ.

//-----------------------------------------------------------------------------
// Extracts an archived file and saves it to the disk.
//
// Parameters :
//
//   char * szArchiveName  - Archive file name
//   char * szArchivedFile - Name/number of archived file.
//   char * szFileName     - Name of the target disk file.

static int ExtractFile(char * szArchiveName, char * szArchivedFile, char * szFileName)
{
    HANDLE hMpq   = NULL;          // Open archive handle
    HANDLE hFile  = NULL;          // Archived file handle
    HANDLE handle = NULL;          // Disk file handle
    int    nError = ERROR_SUCCESS; // Result value

    // Open an archive, e.g. "d2music.mpq"
    if(nError == ERROR_SUCCESS)
    {
        if(!SFileOpenArchive(szArchiveName, 0, 0, &hMpq))
            nError = GetLastError();
    }
    
    // Open a file in the archive, e.g. "data\global\music\Act1\tristram.wav"
    if(nError == ERROR_SUCCESS)            
    {
        if(!SFileOpenFileEx(hMpq, szArchivedFile, 0, &hFile))
            nError = GetLastError()
    }

    // Create the target file
    if(nError == ERROR_SUCCESS)
    {
        handle = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
        if(handle == INVALID_HANDLE_VALUE)
            nError = GetLastError();
    }

    // Read the file from the archive
    if(nError == ERROR_SUCCESS)
    {
        char  szBuffer[0x10000];
        DWORD dwBytes = 1;

        while(dwBytes > 0)
        {
            SFileReadFile(hFile, szBuffer, sizeof(szBuffer), &dwBytes, NULL);
            if(dwBytes > 0)
                WriteFile(handle, szBuffer, dwBytes, &dwBytes, NULL);
        }
    }        

    // Cleanup and exit
    if(handle != NULL)
        CloseHandle(handle);
    if(hFile != NULL)
        SFileCloseFile(hFile);
    if(hMpq != NULL)
        SFileCloseArchive(hMpq);

    return nError;
}