//****************************************************************** // MPQapi.cpp // File pack API // By Michael O'Brien (6/1/96) && Patrick Wyatt (6/24/96) //****************************************************************** #include "diablo.h" #pragma hdrstop #include "storm/h/storm.h" #include "engine.h" #include "mpqapi.h" //****************************************************************** // debugging //****************************************************************** #define DUMP_BLOCK 0 // 0 in final #ifdef NDEBUG #undef DUMP_BLOCK #define DUMP_BLOCK 0 #endif //****************************************************************** // extern //****************************************************************** extern BYTE gbMaxPlayers; //****************************************************************** // public //****************************************************************** char gszProgKey[] = "Hellfire"; //****************************************************************** // private //****************************************************************** #define MPQ_HIDE_ATTR (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM) static HANDLE sghArchive = INVALID_HANDLE_VALUE; static BOOL sgbChanged; // extra header -- non-standard .MPQ stuff #define FILE_EXHDR_SIZE 72 #define FILE_EXHDR_OFFSET sizeof(FILEHEADER) // block table #define BLOCK_ENTRIES 2048 #define BLOCK_TBL_SIZE (BLOCK_ENTRIES * sizeof(BLOCKENTRY)) #define BLOCK_TBL_OFFSET (FILE_EXHDR_OFFSET + FILE_EXHDR_SIZE) static BLOCKENTRYPTR sgpBlockTbl; // hash table #define HASH_ENTRIES 2048 // must be pow2 #define HASH_TBL_SIZE (HASH_ENTRIES * sizeof(HASHENTRY)) #define HASH_TBL_OFFSET (BLOCK_TBL_OFFSET + BLOCK_TBL_SIZE) static HASHENTRYPTR sgpHashTbl; // offset past last file where next added file will start #define FIRST_FILE_START (HASH_TBL_OFFSET + HASH_TBL_SIZE) static DWORD sgdwNextFileStart; // minimum space we will allow as standalone "free" block in file #define MIN_FREE_SIZE 1024 static BYTE sgbSaveCreationKey = FALSE; #if IS_VERSION(SHAREWARE) static char sgszArchiveKey[] = "Audio Playback "; #else static char sgszArchiveKey[] = "Video Player "; #endif #define CREATION_TIME 0 #define LASTWRITE_TIME 1 #define NUM_TIMES 2 typedef struct EXFILEHEADER { BYTE bData[FILE_EXHDR_SIZE]; } EXFILEHEADER; typedef struct FULLHEADER { FILEHEADER hdr; EXFILEHEADER exhdr; } FULLHEADER; //****************************************************************** // imported functions //****************************************************************** void InitializeHashSource(); void Decrypt(LPDWORD data, DWORD bytes, DWORD key); void Encrypt(LPDWORD data, DWORD bytes, DWORD key); DWORD Hash(const char *filename, int hashtype); DWORD Compress(LPBYTE data, DWORD bytes); //****************************************************************** //****************************************************************** static void xor_timestamp(FILETIME * pTime) { DWORD dwKey = 0xf0761ab; LPBYTE lpStamp = (LPBYTE) pTime; DWORD dwBytes = sizeof(FILETIME); while (dwBytes--) { *lpStamp++ ^= (BYTE) dwKey; dwKey = _rotl(dwKey,1); } } //****************************************************************** //****************************************************************** static BOOL reg_get_stamps(FILETIME * pft,DWORD dwSize) { ZeroMemory(pft,dwSize); DWORD dwBytes; if (! SRegLoadData(gszProgKey,sgszArchiveKey,0,pft,dwSize,&dwBytes)) return FALSE; if (dwBytes != dwSize) return FALSE; while (dwSize >= sizeof FILETIME) { xor_timestamp(pft++); dwSize -= sizeof FILETIME; } return TRUE; } //****************************************************************** //****************************************************************** static void reg_set_stamps(FILETIME * pft,DWORD dwSize) { // only update registry stamps for multiplayer mode app_assert(gbMaxPlayers != 1); FILETIME * pft2 = pft; for (DWORD d = dwSize; d >= sizeof FILETIME; d -= sizeof FILETIME) xor_timestamp(pft2++); // pjw.patch1.start // SRegSaveData(gszProgKey,sgszArchiveKey,SREG_FLAG_FLUSHTODISK,pft,dwSize); SRegSaveData(gszProgKey,sgszArchiveKey,0,pft,dwSize); // pjw.patch1.end } //****************************************************************** //****************************************************************** /* pjw.patch1.start static int stamp_compare(FILETIME * pf1,FILETIME * pf2) { return ! memcmp(pf1,pf2,sizeof FILETIME); } pjw.patch1.end */ //****************************************************************** //****************************************************************** BOOL MPQSetAttributes(const char * pszArchive,BOOL bHide) { app_assert(pszArchive); // get the attributes for the file DWORD dwAttr; if (0xffffffff == (dwAttr = GetFileAttributes(pszArchive))) { if (ERROR_FILE_NOT_FOUND == GetLastError()) return TRUE; return FALSE; } DWORD dwWantAttr = bHide ? MPQ_HIDE_ATTR : 0; if (dwAttr == dwWantAttr) return TRUE; return SetFileAttributes(pszArchive,dwWantAttr); } //****************************************************************** //****************************************************************** BOOL MPQCompareTimeStamps(const char * pszArchive,DWORD dwChar) { /* pjw.patch1.start app_assert(pszArchive); app_assert(dwChar < MAX_CHARACTERS); // get archive timestamps from registry FILETIME ft[MAX_CHARACTERS][NUM_TIMES]; if (! reg_get_stamps(&ft[0][0],sizeof ft)) return FALSE; // get archive file timestamps WIN32_FIND_DATA finddata; HANDLE findhandle = FindFirstFile(pszArchive,&finddata); if (findhandle == INVALID_HANDLE_VALUE) return FALSE; FindClose(findhandle); return stamp_compare(&finddata.ftCreationTime,&ft[dwChar][CREATION_TIME]) && stamp_compare(&finddata.ftLastWriteTime,&ft[dwChar][LASTWRITE_TIME]); pjw.patch1.end */ return TRUE; } //****************************************************************** //****************************************************************** void MPQMungeStamps(DWORD dwChar) { /* pjw.patch1.start // only update registry stamps for multiplayer mode if (gbMaxPlayers == 1) return; app_assert(dwChar < MAX_CHARACTERS); FILETIME ft[MAX_CHARACTERS][NUM_TIMES]; reg_get_stamps(&ft[0][0],sizeof ft); ft[dwChar][0].dwHighDateTime = 0x78341348; reg_set_stamps(&ft[0][0],sizeof ft); pjw.patch1.end */ } //****************************************************************** //****************************************************************** static void MPQUpdateLastWriteTimeStamp(const char * pszArchive,DWORD dwChar) { // only update registry stamps for multiplayer mode if (gbMaxPlayers == 1) return; app_assert(pszArchive); app_assert(dwChar < MAX_CHARACTERS); // get archive timestamps from registry FILETIME ft[MAX_CHARACTERS][NUM_TIMES]; reg_get_stamps(&ft[0][0],sizeof ft); // get archive file timestamps WIN32_FIND_DATA finddata; HANDLE findhandle = FindFirstFile(pszArchive,&finddata); if (findhandle == INVALID_HANDLE_VALUE) return; FindClose(findhandle); // update timestamp ft[dwChar][LASTWRITE_TIME] = finddata.ftLastWriteTime; // save back to registry reg_set_stamps(&ft[0][0],sizeof ft); } //****************************************************************** //****************************************************************** void MPQUpdateCreationTimeStamp(const char * pszArchive,DWORD dwChar) { // only update registry stamps for multiplayer mode if (gbMaxPlayers == 1) return; app_assert(pszArchive); app_assert(dwChar < MAX_CHARACTERS); // get archive timestamps from registry FILETIME ft[MAX_CHARACTERS][NUM_TIMES]; reg_get_stamps(&ft[0][0],sizeof ft); // get archive file timestamps WIN32_FIND_DATA finddata; HANDLE findhandle = FindFirstFile(pszArchive,&finddata); if (findhandle == INVALID_HANDLE_VALUE) return; FindClose(findhandle); // update timestamp ft[dwChar][CREATION_TIME] = finddata.ftCreationTime; reg_set_stamps(&ft[0][0],sizeof ft); } //****************************************************************** //****************************************************************** #if DUMP_BLOCK static const char * fflags_2_str(DWORD dwFlags) { if (dwFlags & MPQ_ADD_COMPRESSED) { if (dwFlags & MPQ_ADD_ENCRYPTED) return "ENCRYPT+COMPRESS"; return "COMPRESS"; } if (dwFlags & MPQ_ADD_ENCRYPTED) return "ENCRYPT"; return "NONE"; } #endif //****************************************************************** //****************************************************************** #if DUMP_BLOCK static void dump_blocktbl(void) { FILE * f = fopen("c:\\block.txt","wb"); if (! f) return; fprintf(f," start + size = end\r\n"); DWORD dwPosition = FIRST_FILE_START; while (dwPosition != sgdwNextFileStart) { BLOCKENTRY * pBlk = sgpBlockTbl; for (DWORD i = BLOCK_ENTRIES; i--; pBlk++) { if (pBlk->offset != dwPosition) continue; fprintf(f,"%8d + %8d = %8d %s\r\n", pBlk->offset, pBlk->sizealloc, pBlk->offset + pBlk->sizealloc, fflags_2_str(pBlk->flags) ); dwPosition = pBlk->offset + pBlk->sizealloc; break; } if (i == -1) { fclose(f); void myDebugBreak(); myDebugBreak(); app_fatal("Bad block table"); } } fprintf(f,"next file start: %d\r\n",sgdwNextFileStart); fclose(f); } #endif //****************************************************************** //****************************************************************** static BLOCKENTRY * get_free_block(DWORD * pdwBlockIndex) { BLOCKENTRY * pBlk = sgpBlockTbl; for (DWORD i = 0; i < BLOCK_ENTRIES; i++, pBlk++) { if (pBlk->offset) continue; if (pBlk->sizealloc) continue; if (pBlk->flags) continue; if (pBlk->sizefile) continue; if (pdwBlockIndex) *pdwBlockIndex = i; return pBlk; } app_fatal("Out of free block entries"); return NULL; } //****************************************************************** //****************************************************************** static void add_free_block(DWORD dwOffset,DWORD dwSize) { // see if we can merge this block with an existing free block BLOCKENTRY * pBlk = sgpBlockTbl; for (DWORD i = BLOCK_ENTRIES; i--; pBlk++) { // is this block unused? if (pBlk->offset == 0) continue; if (pBlk->flags != 0) continue; if (pBlk->sizefile != 0) continue; if (pBlk->offset + pBlk->sizealloc == dwOffset) { dwOffset = pBlk->offset; dwSize += pBlk->sizealloc; } else if (dwOffset + dwSize == pBlk->offset) { dwSize += pBlk->sizealloc; } else { continue; } // try adding the new larger block. // NOTE: in the worst case, we free a block which is between // two free blocks, which causes only one deep recursion ZeroMemory(pBlk,sizeof(*pBlk)); add_free_block(dwOffset,dwSize); return; } if (dwOffset + dwSize > sgdwNextFileStart) app_fatal("MPQ free list error"); // is this block at the end of the file? if (dwOffset + dwSize == sgdwNextFileStart) { sgdwNextFileStart = dwOffset; return; } // create a new block entry pBlk = get_free_block(NULL); pBlk->offset = dwOffset; pBlk->sizealloc = dwSize; pBlk->sizefile = 0; pBlk->flags = 0; } //****************************************************************** //****************************************************************** static DWORD get_free_space(DWORD dwSize,DWORD * pdwSizeAlloc) { DWORD dwOffset; // see if there is a space large enough in an existing block BLOCKENTRY * pBlk = sgpBlockTbl; for (DWORD i = BLOCK_ENTRIES; i--; pBlk++) { // is this block unused? if (pBlk->offset == 0) continue; if (pBlk->flags != 0) continue; if (pBlk->sizefile != 0) continue; if (pBlk->sizealloc < dwSize) continue; // use a portion of this block dwOffset = pBlk->offset; *pdwSizeAlloc = dwSize; // fixup this block pBlk->offset += dwSize; pBlk->sizealloc -= dwSize; // did we use the entire block? if (! pBlk->sizealloc) ZeroMemory(pBlk,sizeof(BLOCKENTRY)); return dwOffset; } // use free space at end of .MPQ file *pdwSizeAlloc = dwSize; dwOffset = sgdwNextFileStart; sgdwNextFileStart += dwSize; return dwOffset; } //****************************************************************** //****************************************************************** static DWORD SearchHashEntry( DWORD hashindex, DWORD hashcheck0, DWORD hashcheck1, LCID lcid ) { DWORD dwCount = HASH_ENTRIES; for ( DWORD entry = hashindex & (HASH_ENTRIES - 1); (sgpHashTbl+entry)->block != HASH_BLOCK_UNUSED; entry = (entry + 1) & (HASH_ENTRIES - 1) ) { if (! dwCount--) break; if ((sgpHashTbl+entry)->hashcheck[0] != hashcheck0) continue; if ((sgpHashTbl+entry)->hashcheck[1] != hashcheck1) continue; if ((sgpHashTbl+entry)->lcid != lcid) continue; if ((sgpHashTbl+entry)->block == HASH_BLOCK_FREED) continue; return entry; } return HASH_ENTRY_UNUSED; } //****************************************************************** //****************************************************************** static BOOL WriteFileHeader() { FULLHEADER fhdr; // initialize header ZeroMemory(&fhdr,sizeof(fhdr)); // fill in .mpq header fhdr.hdr.signature = SIGNATURE; fhdr.hdr.headersize = sizeof(FILEHEADER); fhdr.hdr.filesize = GetFileSize(sghArchive,NULL); fhdr.hdr.version = VERSION; fhdr.hdr.sectorsizeid = SECTORSIZEID; fhdr.hdr.hashoffset = HASH_TBL_OFFSET; fhdr.hdr.blockoffset = BLOCK_TBL_OFFSET; fhdr.hdr.hashcount = HASH_ENTRIES; fhdr.hdr.blockcount = BLOCK_ENTRIES; DWORD dwTemp; if (0xffffffff == SetFilePointer(sghArchive,0,NULL,FILE_BEGIN)) return FALSE; if (! WriteFile(sghArchive,&fhdr.hdr,sizeof(fhdr),&dwTemp,NULL)) return FALSE; return sizeof(fhdr) == dwTemp; } //****************************************************************** //****************************************************************** static BOOL WriteBlockTable() { #if DUMP_BLOCK dump_blocktbl(); #endif if (0xffffffff == SetFilePointer(sghArchive,BLOCK_TBL_OFFSET,NULL,FILE_BEGIN)) return FALSE; DWORD dwTemp; Encrypt((LPDWORD) sgpBlockTbl,BLOCK_TBL_SIZE,Hash("(block table)",HASH_ENCRYPTKEY)); BOOL bResult = WriteFile(sghArchive,sgpBlockTbl,BLOCK_TBL_SIZE,&dwTemp,NULL); Decrypt((LPDWORD) sgpBlockTbl,BLOCK_TBL_SIZE,Hash("(block table)",HASH_ENCRYPTKEY)); return bResult && BLOCK_TBL_SIZE == dwTemp; } //****************************************************************** //****************************************************************** static BOOL WriteHashTable() { if (0xffffffff == SetFilePointer(sghArchive,HASH_TBL_OFFSET,NULL,FILE_BEGIN)) return FALSE; DWORD dwTemp; Encrypt((LPDWORD) sgpHashTbl,HASH_TBL_SIZE,Hash("(hash table)",HASH_ENCRYPTKEY)); BOOL bResult = WriteFile(sghArchive,sgpHashTbl,HASH_TBL_SIZE,&dwTemp,NULL); Decrypt((LPDWORD) sgpHashTbl,HASH_TBL_SIZE,Hash("(hash table)",HASH_ENCRYPTKEY)); return bResult && HASH_TBL_SIZE == dwTemp; } //****************************************************************** //****************************************************************** static BOOL SetEOF() { if (0xffffffff == SetFilePointer(sghArchive,sgdwNextFileStart,NULL,FILE_BEGIN)) return FALSE; return SetEndOfFile(sghArchive); } //****************************************************************** //****************************************************************** static BOOL read_mpq_file_hdr(FULLHEADER * pHdr,DWORD * pdwNextFileStart) { BOOL bError; DWORD dwBytes; app_assert(pHdr); app_assert(pdwNextFileStart); // read file hdr DWORD dwFileSize = GetFileSize(sghArchive,NULL); *pdwNextFileStart = dwFileSize; if (dwFileSize == 0xffffffff) bError = TRUE; else if (sizeof(*pHdr) > dwFileSize) bError = TRUE; else if (! ReadFile(sghArchive,pHdr,sizeof(*pHdr),&dwBytes,NULL)) bError = TRUE; else if (sizeof(*pHdr) != dwBytes) bError = TRUE; else if (pHdr->hdr.signature != SIGNATURE) bError = TRUE; else if (pHdr->hdr.headersize != sizeof(FILEHEADER)) bError = TRUE; else if (pHdr->hdr.version > VERSION) bError = TRUE; else if (pHdr->hdr.sectorsizeid != SECTORSIZEID) bError = TRUE; else if (pHdr->hdr.filesize != dwFileSize) bError = TRUE; else if (pHdr->hdr.hashoffset != HASH_TBL_OFFSET) bError = TRUE; else if (pHdr->hdr.blockoffset != BLOCK_TBL_OFFSET) bError = TRUE; else if (pHdr->hdr.hashcount != HASH_ENTRIES) bError = TRUE; else if (pHdr->hdr.blockcount != BLOCK_ENTRIES) bError = TRUE; else // NO ERROR (finally) bError = FALSE; if (bError) { // kill off any existing file hdr information if (0xffffffff == SetFilePointer(sghArchive,0,NULL,FILE_BEGIN)) return FALSE; if (! SetEndOfFile(sghArchive)) return FALSE; // initialize file header ZeroMemory(pHdr,sizeof(*pHdr)); // fill in .mpq header pHdr->hdr.signature = SIGNATURE; pHdr->hdr.headersize = sizeof(FILEHEADER); pHdr->hdr.sectorsizeid = SECTORSIZEID; pHdr->hdr.version = VERSION; // set location of start of next file *pdwNextFileStart = FIRST_FILE_START; // modified the file sgbChanged = TRUE; sgbSaveCreationKey = TRUE; } return TRUE; } //****************************************************************** //****************************************************************** static DWORD SearchHashName(const char * pszName) { return SearchHashEntry( Hash(pszName,HASH_INDEX), Hash(pszName,HASH_CHECK0), Hash(pszName,HASH_CHECK1), MAKELCID(MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL),SORT_DEFAULT) ); } //****************************************************************** //****************************************************************** static void mpq_delete_file(const char * pszName) { DWORD dwEntry = SearchHashName(pszName); if (dwEntry == HASH_ENTRY_UNUSED) return; HASHENTRY * pHash = sgpHashTbl + dwEntry; BLOCKENTRY * pBlk = sgpBlockTbl + pHash->block; // we can't set the hash block to HASH_BLOCK_UNUSED, because // we use closed hashing. Another file may have hashed // to the same spot, and so was added to the hash table // after this hash entry. Therefore, just mark the hash // entry as freed, but not unused pHash->block = HASH_BLOCK_FREED; // free the block entry and give back the memory DWORD dwOffset = pBlk->offset; DWORD dwSize = pBlk->sizealloc; ZeroMemory(pBlk,sizeof(*pBlk)); add_free_block(dwOffset,dwSize); // modified the file sgbChanged = TRUE; } //****************************************************************** //****************************************************************** void MPQDeleteFile(const char * pszName) { app_assert(sghArchive != INVALID_HANDLE_VALUE); app_assert(pszName); // if the specified file is not part of the archive, // then consider the delete operation successful mpq_delete_file(pszName); } //****************************************************************** //****************************************************************** void MPQDeleteFiles(TGetNameFcn fnGetName) { app_assert(sghArchive != INVALID_HANDLE_VALUE); app_assert(fnGetName); DWORD dwIndex = 0; char szName[MAX_PATH]; while (fnGetName(dwIndex++,szName)) mpq_delete_file(szName); } //****************************************************************** //****************************************************************** static BLOCKENTRY * InsertIntoHash(const char * pszName,BLOCKENTRY * pBlk,DWORD dwBlock) { DWORD hashindex = Hash(pszName,HASH_INDEX); DWORD hashcheck0 = Hash(pszName,HASH_CHECK0); DWORD hashcheck1 = Hash(pszName,HASH_CHECK1); if (HASH_ENTRY_UNUSED != SearchHashEntry( hashindex, hashcheck0, hashcheck1, MAKELCID(MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL),SORT_DEFAULT))) app_fatal("Hash collision between \"%s\" and existing file\n",pszName); // find free slot in hash table long lCount = HASH_ENTRIES; DWORD entry = hashindex & (HASH_ENTRIES - 1); while (lCount--) { if ((sgpHashTbl+entry)->block == HASH_BLOCK_UNUSED) break; if ((sgpHashTbl+entry)->block == HASH_BLOCK_FREED) break; entry = (entry + 1) & (HASH_ENTRIES - 1); } if (lCount < 0) app_fatal("Out of hash space"); if (! pBlk) pBlk = get_free_block(&dwBlock); (sgpHashTbl+entry)->hashcheck[0] = hashcheck0; (sgpHashTbl+entry)->hashcheck[1] = hashcheck1; (sgpHashTbl+entry)->lcid = MAKELCID(MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL),SORT_DEFAULT); (sgpHashTbl+entry)->block = dwBlock; return pBlk; } //****************************************************************** //****************************************************************** static BOOL WriteFileData( const char * pszName, const BYTE * pbData, DWORD dwLen, BLOCKENTRY * pBlk ) { // DETERMINE THE FILE NAME PORTION OF THE PATH NAME const char * pszTemp; while (NULL != (pszTemp = strchr(pszName,':'))) pszName = pszTemp + 1; while (NULL != (pszTemp = strchr(pszName,'\\'))) pszName = pszTemp + 1; // CREATE AN ENCRYPTION KEY BASED ON THE FILE NAME DWORD key = Hash(pszName,HASH_ENCRYPTKEY); DWORD sectors = (dwLen + SECTORSIZE - 1) / SECTORSIZE; DWORD sectoroffsetsize = (sectors + 1) * sizeof(DWORD); // find free space for file DWORD dwSizeAllocGuess = dwLen + sectoroffsetsize; pBlk->offset = get_free_space(dwSizeAllocGuess,&pBlk->sizealloc); pBlk->sizefile = dwLen; pBlk->flags = MPQ_ADD_COMPRESSED | MPQ_ADD_ALLOCATED; if (0xffffffff == SetFilePointer(sghArchive,pBlk->offset,NULL,FILE_BEGIN)) return FALSE; DWORD sector = 0; DWORD destsize = 0; static BYTE buffer[SECTORSIZE]; LPDWORD sectoroffsettable = NULL; while (dwLen) { for (int loop = 0; loop < SECTORSIZE; ++loop) buffer[loop] += 0xAA; // PERFORM COMPRESSION DWORD bytes = min(dwLen,SECTORSIZE); CopyMemory(buffer,pbData,bytes); pbData += bytes; bytes = Compress(buffer,bytes); // LEAVE SPACE FOR THE SECTOR OFFSET TABLE. if (! sector) { sectoroffsetsize = (sectors+1)*sizeof(DWORD); sectoroffsettable = (LPDWORD) DiabloAllocPtrSig(sectoroffsetsize,'MPQt'); ZeroMemory(sectoroffsettable,sectoroffsetsize); if (! WriteFile(sghArchive,sectoroffsettable,sectoroffsetsize,§oroffsetsize,NULL)) goto error; destsize += sectoroffsetsize; } // SAVE THE SECTOR OFFSET app_assert(sectoroffsettable); *(sectoroffsettable+sector) = destsize; // PERFORM ENCRYPTION -- not encrypted for save files // Encrypt((LPDWORD)buffer,bytes & 0xFFFFFFFC,key+sector); // WRITE THE SECTOR if (! WriteFile(sghArchive,buffer,bytes,&bytes,NULL)) goto error; ++sector; if (dwLen > SECTORSIZE) dwLen -= SECTORSIZE; else dwLen = 0; destsize += bytes; } app_assert(sectoroffsettable); *(sectoroffsettable+sector) = destsize; // bug in mopaq 1.91 -- fixed in 1.92 // sector table is not supposed to be encrypted for unencrypted files // Encrypt(sectoroffsettable,sectoroffsetsize,key+0xFFFFFFFF); if (0xffffffff == SetFilePointer(sghArchive,-(LONG)destsize,NULL,FILE_CURRENT)) goto error; if (! WriteFile(sghArchive,sectoroffsettable,sectoroffsetsize,§oroffsetsize,NULL)) goto error; if (0xffffffff == SetFilePointer(sghArchive,destsize-sectoroffsetsize,NULL,FILE_CURRENT)) goto error; DiabloFreePtr(sectoroffsettable); // make sure the file fit into the hole we provided app_assert(destsize <= pBlk->sizealloc); // give back any extra space we might have allocated if (destsize < pBlk->sizealloc) { DWORD dwLeftover = pBlk->sizealloc - destsize; if (dwLeftover >= MIN_FREE_SIZE) { pBlk->sizealloc = destsize; add_free_block(pBlk->offset + destsize, dwLeftover); } } return TRUE; error: if (sectoroffsettable) DiabloFreePtr(sectoroffsettable); return FALSE; } //****************************************************************** //****************************************************************** BOOL MPQAddFile(const char * pszName,const BYTE * pbData,DWORD dwLen) { app_assert(sghArchive != INVALID_HANDLE_VALUE); app_assert(pszName); app_assert(pbData); app_assert(dwLen); // think positive BOOL bResult = TRUE; sgbChanged = TRUE; // delete any existing file with the same name mpq_delete_file(pszName); // insert the new file data BLOCKENTRY * pBlk = InsertIntoHash(pszName,NULL,0); if (! WriteFileData(pszName,pbData,dwLen,pBlk)) { mpq_delete_file(pszName); return FALSE; } return TRUE; } //****************************************************************** //****************************************************************** void MPQRenameFile(const char * pszOld,const char * pszNew) { app_assert(sghArchive != INVALID_HANDLE_VALUE); app_assert(pszOld); app_assert(pszNew); DWORD dwEntry = SearchHashName(pszOld); if (dwEntry == HASH_ENTRY_UNUSED) return; HASHENTRY * pHash = sgpHashTbl + dwEntry; BLOCKENTRY * pBlk = sgpBlockTbl + pHash->block; DWORD dwBlock = pHash->block; // we can't set the hash block to HASH_BLOCK_UNUSED, because // we use closed hashing. Another file may have hashed // to the same spot, and so was added to the hash table // after this hash entry. Therefore, just mark the hash // entry as freed, but not unused pHash->block = HASH_BLOCK_FREED; // create a new hash entry which references the existing block InsertIntoHash(pszNew,pBlk,dwBlock); // modified the file sgbChanged = TRUE; } //****************************************************************** //****************************************************************** BOOL MPQFileExists(const char * pszName) { app_assert(sghArchive != INVALID_HANDLE_VALUE); app_assert(pszName); return SearchHashName(pszName) != HASH_ENTRY_UNUSED; } //****************************************************************** //****************************************************************** static void CloseArchive(const char * pszArchive,BOOL bFree,DWORD dwChar) { if (bFree) { DiabloFreePtr(sgpBlockTbl); DiabloFreePtr(sgpHashTbl); } if (sghArchive != INVALID_HANDLE_VALUE) { CloseHandle(sghArchive); sghArchive = INVALID_HANDLE_VALUE; } if (sgbChanged) { // since we wrote the file, update the last write timestamp sgbChanged = FALSE; MPQUpdateLastWriteTimeStamp(pszArchive,dwChar); } if (sgbSaveCreationKey) { // update the file creation timestamp if the archive was just created sgbSaveCreationKey = FALSE; MPQUpdateCreationTimeStamp(pszArchive,dwChar); } } //****************************************************************** //****************************************************************** BOOL MPQOpenArchive(const char * pszArchive,BOOL bHide,DWORD dwChar) { DWORD dwTemp; app_assert(pszArchive); app_assert(sghArchive == INVALID_HANDLE_VALUE); InitializeHashSource(); if (! MPQSetAttributes(pszArchive,bHide)) return FALSE; DWORD dwFlags = gbMaxPlayers > 1 ? FILE_FLAG_WRITE_THROUGH : 0; sgbSaveCreationKey = FALSE; sghArchive = CreateFile( pszArchive, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, dwFlags, NULL ); if (sghArchive == INVALID_HANDLE_VALUE) { DWORD dwAttr = bHide ? MPQ_HIDE_ATTR : 0; if (INVALID_HANDLE_VALUE == (sghArchive = CreateFile( pszArchive, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, dwAttr | dwFlags, NULL ))) return FALSE; sgbSaveCreationKey = TRUE; sgbChanged = TRUE; } // if the file is still loaded in memory, skip reading file if (sgpBlockTbl && sgpHashTbl) return TRUE; FULLHEADER fhdr; ZeroMemory(&fhdr,sizeof(fhdr)); if (! read_mpq_file_hdr(&fhdr,&sgdwNextFileStart)) goto error; // allocate enough memory for all the blocks we might need app_assert(! sgpBlockTbl); sgpBlockTbl = (BLOCKENTRYPTR) DiabloAllocPtrSig(BLOCK_TBL_SIZE,'MPQt'); ZeroMemory(sgpBlockTbl,BLOCK_TBL_SIZE); // read in block table if (fhdr.hdr.blockcount) { app_assert(fhdr.hdr.blockcount == BLOCK_ENTRIES); app_assert(fhdr.hdr.blockoffset == BLOCK_TBL_OFFSET); if (0xffffffff == SetFilePointer(sghArchive,BLOCK_TBL_OFFSET,NULL,FILE_BEGIN)) goto error; if (! ReadFile(sghArchive,sgpBlockTbl,BLOCK_TBL_SIZE,&dwTemp,NULL)) goto error; app_assert(BLOCK_TBL_SIZE == dwTemp); Decrypt( (LPDWORD) sgpBlockTbl, BLOCK_TBL_SIZE, Hash("(block table)", HASH_ENCRYPTKEY) ); } // allocate enough memory for hash table app_assert(! sgpHashTbl); sgpHashTbl = (HASHENTRYPTR) DiabloAllocPtrSig(HASH_TBL_SIZE,'MPQt'); FillMemory(sgpHashTbl,HASH_TBL_SIZE,0xff); // read in hash table if (fhdr.hdr.hashcount) { app_assert(fhdr.hdr.hashcount == HASH_ENTRIES); app_assert(fhdr.hdr.hashoffset == HASH_TBL_OFFSET); if (0xffffffff == SetFilePointer(sghArchive,HASH_TBL_OFFSET,NULL,FILE_BEGIN)) goto error; if (! ReadFile(sghArchive,sgpHashTbl,HASH_TBL_SIZE,&dwTemp,NULL)) goto error; app_assert(HASH_TBL_SIZE == dwTemp); Decrypt( (LPDWORD) sgpHashTbl, HASH_TBL_SIZE, Hash("(hash table)", HASH_ENCRYPTKEY) ); } return TRUE; error: CloseArchive(pszArchive,TRUE,dwChar); return FALSE; } //****************************************************************** //****************************************************************** BOOL MPQCloseArchive(const char * pszArchive,BOOL bFree,DWORD dwChar) { // write archive data only if the file open & changed BOOL bResult; if (sghArchive == INVALID_HANDLE_VALUE) bResult = TRUE; else if (! sgbChanged) bResult = TRUE; else if (! SetEOF()) bResult = FALSE; else if (! WriteFileHeader()) bResult = FALSE; else if (! WriteBlockTable()) bResult = FALSE; else if (! WriteHashTable()) bResult = FALSE; else bResult = TRUE; CloseArchive(pszArchive,bFree,dwChar); return bResult; }