#include "base/md5.h" #include "mpshared/packfile.h" #include "mpshared/misc.h" #include "base/log.h" #include #include #include #include namespace wi { //#define INCL_RECORDOPENS #if defined(INCL_RECORDOPENS) void RecordOpen(char *pszApi, char *pszFile, dword dw) { FILE *pfil = fopen("openlog.txt", "a+"); fseek(pfil, 0, SEEK_END); fprintf(pfil, "0x%08lx: %s %s\n", dw, pszApi, pszFile); fclose(pfil); } void RecordClose(dword dw) { FILE *pfil = fopen("openlog.txt", "r+"); char sz[128]; while (true) { long pos = ftell(pfil); if (fgets(sz, sizeof(sz) - 1, pfil) == NULL) break; char szCompare[32]; sprintf(szCompare, "0x%08lx", dw); if (strncmp(sz, szCompare, 10) == 0) { fseek(pfil, pos, SEEK_SET); char ch = 'x'; fwrite(&ch, 1, 1, pfil); fclose(pfil); return; } } fclose(pfil); } #endif // // Reads packed files // PackFileReader::PackFileReader() { m_crnfo = 0; m_crnfoAlloc = 0; m_arnfo = NULL; } PackFileReader::~PackFileReader() { while (m_crnfo != 0) Pop(); delete[] m_arnfo; } File *PackFileReader::fopen(const char *pszFn, const char *pszMode) { // Only support read binary mode if (strcmp(pszMode, "rb") != 0) return NULL; // Find this directory entry ReaderInfo *prnfo; DirEntry dir; if (!FindDirEntry(pszFn, &dir, &prnfo)) { LOG() << "can't find dir entry " << pszFn; return NULL; } // Alloc the file stream info for this file, fill it in File *pfil = (File *)new byte[sizeof(File) + sizeof(word) * (dir.cRecs - 1)]; if (pfil == NULL) return NULL; pfil->prnfo = prnfo; pfil->offRecStart = 0; pfil->offStream = 0; pfil->nRecOffStream = 0; pfil->cbTotal = 0; pfil->cRecs = dir.cRecs; pfil->nRecFirst = dir.nRecFirst; // Remember the size of each record word *pcb = pfil->acb; word nRecFirst = BigWord(dir.nRecFirst); for (word nRec = nRecFirst; nRec < nRecFirst + dir.cRecs; nRec++) { word cbT; prnfo->ppdbReader->GetRecordSize(nRec, &cbT); pfil->cbTotal += cbT; *pcb++ = cbT; } // Remember we have one open prnfo->cOpen++; #if defined(INCL_RECORDOPENS) RecordOpen("fopen", pszFn, (dword)pfil); #endif return pfil; } int PackFileReader::fclose(File *pfil) { #if defined(INCL_RECORDOPENS) RecordClose((dword)pfil); #endif Assert(pfil->prnfo->cOpen > 0); pfil->prnfo->cOpen--; delete[] (byte *)pfil; return 0; } dword PackFileReader::fread(void *pv, dword cb, int c, File *pfil) { dword cbT = cb * c; byte *pb = (byte *)pv; word nRecFirst = BigWord(pfil->nRecFirst); while (cbT != 0 && pfil->nRecOffStream < pfil->cRecs) { dword offRecEnd = pfil->offRecStart + pfil->acb[pfil->nRecOffStream]; word cbLeft = (word)(offRecEnd - pfil->offStream); word cbReadNext = cbT < (dword)cbLeft ? (word)cbT : cbLeft; pfil->prnfo->ppdbReader->ReadRecord(nRecFirst + pfil->nRecOffStream, (word)(pfil->offStream - pfil->offRecStart), cbReadNext, pb); cbT -= cbReadNext; pb += cbReadNext; pfil->offStream += cbReadNext; if (pfil->offStream == offRecEnd && pfil->nRecOffStream < pfil->cRecs) { pfil->offRecStart = offRecEnd; pfil->nRecOffStream++; } } // If it was all read, return the count if (cbT == 0) return c; // It wasn't all read, return the count that was read. Note we may have a partial // item read which according to c-runtime docs is ok. dword cbRead = cb * c - cbT; return cbRead / cb; } int PackFileReader::fseek(File *pfil, int delta, int nOrigin) { dword offNew; switch (nOrigin) { case SEEK_CUR: // Current file position + delta offNew = pfil->offStream + delta; break; case SEEK_END: // End of file + delta offNew = pfil->cbTotal + delta; break; case SEEK_SET: // Start of file + delta offNew = delta; break; default: // Error return 1; } // Within file bounds? Error for now even though c-runtime allows this if ((long)offNew < 0 || offNew > pfil->cbTotal) return 1; // Special supported case for seeking to the end if (offNew == pfil->cbTotal) { if (pfil->cbTotal == 0) return 0; while (pfil->nRecOffStream < pfil->cRecs - 1) { pfil->offRecStart += pfil->acb[pfil->nRecOffStream]; pfil->nRecOffStream++; } pfil->offStream = offNew; return 0; } // Special case for seeking from the end backwards if (pfil->offStream == pfil->cbTotal) { if (pfil->nRecOffStream == pfil->cRecs) { pfil->nRecOffStream--; pfil->offRecStart -= pfil->acb[pfil->nRecOffStream]; } } // Need to update File * members. if (offNew < pfil->offRecStart + pfil->acb[pfil->nRecOffStream]) { // The new pos is in the current record or behind if (offNew < pfil->offRecStart) { // The new pos is in a previous record while (true) { pfil->nRecOffStream--; Assert((char)pfil->nRecOffStream >= 0); pfil->offRecStart -= pfil->acb[pfil->nRecOffStream]; if (offNew >= pfil->offRecStart) { pfil->offStream = offNew; break; } } } else { // The new pos is in the current record pfil->offStream = offNew; } } else { // The new pos is in a record ahead of the current record while (true) { pfil->offRecStart += pfil->acb[pfil->nRecOffStream]; pfil->nRecOffStream++; Assert(pfil->nRecOffStream < pfil->cRecs); if (offNew < pfil->offRecStart + pfil->acb[pfil->nRecOffStream]) { pfil->offStream = offNew; break; } } } return 0; } dword PackFileReader::ftell(File *pfil) { return pfil->offStream; } bool PackFileReader::EnumFiles(Enum *penm, int key, char *pszFn, int cb) { if (penm->m_pvNext == (void *)kEnmFirst) { if (m_crnfo == 0) { return false; } ReaderInfo *prnfo = NULL; if (key == PACKENUM_FIRST) { prnfo = &m_arnfo[0]; } if (key == PACKENUM_LAST) { prnfo = &m_arnfo[m_crnfo - 1]; } if (prnfo == NULL) { return false; } penm->m_pvNext = (void *)prnfo; penm->m_dwUser = 0; } ReaderInfo *prnfo = (ReaderInfo *)penm->m_pvNext; if ((int)penm->m_dwUser < prnfo->cEntries) { DirEntry *pdirT = &prnfo->pdir[penm->m_dwUser]; strncpyz(pszFn, pdirT->szFn, cb); penm->m_dwUser++; return true; } return false; } void *PackFileReader::MapFile(const char *pszFn, FileMap *pfmap, dword *pcb) { memset(pfmap, 0, sizeof(*pfmap)); // Find the file ReaderInfo *prnfo; DirEntry dir; if (!FindDirEntry(pszFn, &dir, &prnfo)) return NULL; word nRecFirst = BigWord(dir.nRecFirst); // For now don't allow mapping of multi-record files. If this is needed // later it can be done. byte *pb = NULL; if (dir.cRecs > 1) { File *pfil = this->fopen(pszFn, "rb"); if (pfil == NULL) { return NULL; } this->fseek(pfil, 0, SEEK_END); dword cb = ftell(pfil); this->fseek(pfil, 0, SEEK_SET); pb = new byte[cb]; if (pb == NULL) { this->fclose(pfil); return NULL; } if (this->fread(pb, cb, 1, pfil) != 1) { this->fclose(pfil); return NULL; } pfmap->prnfo = prnfo; pfmap->pvCookie = 0; pfmap->nRec = 0; pfmap->pbAlloced = pb; if (pcb != NULL) { *pcb = cb; } this->fclose(pfil); } else { // See if pdbReader will map this entry word cbRec; void *pvCookie; pb = prnfo->ppdbReader->MapRecord(nRecFirst, &pvCookie, &cbRec); if (pb == NULL) return NULL; // All is ok. pfmap->prnfo = prnfo; pfmap->nRec = nRecFirst; pfmap->pvCookie = pvCookie; pfmap->pbAlloced = NULL; if (pcb != NULL) *pcb = (dword)cbRec; } prnfo->cOpen++; #if defined(INCL_RECORDOPENS) RecordOpen("MapFile", pszFn, (dword)pfmap); #endif return pb; } void PackFileReader::UnmapFile(FileMap *pfmap) { #if defined(INCL_RECORDOPENS) RecordClose((dword)pfmap); #endif Assert(pfmap->prnfo->cOpen > 0); pfmap->prnfo->cOpen--; if (pfmap->pbAlloced != NULL) { delete[] pfmap->pbAlloced; } else { pfmap->prnfo->ppdbReader->UnmapRecord(pfmap->nRec, pfmap->pvCookie); } } bool PackFileReader::HashFile(const char *pszFn, byte *hash) { dword cb; FileMap fmap; byte *pb = (byte *)MapFile(pszFn, &fmap, &cb); if (pb == NULL) { return false; } MD5_CTX ctx; MD5Init(&ctx); MD5Update(&ctx, pb, cb); MD5Final(hash, &ctx); UnmapFile(&fmap); return true; } bool PackFileReader::GetPdbName(const char *pszFn, char *pszPdb, int cbPdb, char *pszDir, int cbDir) { ReaderInfo *prnfo; DirEntry dir; if (!FindDirEntry(pszFn, &dir, &prnfo)) return false; strncpyz(pszPdb, prnfo->pszFn, cbPdb); if (pszDir != NULL) { strncpyz(pszDir, prnfo->pszDir, cbDir); } return true; } bool PackFileReader::FindDirEntry(const char *psz, DirEntry *pdir, ReaderInfo **pprnfo) { // Convert to lower case char szT[kcbFilename]; // Lower case apis differ platform to platform, so fudge it strcpy(szT, psz); char *pszT = szT; while (*pszT != 0) { if (*pszT >= 'A' && *pszT <= 'Z') (*pszT) += 'a' - 'A'; pszT++; } // The most recently pushed PdbReader has priority for (int n = m_crnfo - 1; n >= 0; n--) { ReaderInfo *prnfo = &m_arnfo[n]; // Look through the directory entries for this file. The entries // are sorted in ascending order, so use a binary search. int nMin = 0; int nMax = prnfo->cEntries - 1; while (true) { if (nMax < nMin) break; int nCurrent = nMin + (nMax - nMin) / 2; DirEntry *pdirT = &prnfo->pdir[nCurrent]; int n = strcmp(szT, pdirT->szFn); if (n == 0) { *pdir = *pdirT; *pprnfo = prnfo; return true; } if (n > 0) { nMin = nCurrent + 1; continue; } if (n < 0) { nMax = nCurrent - 1; continue; } } } return false; } #define kcrnfoAlloc 25 bool PackFileReader::Push(const char *pszDir, const char *pszFn, PdbReader *ppdbReader) { // Grow as necessary if (m_crnfo == m_crnfoAlloc) { int crnfoAllocNew = m_crnfoAlloc + kcrnfoAlloc; ReaderInfo *prnfo = new ReaderInfo[crnfoAllocNew]; if (prnfo == NULL) return false; if (m_arnfo != NULL) { memcpy(prnfo, m_arnfo, m_crnfo * sizeof(ReaderInfo)); delete m_arnfo; } m_arnfo = prnfo; m_crnfoAlloc = crnfoAllocNew; } // First try to map the directory record word cb; void *pvCookie; DirEntry *pdir = (DirEntry *)ppdbReader->MapRecord(0, &pvCookie, &cb); if (pdir == NULL) return false; // That worked. Add this pdb to the list ReaderInfo *prnfo = &m_arnfo[m_crnfo]; m_crnfo++; prnfo->ppdbReader = ppdbReader; prnfo->pvCookie = pvCookie; prnfo->pdir = pdir; prnfo->cEntries = cb / sizeof(DirEntry); prnfo->cOpen = 0; // If pszDir is NULL, split pszFn into directory and filename. // prnfo->pszFn must just point to the basename. if (pszDir == NULL) { char *pszBasename = basename((char *)pszFn); int cchBase = (int)strlen(pszBasename); int cchFn = (int)strlen(pszFn); prnfo->pszDir = new char[cchFn - cchBase + 1]; strncpyz(prnfo->pszDir, pszFn, cchFn - cchBase + 1); int cch = (int)strlen(prnfo->pszDir); if (cch >= 1 && prnfo->pszDir[cch - 1] == '/') { prnfo->pszDir[cch - 1] = 0; } pszFn = pszBasename; } else { prnfo->pszDir = new char[strlen(pszDir) + 1]; strcpy(prnfo->pszDir, pszDir); } prnfo->pszFn = new char[strlen(pszFn) + 1]; strcpy(prnfo->pszFn, pszFn); return true; } bool PackFileReader::Pop() { // if the sound data file is missing, we can encounter this case if (m_crnfo == 0) return false; RemoveReader(m_crnfo - 1); return true; } void PackFileReader::RemoveReader(int rnfo) { ReaderInfo *prnfo = &m_arnfo[rnfo]; Assert(prnfo->cOpen == 0); Assert(m_crnfo != 0); // Free the directory record and close the pdb reader delete[] prnfo->pszDir; delete[] prnfo->pszFn; prnfo->ppdbReader->UnmapRecord(0, prnfo->pvCookie); prnfo->ppdbReader->Close(); delete prnfo->ppdbReader; // Move readers down memcpy(&m_arnfo[rnfo], &m_arnfo[rnfo + 1], (m_crnfo - 1 - rnfo) * ELEMENTSIZE(m_arnfo)); m_crnfo--; } bool PackFileReader::Delete(const char *pszDir, const char *pszFn) { // See if pszFn is pushed. If so is it in use? // If pushed and in use, can't delete for (int n = 0; n < m_crnfo; n++) { ReaderInfo *prnfo = &m_arnfo[n]; if (pszDir != NULL) { if (stricmp(pszDir, prnfo->pszDir) != 0) continue; } if (stricmp(pszFn, prnfo->pszFn) != 0) continue; if (prnfo->cOpen != 0) return false; RemoveReader(n); break; } // Reader removed. Now the file needs to be deleted return DeletePdb(pszDir, pszFn); } bool PackFileReader::Push(const char *pszDir, const char *pszFn) { PdbReader *ppdbReader = OpenPdb(pszDir, pszFn); if (ppdbReader == NULL) { return false; } if (!Push(pszDir, pszFn, ppdbReader)) { ppdbReader->Close(); delete ppdbReader; return false; } return true; } } // namespace wi