#include #include #include //#include #include "bigfile.h" #include "crc32.h" #include "BitIO.h" #include "LZSS.h" #ifdef BF_HOMEWORLD #include "types.h" #include "memory.h" #include "debug.h" #include "file.h" #endif // // The following value sets the threshold used by the bigfile system to decide which // files don't compress well enough to bother. Files with compression ratios below // (better than) or equal to this value will be compressed. // // BUG: at present, BigFileAdd compresses everything or nothing, and ignores this define // #define MINIMUM_COMPRESSION_RATIO 0.950f // // bigfile.c - bigfile support // // Copyright (C) 1998 Relic Entertainment Inc. // // Created May 1998 by Darren Stone. // See bigfile.h for version notes. // #ifdef BF_HOMEWORLD // on a file-by-file basis, use the most recent file // out of either the bigfile(s) or the filesystem // NOTE: This slows the initial (blackscreen) load of the // game a bit, and we could probably eliminate it for // the retail version. bool CompareBigfiles = TRUE; // allows you to force loading from regular filesystem, // even if files are available in a bigfile bool IgnoreBigfiles = FALSE; // allows you to create a text file of all data file loads // (useful for ordering or creating a bigfile) bool LogFileLoads = FALSE; // the main bigfile (others can be opened too, if desired), // but this is the one that persists through the game FILE *mainFP = NULL; bigTOC mainTOC; FILE *updateFP = NULL; // if a bigfile patch is present, this will be non-NULL bigTOC updateTOC; // array that tells us if there are newer files available // in the filesystem than in the bigfile(s) unsigned char *mainNewerAvailable = NULL; unsigned char *updateNewerAvailable = NULL; #endif // // 1. convert all slashes to backslashes // 2. reduce multiple consecutive slashes to single slashes // // (operate on the string in place) // static void filenameSlashMassage(char *filename) { udword i, j; udword len = strlen(filename); for (i = 0; i < len; ++i) if (filename[i] == '/') filename[i] = '\\'; i = 0; while (i < strlen(filename) - 1) { if (filename[i] == '\\' && filename[i+1] == '\\') { for (j = i; j < strlen(filename) - 1; ++j) filename[j] = filename[j+1]; filename[j] = 0; } ++i; } } /*----------------------------------------------------------------------------- Name : PrettyFilesize Description : Format a file size (in bytes) attractively) Inputs : bytes - file size Outputs : buf - an attractive numeric string, like "1,326,719" Return : buf ----------------------------------------------------------------------------*/ static char *PrettyFilesize(long bytes, char *buf) { char temp[32], tempRev[32], *p, *r; int count; sprintf(temp, "%0d", bytes); // copy backwards, by 3's into tempRev p = temp + strlen(temp) - 1; r = tempRev; count = 0; while (1) { *r = *p; ++count; if (p == temp) break; if (!(count % 3)) *(++r) = ','; --p; ++r; } *(r+1) = 0; // copy backwards into buf r = tempRev + strlen(tempRev) -1; p = buf; while (1) { *p = *r; if (r == tempRev) break; --r; ++p; } *(p+1) = 0; return buf; } /*----------------------------------------------------------------------------- Name : PrettyDateTime Description : Format a time stamp attractively Inputs : dt - time stamp Outputs : pretty - an attractive numeric string, like "1998/05/11 05:32:12" Return : pretty; ----------------------------------------------------------------------------*/ static char *PrettyDateTime(time_t dt, char *pretty) { struct tm *stamp; stamp = localtime(&dt); strftime(pretty, 31, "%Y/%m/%d %H:%M:%S", stamp); return pretty; } /*----------------------------------------------------------------------------- Name : bigFileExists Description : Determine if a file exists Inputs : filename - file you're looking for Outputs : nothing Return : 1 if the file exists, 0 otherwise ----------------------------------------------------------------------------*/ static int bigFileExists(char *filename) { struct _finddata_t findData; return (_findfirst(filename, &findData) != -1); } /*----------------------------------------------------------------------------- Name : bigFilenameRetrieve Description : Pull filename from file data portion of bigfile. This is a fairly slow operation because of the seeking and reading required -- use it only when necessary! Inputs : toc - table of contents of bigfile fileNum - ordinal location of file fp - filepointer into bigfile Outputs : filename is set filepointer in fp is restored to original position Return : 1 on success, 0 on failure ----------------------------------------------------------------------------*/ static int bigFilenameRetrieve(bigTOC *toc, int fileNum, FILE *fp, char *filename) { long fpos = ftell(fp); fseek(fp, (toc->fileEntries + fileNum)->offset, SEEK_SET); fread(filename, 1, (toc->fileEntries + fileNum)->nameLength+1, fp); fseek(fp, fpos, SEEK_SET); bigFilenameDecrypt(filename, (toc->fileEntries + fileNum)->nameLength); return 1; } /*----------------------------------------------------------------------------- Name : bigCopy File Description : Copy a file. Like windows CopyFile(). Inputs : existingFilename - for source file newFilename - for (new) dest file failIfExists - 1/0 Outputs : file will be copied Return : 1 on succes, 0 on failure ----------------------------------------------------------------------------*/ static int bigCopyFile(char *existingFilename, char *newFilename, int failIfExists) { FILE *oldfp, *newfp; char ch; oldfp = fopen(existingFilename, "rb"); if (!oldfp) return 0; if (failIfExists && bigFileExists(newFilename)) return 0; newfp = fopen(newFilename, "wb"); if (!newfp) return 0; while (1) { // horribly inefficient! change this! ch = (char)getc(oldfp); if (feof(oldfp)) break; putc(ch, newfp); } fclose(oldfp); fclose(newfp); return 1; } /*----------------------------------------------------------------------------- Name : bigFilenameExtract Description : Extract the name-only portion from a full pathname Inputs : filenameonly - where you want a new string fullpathname - full pathname to operate on Outputs : filenameonly will be filled in Return : nothing ----------------------------------------------------------------------------*/ static void bigFilenameExtract(char *filenameonly, char *fullpathname) { char *p = fullpathname + strlen(fullpathname) - 1; // work backwards through the fullpathname, until we get to the first // backslash or the start of the string while (p != fullpathname) { if (*p == '\\') { ++p; break; } --p; } strcpy(filenameonly, p); } /*----------------------------------------------------------------------------- Name : bigTOCWrite Description : Write table of contents & file entries to bigfile . File pointer must be positioned right after header Inputs : fp - file pointer of bigfile toc - table of contents struct Outputs : toc & file entries are written, leaving file pointer right after it Return : 1 on succes, 0 on failure ----------------------------------------------------------------------------*/ static int bigTOCWrite(FILE *fp, bigTOC *toc) { fwrite((void *)&(toc->numFiles), sizeof(toc->numFiles), 1, fp); fwrite((void *)&(toc->flags), sizeof(toc->flags), 1, fp); if (toc->numFiles) fwrite((void *)(toc->fileEntries), sizeof(bigTOCFileEntry), toc->numFiles, fp); return 1; } /*----------------------------------------------------------------------------- Name : bigTOCRead Description : Read table of contents & file entries from bigfile . File pointer must be positioned right after header Inputs : fp - file pointer of bigfile toc - table of contents struct Outputs : toc & file entries are read, leaving file pointer right after it *** memory is left allocated for toc->fileEntries -- remember to free this when you're done Return : 1 on succes, 0 on failure ----------------------------------------------------------------------------*/ static int bigTOCRead(FILE *fp, bigTOC *toc) { fread((void *)&(toc->numFiles), sizeof(toc->numFiles), 1, fp); fread((void *)&(toc->flags), sizeof(toc->flags), 1, fp); if (toc->numFiles) { toc->fileEntries = malloc(sizeof(bigTOCFileEntry) * toc->numFiles); fread((void *)(toc->fileEntries), sizeof(bigTOCFileEntry), toc->numFiles, fp); } return 1; } // // bigTOCCRC // // returns the 32-bit CRC for the TOC // crc32 bigTOCCRC(bigTOC *toc) { return crc32Compute((ubyte *)(toc->fileEntries), sizeof(bigTOCFileEntry)*(toc->numFiles)); } /*----------------------------------------------------------------------------- Name : bigHeaderWrite Description : Write header/version info to bigfile . File pointer must be positioned at start of file Inputs : fp - file pointer of bigfile Outputs : header/version info is written, leaving file pointer right after it Return : 1 on succes, 0 on failure ----------------------------------------------------------------------------*/ static int bigHeaderWrite(FILE *fp) { char header[16]; sprintf(header, "%s%s", BF_FILE_HEADER, BF_VERSION); fwrite((void *)header, strlen(header), 1, fp); return 1; } /*----------------------------------------------------------------------------- Name : bigHeaderVerify Description : Read header/version info from bigfile . File pointer must be positioned at start of file Inputs : fp - file pointer of bigfile Outputs : header/version info is read, leaving file pointer right after it Return : 1 if the type & version are OK, 0 on failure ----------------------------------------------------------------------------*/ static int bigHeaderVerify(FILE *fp) { char headerNeed[16], headerHave[16]; int headerLength; sprintf(headerNeed, "%s%s", BF_FILE_HEADER, BF_VERSION); headerLength = strlen(headerNeed); fgets(headerHave, headerLength+1, fp); headerHave[headerLength] = 0; return !strcmp(headerHave, headerNeed); } // // sorts given bigfile // returns 1 on success // returns 0 on failure // static int bigSort(char *bigfilename) { FILE *bigFP; bigTOC toc; bigFP = fopen(bigfilename, "r+b"); if (!bigFP) return 0; if (!bigHeaderVerify(bigFP)) // ensure correct file type & version { fclose(bigFP); return 0; } bigTOCRead(bigFP, &toc); bigTOCSort(&toc); fseek(bigFP, strlen(BF_FILE_HEADER BF_VERSION), SEEK_SET); bigTOCWrite(bigFP, &toc); fclose(bigFP); return 1; } /*----------------------------------------------------------------------------- Name : bigTOCFileExists Description : Determine if a file exists in a big file (from the CRC in the TOC) Inputs : toc - pointer to table of contents filename - what we're looking for Outputs : fileNum - 0...numFiles, if it's found Return : 1 if found, 0 otherwise ----------------------------------------------------------------------------*/ int bigTOCFileExists(bigTOC *toc, char *filename, int *fileNum) { crc32 nameCRC1, nameCRC2; char filenamei[BF_MAX_FILENAME_LENGTH+1]; bigTOCFileEntry target; int numFiles = toc->numFiles; if (!numFiles) return 0; if ((filename == NULL) || (filename[0] == 0)) { return 0; } // pretend the filename is in lowercase strcpy(filenamei, filename); _strlwr(filenamei); // convert all slashes to backslashes and whittle multiple slashes down to a single one filenameSlashMassage(filenamei); target.nameLength = strlen(filenamei); target.nameCRC1 = (nameCRC1 = crc32Compute((ubyte *)filenamei, target.nameLength/2)); target.nameCRC2 = (nameCRC2 = crc32Compute((ubyte *)filenamei + target.nameLength/2, target.nameLength/2)); return bigTOCFileExistsByCRC(toc, &target, fileNum); } // // This is similar to bigTOCFileExists, but it assumes you have a fileEntry // (only the CRC of it is used) instead of a filename. // int bigTOCFileExistsByCRC(bigTOC *toc, bigTOCFileEntry *target, int *fileNum) { static int FileNum = 0; int startFileNum; int numFiles = toc->numFiles; int low, high; // for binary search if (toc->flags & BF_FLAG_TOC_SORTED) { // sorted toc -- binary search low = 0; high = numFiles-1; while (low <= high) { *fileNum = (low+high)/2; // middle if (bigCRC64EQ(target, toc->fileEntries + *fileNum)) return 1; else if (bigCRC64GT(target, toc->fileEntries + *fileNum)) low = *fileNum +1; else // if (b < a[*fileNum]) high = *fileNum -1; } return 0; } else { // unsorted toc -- linear search, but optimized to // start searching from wherever we left off last time // to potentially find sequentially ordered files faster startFileNum = FileNum; do { if (toc->fileEntries[FileNum].nameLength == target->nameLength && toc->fileEntries[FileNum].nameCRC1 == target->nameCRC1 && toc->fileEntries[FileNum].nameCRC2 == target->nameCRC2) { *fileNum = FileNum; return 1; } ++FileNum; if (FileNum >= numFiles) FileNum = 0; } while (FileNum != startFileNum); } return 0; } #if 0 // 64-bit CRC comparison // (pass pointers to bigTOCFileEntries) // return 1 if a's CRC is greater than b's int bigCRC64GT(bigTOCFileEntry *a, bigTOCFileEntry *b) { // this is a good candidate for a macro return ((a->nameCRC1 > b->nameCRC2) || (a->nameCRC1 == b->nameCRC2 && a->nameCRC2 > b->nameCRC2)); } #endif // // check status of bigfile (for example, before opening or after an error) // // returns 1 if all okay // returns 0 if it doesn't exist // returns -1 if it's incompatible (it's there but it's the wrong version or not a bigfile) // int bigCheck(char *bigFilename) { FILE *mainFP; #ifdef BF_HOMEWORLD if (IgnoreBigfiles) return 1; #endif // exists? if (!bigFileExists(bigFilename)) return 0; // can open it? mainFP = fopen(bigFilename, "rb"); if (!mainFP) return 0; if (!bigHeaderVerify(mainFP)) return -1; fclose(mainFP); return 1; } void bigFilenameEncrypt(char *filename) { int maskch = 213; int nextmask; while (*filename) { nextmask = *filename; *(filename++) ^= maskch; maskch = nextmask; } } // // since the encrypted string could contain 0's, we need to pass the length // (not including the terminating 0, which is not encrypted) // void bigFilenameDecrypt(char *filename, int len) { int maskch = 213; while (len--) { *filename ^= maskch; maskch = *(filename++); } } /*----------------------------------------------------------------------------- Name : bigTOCSort Description : Sort the TOC in ascending CRC order. Inputs : toc - pointer to table of contents Outputs : toc - newly sorted Return : Note : sorts in place (there are a few types of sorts implemented here...) ----------------------------------------------------------------------------*/ void bigTOCSort(bigTOC *toc) { // a very ignorant bubble sort! #if 0 int noExchanges, first, pass; int numFiles = toc->numFiles; bigTOCFileEntry saveEntry; if (!numFiles || (toc->flags & BF_FLAG_TOC_SORTED)) return; // nothing to do if (numFiles < 2) { toc->flags |= BF_FLAG_TOC_SORTED; // toc is already "sorted" return; } pass = 1; do { noExchanges = 1; for (first = 0; first < numFiles - pass; ++first) { if (bigCRC64GT(&toc->fileEntries[first], &toc->fileEntries[first+1])) { saveEntry = toc->fileEntries[first]; toc->fileEntries[first] = toc->fileEntries[first+1]; toc->fileEntries[first+1] = saveEntry; //memcpy(&saveEntry, &toc->fileEntries[first], sizeof(bigTOCFileEntry)); //memcpy(&toc->fileEntries[first], &toc->fileEntries[first+1], sizeof(bigTOCFileEntry)); //memcpy(&toc->fileEntries[first+1], &saveEntry, sizeof(bigTOCFileEntry)); noExchanges = 0; } } ++pass; } while (!noExchanges); toc->flags |= BF_FLAG_TOC_SORTED; // toc is now sorted #endif // // shell sort #if 1 int i, j, n, h; int numFiles = toc->numFiles; int ub, lb; bigTOCFileEntry saveEntry; if (!numFiles || (toc->flags & BF_FLAG_TOC_SORTED)) return; // nothing to do if (numFiles < 2) { toc->flags |= BF_FLAG_TOC_SORTED; // toc is already "sorted" return; } // shell sort ub = numFiles-1; lb = 0; /* compute largest increment */ n = ub - lb + 1; h = 1; if (n < 14) h = 1; //else if (sizeof(tblIndex) == 2 && n > 29524) i think this is unnecessary // h = 3280; else { while (h < n) h = 3*h + 1; h /= 3; h /= 3; } while (h > 0) { /* sort-by-insertion in increments of h */ for (i = lb + h; i <= ub; i++) { memcpy(&saveEntry, &toc->fileEntries[i], sizeof(bigTOCFileEntry)); for (j = i-h; j >= lb && bigCRC64GT(&toc->fileEntries[j], &saveEntry); j -= h) memcpy(&toc->fileEntries[j+h], &toc->fileEntries[j], sizeof(bigTOCFileEntry)); memcpy(&toc->fileEntries[j+h], &saveEntry, sizeof(bigTOCFileEntry)); } /* compute next increment */ h /= 3; } toc->flags |= BF_FLAG_TOC_SORTED; // toc is now sorted #endif // insertion sort #if 0 int numFiles = toc->numFiles; int i, j; bigTOCFileEntry saveEntry; if (!numFiles || (toc->flags & BF_FLAG_TOC_SORTED)) return; // nothing to do for (i = 1; i < numFiles; ++i) { memcpy(&saveEntry, &toc->fileEntries[i], sizeof(bigTOCFileEntry)); for (j = i-1; j >= 0 && bigCRC64GT(&toc->fileEntries[j], &saveEntry); --j) memcpy(&toc->fileEntries[j+1], &toc->fileEntries[j], sizeof(bigTOCFileEntry)); memcpy(&toc->fileEntries[j+1], &saveEntry, sizeof(bigTOCFileEntry)); } toc->flags |= BF_FLAG_TOC_SORTED; // toc is now sorted #endif } /*----------------------------------------------------------------------------- Name : bigCreateNew Description : Create a new (empty) bigfile Inputs : filename - bigfile to create Outputs : new file is created, with nothing in it except headers, version info, etc. Return : 1 on succes, 0 on failure ----------------------------------------------------------------------------*/ static int bigCreateNew(char *filename) { FILE *fp; bigTOC toc; fp = fopen(filename, "wb"); if (!fp) return 0; bigHeaderWrite(fp); toc.numFiles = 0; toc.flags = 0; // unsorted TOC by default bigTOCWrite(fp, &toc); fclose(fp); return 1; } // // Create a "patch" bigfile from all the files in the newbigfile that have // have been changed or added since the oldbigfile. A byte-for-byte comparison // is done for each file. Date/time-stamps are ignored. // int bigPatch(char *oldfilename, char *newfilename, char *patchfilename, int consoleOutput) { FILE *oldFP, *newFP, *patchFP; bigTOC oldTOC, newTOC, patchTOC; bigTOCFileEntry *newEntry, *oldEntry, *patchEntry; int fileNum; int filesNewer = 0, filesChanged = 0; unsigned long curPatchOffset; int i, j; int toBePatched; unsigned long bytesCompared; // open old (read) if (bigFileExists(oldfilename)) { oldFP = fopen(oldfilename, "rb"); if (!oldFP) { if (consoleOutput) printf("ERROR: Can't open %s\n", oldfilename); return 0; } // ensure correct file type & version if (!bigHeaderVerify(oldFP)) { if (consoleOutput) printf("ERROR: Incompatible file %s\n", oldfilename); fclose(oldFP); return 0; } } else { if (consoleOutput) printf("ERROR: Can't find %s\n", oldfilename); return 0; } bigTOCRead(oldFP, &oldTOC); // open new (read) if (bigFileExists(oldfilename)) { newFP = fopen(newfilename, "rb"); if (!newFP) { if (consoleOutput) printf("ERROR: Can't open %s\n", newfilename); return 0; } // ensure correct file type & version if (!bigHeaderVerify(newFP)) { if (consoleOutput) printf("ERROR: Incompatible file %s\n", newfilename); fclose(newFP); return 0; } } else { if (consoleOutput) printf("ERROR: Can't find %s\n", newfilename); return 0; } bigTOCRead(newFP, &newTOC); // open patch (write) if (bigFileExists(patchfilename)) { unlink(patchfilename); if (bigFileExists(patchfilename)) { if (consoleOutput) printf("ERROR: Can't overwrite %s\n", patchfilename); return 0; } if (consoleOutput) printf("Overwriting"); } else if (consoleOutput) printf("Creating"); if (consoleOutput) printf(" %s (update from %s to %s)\n", patchfilename, oldfilename, newfilename); patchFP = fopen(patchfilename, "wb"); if (!patchFP) { if (consoleOutput) printf("ERROR: Can't create %s\n", patchfilename); return 0; } bigHeaderWrite(patchFP); patchTOC.numFiles = 0; patchTOC.flags = 0; patchTOC.fileEntries = malloc(sizeof(bigTOCFileEntry) * newTOC.numFiles); curPatchOffset = strlen(BF_FILE_HEADER BF_VERSION) + sizeof(patchTOC.numFiles) + sizeof(patchTOC.flags); if (consoleOutput) printf("Comparing files\n"); // compare each file in the new bigfile for (i = 0; i < newTOC.numFiles; ++i) { toBePatched = 0; newEntry = &newTOC.fileEntries[i]; if (bigTOCFileExistsByCRC(&oldTOC, newEntry, &fileNum)) { // add to patch if it is different oldEntry = &oldTOC.fileEntries[fileNum]; // quick size compare if (newEntry->storedLength != oldEntry->storedLength || newEntry->realLength != oldEntry->realLength || newEntry->compressionType != oldEntry->compressionType || newEntry->nameLength != oldEntry->nameLength) { toBePatched = 1; ++filesChanged; } else { // byte-by-byte compare fseek(oldFP, oldEntry->offset + oldEntry->nameLength+1, SEEK_SET); fseek(newFP, newEntry->offset + newEntry->nameLength+1, SEEK_SET); bytesCompared = 0; while (bytesCompared < newEntry->storedLength) { if (fgetc(oldFP) != fgetc(newFP)) { toBePatched = 1; ++filesChanged; break; } ++bytesCompared; } } } else { // it's new, so add it toBePatched = 1; ++filesNewer; } if (toBePatched) { patchEntry = &patchTOC.fileEntries[patchTOC.numFiles]; patchEntry->nameCRC1 = newEntry->nameCRC1; patchEntry->nameCRC2 = newEntry->nameCRC2; patchEntry->nameLength = newEntry->nameLength; patchEntry->storedLength = newEntry->storedLength; patchEntry->realLength = newEntry->realLength; patchEntry->timeStamp = newEntry->timeStamp; patchEntry->compressionType = newEntry->compressionType; patchEntry->offset = curPatchOffset; curPatchOffset += patchEntry->storedLength + patchEntry->nameLength+1; ++patchTOC.numFiles; } } // update offsets, now that we know the size of the TOC for (i = 0; i < patchTOC.numFiles; ++i) patchTOC.fileEntries[i].offset += sizeof(bigTOCFileEntry) * patchTOC.numFiles; if (consoleOutput) { printf("%d new file%s\n", filesNewer, filesNewer == 1 ? "" : "s"); printf("%d updated file%s\n", filesChanged, filesChanged == 1 ? "" : "s"); } if (consoleOutput) printf("Writing data\n"); fseek(patchFP, strlen(BF_FILE_HEADER BF_VERSION), SEEK_SET); // TOC bigTOCWrite(patchFP, &patchTOC); // file data for (i = 0; i < patchTOC.numFiles; ++i) { bigTOCFileExistsByCRC(&newTOC, &patchTOC.fileEntries[i], &fileNum); fseek(newFP, newTOC.fileEntries[fileNum].offset, SEEK_SET); j = patchTOC.fileEntries[i].storedLength + patchTOC.fileEntries[i].nameLength + 1; while (j--) fputc(fgetc(newFP), patchFP); } fclose(oldFP); fclose(newFP); fclose(patchFP); if (consoleOutput) printf("Sorting table of contents\n"); if (!bigSort(patchfilename)) { if (consoleOutput) printf("WARNING: Can't sort bigfile %s\n", patchfilename); return 0; } return 1; } /*----------------------------------------------------------------------------- Name : bigAdd Description : Add files to a bigfile Inputs : bigfilename - filename of bigfile (to add or append to) numFiles - # of filenames in following array filenames - names of files to add optCompression - 1|0 compress optNewer - 1|0 add files only if newer optMove - 1|0 move (not just copy) files optPathnames - 1|0 store full pathnames consoleOutput - 1|0 = output to stdout (yes/no) Outputs : New (or updated) bigfile. Possibly deleted (moved) files. Output to stdout (if enabled). Return : 1 on success, 0 on failure ----------------------------------------------------------------------------*/ int bigAdd(char *bigfilename, int numFiles, char *filenames[], int optCompression, int optNewer, int optMove, int optPathnames, int consoleOutput) { char tempfilename[L_tmpnam+1]; char tempshortfilename[BF_MAX_FILENAME_LENGTH+1]; int filesAdded = 0, filesUpdated = 0, filesUnchanged = 0; int f; int res; int *moveFiles; FILE *fp, *filelistFP; int filelist; char *filelistName; char filelistLine[BF_MAX_FILENAME_LENGTH+1]; char filename[BF_MAX_FILENAME_LENGTH+1]; // temp file in case we need to abort if (!tmpnam(tempfilename)) { if (consoleOutput) printf("ERROR: Can't create temporary filename\n"); return 0; } // either update or create if (bigFileExists(bigfilename)) { fp = fopen(bigfilename, "rb"); if (!fp) { if (consoleOutput) printf("ERROR: Can't open %s\n", bigfilename); return 0; } // ensure correct file type & version if (!bigHeaderVerify(fp)) { if (consoleOutput) printf("ERROR: Incompatible file %s\n", bigfilename); fclose(fp); return 0; } fclose(fp); if (consoleOutput) printf("Updating %s\n", bigfilename); } else { if (consoleOutput) printf("Creating %s\n", bigfilename); bigCreateNew(bigfilename); } // move original file to safe, temporary place if (!bigCopyFile(bigfilename, tempfilename, 1)) { if (consoleOutput) printf("ERROR: Can't create %s\n", tempfilename); return 0; } // keep track of files that need to be moved/deleted moveFiles = (int *)malloc(sizeof(int) * numFiles); // add each file for (f = 0; f < numFiles; ++f) { filelist = 0; if (filenames[f][0] == '@') { filelistName = filenames[f] + 1; // clean up messy slashes in filenames filenameSlashMassage(filelistName); filelist = 1; filelistFP = fopen(filelistName, "r"); if (!filelistFP) { if (consoleOutput) printf("ERROR: Can't open filelist: %s\n", filelistName); unlink(tempfilename); free(moveFiles); return 0; } if (optMove && consoleOutput) printf("WARNING: Can't (re)move files from filelist: %s\n", filelistName); } do // loop in case we have a filelist to process for this one "filename" { if (filelist) { if (feof(filelistFP)) break; // get next filename fgets(filelistLine, BF_MAX_FILENAME_LENGTH, filelistFP); filelistLine[BF_MAX_FILENAME_LENGTH] = 0; if (sscanf(filelistLine, "%s", filename) != 1 || !strlen(filename)) continue; } else strcpy(filename, filenames[f]); // clean up messy slashes for the TOC filenameSlashMassage(filename); // use simple filename or full path if (!optPathnames) bigFilenameExtract(tempshortfilename, filename); else strcpy(tempshortfilename, filename); if (consoleOutput) printf(" %s ", filename); res = bigAddFile(tempfilename, filename, tempshortfilename, optCompression, optNewer, consoleOutput); if (!res) { //if (consoleOutput) // printf("\nERROR: Can't add %s\n", filename); _unlink(tempfilename); free(moveFiles); return 0; } else { if (res & BF_ADD_RES_ADDED) { ++filesAdded; if (consoleOutput) printf("added"); moveFiles[f] = optMove && !filelist; } else if (res & BF_ADD_RES_UPDATED) { ++filesUpdated; if (consoleOutput) printf("updated"); moveFiles[f] = optMove && !filelist; } else if (res & BF_ADD_RES_OLD) { ++filesUnchanged; if (consoleOutput) printf("unchanged"); moveFiles[f] = 0; } else moveFiles[f] = 0; if (strcmpi(tempshortfilename, filename) && consoleOutput) { printf(" (%s)", tempshortfilename); } if (consoleOutput) printf("\n"); } } while (filelist && !feof(filelistFP)); if (filelist) fclose(filelistFP); } // "move" (delete) files if necessary for (f = 0; f < numFiles; ++f) if (moveFiles[f]) { if (_unlink(filenames[f]) == -1) if (consoleOutput) printf("ERROR: Can't (re)move %s\n", filenames[f]); } // sort if (filesAdded) { if (consoleOutput) printf("Sorting table of contents\n"); if (!bigSort(tempfilename)) { if (consoleOutput) printf("WARNING: Can't sort bigfile %s\n", tempfilename); return 0; } } // summary if (consoleOutput) { if (!(filesAdded + filesUpdated)) printf("Nothing to do.\n"); else { if (filesAdded) printf("%d file%s added%s\n", filesAdded, ((filesAdded == 1) ? "" : "s"), optMove ? " (moved)":""); if (filesUpdated) printf("%d file%s updated%s\n", filesUpdated, ((filesUpdated == 1) ? "" : "s"), optMove ? " (moved)":""); } } // replace original file if (!bigCopyFile(tempfilename, bigfilename, 0)) { if (consoleOutput) printf("ERROR: Can't overwrite %s with new file (%s)\n", bigfilename, tempfilename); free(moveFiles); return 0; } if(_unlink(tempfilename) == -1) { if (consoleOutput) printf("ERROR: Can't remove temporary file %s\n", tempfilename); free(moveFiles); return 0; } free(moveFiles); return 1; } /*----------------------------------------------------------------------------- Name : bigFastCreate Description : Create a new bigfile quickly Inputs : bigfilename - filename of bigfile to create numFiles - # of filenames in following array filenames - names of files to add optCompression - 1|0 compress optNewer - 1|0 add files only if newer optMove - 1|0 move (not just copy) files optPathnames - 1|0 store full pathnames consoleOutput - 1|0 = output to stdout (yes/no) Outputs : New bigfile. Possibly deleted (moved) files. Output to stdout (if enabled). Return : 1 on success, 0 on failure ----------------------------------------------------------------------------*/ int bigFastCreate(char *bigfilename, int numFiles, char *filenames[], int optCompression, int optNewer, int optMove, int optPathnames, int consoleOutput) { char tempshortfilename[BF_MAX_FILENAME_LENGTH+1]; char tempshortfilenamei[BF_MAX_FILENAME_LENGTH+1]; char ch; int filesAdded = 0, dupesSkipped = 0; int pass; int f, i, index; int *moveFiles; FILE *bigFP, *filelistFP, *dataFP; int filelist; char *filelistName; char filelistLine[BF_MAX_FILENAME_LENGTH+1]; char filename[BF_MAX_FILENAME_LENGTH+1]; bigTOC toc; struct _finddata_t findData; bigTOCFileEntry fileEntry; unsigned long curOffset; int compressedSize; crc32 *crcAdded1, *crcAdded2; int dupe; // either update or create if (bigFileExists(bigfilename)) { unlink(bigfilename); if (bigFileExists(bigfilename)) { if (consoleOutput) printf("ERROR: Can't overwrite %s\n", bigfilename); return 0; } if (consoleOutput) printf("Fast-creating %s (overwriting old file)\n", bigfilename); } else if (consoleOutput) printf("Fast-creating %s\n", bigfilename); bigFP = fopen(bigfilename, "wb"); if (!bigFP) { if (consoleOutput) printf("ERROR: Can't create %s\n", bigfilename); return 0; } bigHeaderWrite(bigFP); toc.numFiles = 0; toc.flags = 0; // keep track of files that need to be moved/deleted moveFiles = (int *)malloc(sizeof(int) * numFiles); // do 4 passes : count files, write filecount, TOC headers, data for (pass = -1; pass < 3; ++pass) { // add each file for (f = 0; f < numFiles; ++f) { filelist = 0; if (filenames[f][0] == '@') { filelistName = filenames[f] + 1; filelist = 1; filelistFP = fopen(filelistName, "r"); if (!filelistFP) { if (consoleOutput) printf("ERROR: Can't open filelist: %s\n", filelistName); free(moveFiles); return 0; } if (pass == -1 && optMove && consoleOutput) printf("WARNING: Can't (re)move files from filelist: %s\n", filelistName); } do // loop in case we have a filelist to process for this one "filename" { if (filelist) { if (feof(filelistFP)) break; // get next filename fgets(filelistLine, BF_MAX_FILENAME_LENGTH, filelistFP); filelistLine[BF_MAX_FILENAME_LENGTH] = 0; if (sscanf(filelistLine, "%s", filename) != 1 || !strlen(filename)) continue; } else strcpy(filename, filenames[f]); // clean up messy slashes for the TOC filenameSlashMassage(filename); // use simple filename or full path if (!optPathnames) bigFilenameExtract(tempshortfilename, filename); else strcpy(tempshortfilename, filename); // pretend name is lowercase for consistent CRCs strcpy(tempshortfilenamei, tempshortfilename); _strlwr(tempshortfilenamei); // convert all slashes to backslashes and whittle multiple slashes down to a single one filenameSlashMassage(tempshortfilenamei); if (pass > 1 && consoleOutput) printf(" %s ", filename); // watch for dupes fileEntry.nameLength = (unsigned short)strlen(tempshortfilenamei); fileEntry.nameCRC1 = crc32Compute((ubyte *)tempshortfilenamei, fileEntry.nameLength/2); fileEntry.nameCRC2 = crc32Compute((ubyte *)tempshortfilenamei + fileEntry.nameLength/2, fileEntry.nameLength/2); fileEntry.compressionType = optCompression; if (pass != -1) { dupe = 0; for (i = 0; i < filesAdded; ++i) if (fileEntry.nameCRC1 == crcAdded1[i] && fileEntry.nameCRC2 == crcAdded2[i]) { dupe = 1; break; } } switch (pass) { case -1: // count everything, even dupes break; case 0: if (dupe) continue; break; case 1: // used to write TOC header entry here, but because // of compression possibility (ie, unknown stored size), // we do it in stage 2 now if (dupe) continue; break; case 2: // TOC header entry & data if (dupe) { ++dupesSkipped; if (consoleOutput) printf("skipped (duplicate)\n"); continue; } if (_findfirst(filename, &findData) == -1) { if (consoleOutput) printf("\nERROR: Can't find %s\n", filename); fclose(bigFP); unlink(bigfilename); free(moveFiles); return 0; } fileEntry.realLength = findData.size; fileEntry.timeStamp = findData.time_write; // data fseek(bigFP, curOffset, SEEK_SET); bigFilenameEncrypt(tempshortfilename); fwrite((void *)(tempshortfilename), 1, fileEntry.nameLength+1, bigFP); bigFilenameDecrypt(tempshortfilename, fileEntry.nameLength); dataFP = fopen(filename, "rb"); if (!dataFP) { if (consoleOutput) printf("ERROR: Can't read %s\n", filename); fclose(bigFP); unlink(bigfilename); free(moveFiles); return 0; } if (fileEntry.compressionType) { // compress and append BIT_FILE *bitFile; bitFile = bitioFileAppendStart(bigFP); lzssCompressFile(dataFP, bitFile); compressedSize = bitioFileAppendStop(bitFile); // revert to uncompressed if the file won't compress if ((float)compressedSize/(float)fileEntry.realLength > MINIMUM_COMPRESSION_RATIO) { fileEntry.compressionType = 0; // theoretically, this could leave some harmless data at the end of a bigfile fseek(bigFP, curOffset + strlen(tempshortfilename)+1, SEEK_SET); fseek(dataFP, 0, SEEK_SET); } } if (!fileEntry.compressionType) { // raw, uncompressed copy while (1) { // inefficient! ch = (char)getc(dataFP); if (feof(dataFP)) break; putc(ch, bigFP); } } fclose(dataFP); // TOC header entry fseek(bigFP, // overwrite placeholder from previous step strlen(BF_FILE_HEADER BF_VERSION) + sizeof(toc.numFiles) + sizeof(toc.flags) + (filesAdded * sizeof(bigTOCFileEntry)), SEEK_SET); fileEntry.storedLength = fileEntry.compressionType ? compressedSize : findData.size; fileEntry.offset = curOffset; fwrite((void *)&(fileEntry), sizeof(fileEntry), 1, bigFP); curOffset += fileEntry.nameLength + 1 + fileEntry.storedLength; moveFiles[f] = optMove && !filelist; break; } if (pass != -1) { // keep track to prevent dupes crcAdded1[filesAdded] = fileEntry.nameCRC1; crcAdded2[filesAdded] = fileEntry.nameCRC2; } ++filesAdded; if (pass == 2 && consoleOutput) { //printf("[pass %d of 2]", pass); // don't mention the first passes if (strcmpi(tempshortfilename, filename)) printf("(%s) ", tempshortfilename); printf("added\n"); } } while (filelist && !feof(filelistFP)); if (filelist) fclose(filelistFP); } switch (pass) { case -1: crcAdded1 = (crc32 *)malloc(sizeof(crc32) * filesAdded); crcAdded2 = (crc32 *)malloc(sizeof(crc32) * filesAdded); filesAdded = 0; break; case 0: fwrite((void *)&(filesAdded), sizeof(filesAdded), 1, bigFP); filesAdded = 0; fwrite((void *)&(toc.flags), sizeof(toc.flags), 1, bigFP); break; case 1: // placeholders for real TOC header entries (to be updated later) for (index = 0; index < filesAdded; index++) { fwrite((void *)&(fileEntry), sizeof(fileEntry), 1, bigFP); } curOffset = strlen(BF_FILE_HEADER BF_VERSION) + sizeof(toc.numFiles) + sizeof(toc.flags) + (filesAdded * sizeof(bigTOCFileEntry)); filesAdded = 0; // reset for final pass, to keep track of dupes break; } } fclose(bigFP); if (consoleOutput) printf("Sorting table of contents"); if (!bigSort(bigfilename)) { printf("\nERROR: Can't sort bigfile %s\n", bigfilename); unlink(bigfilename); free(moveFiles); return 0; } if (consoleOutput) printf("\n"); // "move" (delete) files if necessary for (f = 0; f < numFiles; ++f) if (moveFiles[f]) { if (_unlink(filenames[f]) == -1) if (consoleOutput) printf("ERROR: Can't (re)move %s\n", filenames[f]); } // summary if (consoleOutput) { if (!filesAdded && !dupesSkipped) printf("Nothing to do\n"); else { if (filesAdded) printf("%d file%s added to new bigfile\n", filesAdded, ((filesAdded == 1) ? "" : "s")); if (dupesSkipped) printf("%d duplicate%s skipped\n", dupesSkipped, ((dupesSkipped == 1) ? "" : "s")); } } free(crcAdded1); free(crcAdded2); free(moveFiles); return 1; } /*----------------------------------------------------------------------------- Name : bigAddFile Description : Add a file to a bigfile Inputs : bigFilename - filename of bigfile (to add or update to) filename - file to add (or update) storedFilename - name to store (could be shortr, with no path) (this will have its slashes massaged in place -- possibly resulting in a shorter name) optCompression - 1|0 compress optNewer - 1|0 add file only if newer consoleOutput - 1|0 = output to stdout (yes/no) Outputs : Updated bigfile. Return : 0 on failure, or... BF_ADD_RES_ADDED BF_ADD_RES_UPDATED BF_ADD_RES_OLD Note : If 0 is returned, this routine must display an error message to the console. ----------------------------------------------------------------------------*/ int bigAddFile(char *bigFilename, char *filename, char *storedFilename, int optCompression, int optNewer, int consoleOutput) { char tempshortfilenamei[BF_MAX_FILENAME_LENGTH+1]; FILE *oldfp, *newfp, *datafp; bigTOC oldtoc, newtoc; bigTOCFileEntry fileEntry; char tempFilename[L_tmpnam], compressedTempFilename[L_tmpnam]; int fileNum; int res = 0; signed long i; signed long offsetDelta; unsigned long oldStoredLength; struct _finddata_t findData, findDataCompressed; newtoc.fileEntries = (bigTOCFileEntry *)0; // temp file in case we need to abort if (!tmpnam(tempFilename) || (optCompression && !tmpnam(compressedTempFilename))) { if (consoleOutput) printf("\nERROR: Can't create temporary filename\n"); return 0; } // open current bigfile oldfp = fopen(bigFilename, "rb"); if (!oldfp) { if (consoleOutput) printf("\nERROR: Can't open bigfile %s\n", bigFilename); return 0; } // ensure correct file type & version if (!bigHeaderVerify(oldfp)) { if (consoleOutput) printf("\nERROR: Incompatible file %s\n", bigFilename); fclose(oldfp); return 0; } // start new bigfile newfp = fopen(tempFilename, "wb"); if (!newfp || !bigHeaderWrite(newfp)) { if (consoleOutput) printf("\nERROR: Can't create temporary file %s\n", tempFilename); goto abort; } // read current toc bigTOCRead(oldfp, &oldtoc); // clean up messy slashes for the TOC filenameSlashMassage(filename); filenameSlashMassage(storedFilename); // fill in fileentry if (_findfirst(filename, &findData) == -1) { if (consoleOutput) printf("\nERROR: Can't find %s\n", filename); goto abort; } // compress file to temp file if required if (optCompression) { BIT_FILE *bf = bitioFileOpenOutput(compressedTempFilename); if (!bf) { if (consoleOutput) printf("\nERROR: Can't create temporary file %s\n", compressedTempFilename); goto abort; } datafp = fopen(filename, "rb"); if (!datafp) { if (consoleOutput) printf("\nERROR: Can't open %s\n", filename); goto abort; } lzssCompressFile(datafp, bf); fclose(datafp); bitioFileCloseOutput(bf); if (_findfirst(compressedTempFilename, &findDataCompressed) == -1) { if (consoleOutput) printf("\nERROR: Can't find %s\n", filename); goto abort; } } // pretend name is lowercase for consistent CRCs strcpy(tempshortfilenamei, storedFilename); _strlwr(tempshortfilenamei); // convert all slashes to backslashes and whittle multiple slashes down to a single one filenameSlashMassage(tempshortfilenamei); //strcpy(fileEntry.name, storedFilename); fileEntry.nameLength = (unsigned short)strlen(tempshortfilenamei); fileEntry.nameCRC1 = crc32Compute((ubyte *)tempshortfilenamei, fileEntry.nameLength/2); fileEntry.nameCRC2 = crc32Compute((ubyte *)tempshortfilenamei + fileEntry.nameLength/2, fileEntry.nameLength/2); fileEntry.storedLength = optCompression ? findDataCompressed.size : findData.size; fileEntry.realLength = findData.size; fileEntry.timeStamp = findData.time_write; fileEntry.compressionType = optCompression; // no compression for now // fileEntry.offset will be calculated later // update existing file if (bigTOCFileExists(&oldtoc, storedFilename, &fileNum)) { char compareFilename[BF_MAX_FILENAME_LENGTH]; // important: check right now if we happen to have two filenames // with the same CRCs if (!bigFilenameRetrieve(&oldtoc, fileNum, oldfp, compareFilename)) goto abort; if (strcmpi(storedFilename, compareFilename)) { if (consoleOutput) printf("\nERROR: Can't add %s (same CRC as %s)\n", storedFilename, compareFilename); goto abort; } // if required, ensure file is newer if (optNewer && fileEntry.timeStamp <= (oldtoc.fileEntries + fileNum)->timeStamp) { fclose (newfp); fclose (oldfp); _unlink(tempFilename); return BF_ADD_RES_OLD; } // update fileEntry oldStoredLength = (oldtoc.fileEntries + fileNum)->storedLength; offsetDelta = fileEntry.storedLength - oldStoredLength; fileEntry.offset = (oldtoc.fileEntries + fileNum)->offset; memcpy(oldtoc.fileEntries + fileNum, &fileEntry, sizeof(bigTOCFileEntry)); // all files past the updated one are offset for (i = fileNum + 1; i < oldtoc.numFiles; ++i) (oldtoc.fileEntries + i)->offset += offsetDelta; // write updated TOC (unsorted, still) bigTOCWrite(newfp, &oldtoc); // copy first fileNum-1 file data i = (oldtoc.fileEntries)->offset; while ((unsigned long)i < fileEntry.offset) { fputc(fgetc(oldfp), newfp); ++i; } // write new file data datafp = fopen(optCompression ? compressedTempFilename : filename, "rb"); if (!datafp) { if (consoleOutput) printf("\nERROR: Can't open %s\n", optCompression ? compressedTempFilename : filename); goto abort; } bigFilenameEncrypt(storedFilename); fwrite(storedFilename, 1, fileEntry.nameLength+1, newfp); // filename bigFilenameDecrypt(storedFilename, fileEntry.nameLength); i = fileEntry.storedLength; while (i--) fputc(fgetc(datafp), newfp); fclose(datafp); // skip old data fseek(oldfp, oldStoredLength + fileEntry.nameLength + 1, SEEK_CUR); // copy all subsequent file data i = fileEntry.offset + fileEntry.storedLength + fileEntry.nameLength + 1; while ((unsigned long)i < (oldtoc.fileEntries + oldtoc.numFiles - 1)->offset + (oldtoc.fileEntries + oldtoc.numFiles - 1)->storedLength + (oldtoc.fileEntries + oldtoc.numFiles - 1)->nameLength +1) { fputc(fgetc(oldfp), newfp); ++i; } // already sorted // write updated TOC //bigTOCWrite(newfp, &oldtoc); res = BF_ADD_RES_UPDATED; } // add new file else { // add new toc entry at end and write the toc newtoc.numFiles = oldtoc.numFiles+1; newtoc.flags = oldtoc.flags & (~BF_FLAG_TOC_SORTED); // mark TOC as unsorted now newtoc.fileEntries = malloc(sizeof(bigTOCFileEntry)*(newtoc.numFiles)); memcpy(newtoc.fileEntries, oldtoc.fileEntries, sizeof(bigTOCFileEntry)*oldtoc.numFiles); if (oldtoc.numFiles) { // offset each file data offset to make room for the TOCFileEntry for (i = 0; i < newtoc.numFiles-1; ++i) (newtoc.fileEntries + i)->offset += sizeof(bigTOCFileEntry); fileEntry.offset = (newtoc.fileEntries + oldtoc.numFiles - 1)->offset + (newtoc.fileEntries + oldtoc.numFiles - 1)->storedLength + (newtoc.fileEntries + oldtoc.numFiles - 1)->nameLength + 1; } else // first entry fileEntry.offset = strlen(BF_FILE_HEADER BF_VERSION) + sizeof(oldtoc.numFiles) + sizeof(oldtoc.flags) + sizeof(bigTOCFileEntry); memcpy(newtoc.fileEntries + oldtoc.numFiles, &fileEntry, sizeof(bigTOCFileEntry)); bigTOCWrite(newfp, &newtoc); // (unsorted) // copy all the old file data if (oldtoc.numFiles) { i = (newtoc.fileEntries)->offset; while ((unsigned long)i < fileEntry.offset) { fputc(fgetc(oldfp), newfp); ++i; } } // write the new file data datafp = fopen(optCompression ? compressedTempFilename : filename, "rb"); if (!datafp) { if (consoleOutput) printf("\nERROR: Can't open %s\n", optCompression ? compressedTempFilename : filename); goto abort; } bigFilenameEncrypt(storedFilename); fwrite(storedFilename, 1, fileEntry.nameLength+1, newfp); // filename bigFilenameDecrypt(storedFilename, fileEntry.nameLength); i = fileEntry.storedLength; while (i--) fputc(fgetc(datafp), newfp); fclose(datafp); res = BF_ADD_RES_ADDED; } fclose (newfp); fclose (oldfp); #if 0 // sort if (!bigSort(tempFilename)) { if (consoleOutput) printf("\nWARNING: Can't sort bigfile %s\n", tempFilename); return 0; } #endif // copy new file to old file if (!bigCopyFile(tempFilename, bigFilename, 0)) { if (consoleOutput) printf("\nERROR: Can't overwrite %s with new file (%s)\n", bigFilename, tempFilename); return 0; } // remove tempfile if(_unlink(tempFilename) == -1) { if (consoleOutput) printf("\nERROR: Can't remove temporary file %s\n", tempFilename); return 0; } if (optCompression) { if(_unlink(compressedTempFilename) == -1) { if (consoleOutput) printf("\nERROR: Can't remove temporary file %s\n", compressedTempFilename); return 0; } } return res; abort: if (newtoc.fileEntries) free(newtoc.fileEntries); free(oldtoc.fileEntries); fclose (newfp); fclose (oldfp); return 0; } /*----------------------------------------------------------------------------- Name : bigDelete Description : Delete files from a bigfile Inputs : bigfilename - filename of bigfile (to delete from) numFiles - # of filenames in following array filenames - names of files to delete consoleOutput - 1/0 = output to stdout (yes/no) Outputs : Updated bigfile. Output to stdout (if enabled). Return : 1 on success, 0 on failure ----------------------------------------------------------------------------*/ int bigDelete(char *bigfilename, int numFiles, char *filenames[], int consoleOutput) { if (consoleOutput) printf("ERROR: Delete operation not supported yet.\n"); return 1; } /*----------------------------------------------------------------------------- Name : bigView Description : View contents of a bigfile Inputs : bigfilename - filename of bigfile to view consoleOutput - 1/0 = output to stdout (yes/no) Outputs : Output to stdout (if enabled). Return : 1 on success, 0 on failure ----------------------------------------------------------------------------*/ int bigView(char *bigFilename, int consoleOutput) { FILE *fp; bigTOC toc; int f; unsigned long totalCompressed = 0, totalExpanded = 0; int displayCompressed = 0; char pRealSize[32], pStoredSize[32], pTimestamp[32], pRatio[16]; struct _finddata_t findData; char filename[BF_MAX_FILENAME_LENGTH+1]; // open bigfile fp = fopen(bigFilename, "rb"); if (!fp) { if (consoleOutput) printf("ERROR: Can't open %s\n", bigFilename); return 0; } // ensure correct file type & version if (!bigHeaderVerify(fp)) { if (consoleOutput) printf("ERROR: Incompatible file %s\n", bigFilename); goto abort; } // read current toc bigTOCRead(fp, &toc); if (!toc.numFiles) printf("No files in %s\n", bigFilename); else if (consoleOutput) { _findfirst(bigFilename, &findData); printf("\nContents of %s ", bigFilename); printf( "(%s bytes, ", PrettyFilesize(findData.size, pStoredSize)); printf( "%s):\n", (toc.flags & BF_FLAG_TOC_SORTED)?"CRC-sorted TOC":"unsorted TOC"); printf("\n CRC Stored Size Real Size Ratio Date Time Filename\n"); printf("---------------- ------------ ------------ ----- ---------- -------- ------------\n"); for (f = 0; f < toc.numFiles; ++f) { bigFilenameRetrieve(&toc, f, fp, filename); if ((toc.fileEntries + f)->compressionType) sprintf(pRatio, "%05.3f", ((toc.fileEntries + f)->realLength ? (float)(toc.fileEntries + f)->storedLength/(float)(toc.fileEntries + f)->realLength : 0.0)); else strcpy(pRatio, " na "); printf("%08x%08x %13s %13s %s %s %s\n", (toc.fileEntries + f)->nameCRC1, (toc.fileEntries + f)->nameCRC2, PrettyFilesize((toc.fileEntries + f)->storedLength, pStoredSize), PrettyFilesize((toc.fileEntries + f)->realLength, pRealSize), pRatio, PrettyDateTime((toc.fileEntries + f)->timeStamp, pTimestamp), filename); totalCompressed += (toc.fileEntries + f)->storedLength; totalExpanded += (toc.fileEntries + f)->realLength; if ((toc.fileEntries + f)->storedLength != (toc.fileEntries + f)->realLength) displayCompressed = 1; } // totals if (toc.numFiles) { printf(" ------------ ------------ -----\n"); if (totalCompressed == totalExpanded) { PrettyFilesize(totalExpanded, pRealSize), strcpy(pStoredSize, ""); strcpy(pRatio, " na "); } else { PrettyFilesize(totalCompressed, pStoredSize), PrettyFilesize(totalExpanded, pRealSize), sprintf(pRatio, "%05.3f", (totalExpanded ? (float)totalCompressed/(float)totalExpanded : 0.0)); } printf(" %13s %13s %s\n", pStoredSize, pRealSize, pRatio); } } fclose(fp); return 1; abort: fclose(fp); return 0; } /*----------------------------------------------------------------------------- Name : bigExtract Description : Extract files from a bigfile Inputs : bigfilename - filename of bigfile to extract from numFiles - # of filenames in following array filenames - names of files to extract optNewer - 1|0 extract files only if newer optMove - 1|0 move (not just copy) files out of bigfile optPathnames - 1|0 extract to subdir pathnames stored optOverwrite - 1|0 overwrite existing files consoleOutput - 1/0 = output to stdout (yes/no) Outputs : Possibly updated bigfile. New files. Output to stdout (if enabled). Return : 1 on success, 0 on failure ----------------------------------------------------------------------------*/ int bigExtract(char *bigfilename, int numFiles, char *filenames[], int optFreshen, int optMove, int optPathnames, int optOverwrite, int consoleOutput) { if (consoleOutput) printf("ERROR: Extract operation not supported yet.\n"); return 1; } // // the following routines are for ingame compilation only -- not for the command-line tool // #ifdef BF_HOMEWORLD // // Open the main bigfile. // Returns 1 on success, 0 on failure. // sdword bigOpen(char *_bigFilename, char *_updateFilename) { char bigFilename[256]; char updateFilename[256]; strcpy(bigFilename, filePathPrepend(_bigFilename, 0)); strcpy(updateFilename, filePathPrepend(_updateFilename, 0)); if (IgnoreBigfiles) return 1; // open main bigfile (mandatory) mainFP = fopen(bigFilename, "rb"); if (!mainFP) return 0; updateFP = fopen(updateFilename, "rb"); if (!updateFP) updateFP = NULL; // ensure correct file type & version if (!bigHeaderVerify(mainFP) || (updateFP && !bigHeaderVerify(updateFP))) return 0; // read toc bigTOCRead(mainFP, &mainTOC); if (updateFP) bigTOCRead(updateFP, &updateTOC); return 1; } // // Allocate memory and load a file into it. // If there's any other problem, return -1. Otherwise, return // the number of bytes allocated and loaded. // sdword bigFileLoadAlloc(bigTOC *toc, FILE *bigFP, char *filename, sdword fileNum, void **address) { sdword length; bigTOCFileEntry *entry; char *memoryName; int expandedSize, storedSize; BIT_FILE *bitFile; if (IgnoreBigfiles) return -1; // go there entry = toc->fileEntries + fileNum; fseek(bigFP, entry->offset + entry->nameLength+1, SEEK_SET); // allocate length = entry->realLength; dbgAssert(length > 0); if (entry->nameLength > MEM_NameLength) memoryName = &filename[entry->nameLength - MEM_NameLength]; else memoryName = filename; *address = memAllocAttempt(length, memoryName, NonVolatile | MBF_String); if (*address == NULL) dbgFatalf(DBG_Loc, "bigFileLoadAlloc: couldn't allocate %d bytes for %s", length, filename); if (entry->compressionType) { // expand compressed file data directly into memory bitFile = bitioFileInputStart(bigFP); expandedSize = lzssExpandFileToBuffer(bitFile, *address, length); storedSize = bitioFileInputStop(bitFile); dbgAssert(expandedSize == length); dbgAssert(storedSize == entry->storedLength); } else { // load uncompressed file data fread(*address, 1, length, bigFP); } return length; } // // Load a file into memory. // If there's any problem, return -1. Otherwise, return // the number of bytes loaded. // sdword bigFileLoad(bigTOC *toc, FILE *bigFP, sdword fileNum, void *address) { sdword length; bigTOCFileEntry *entry; int expandedSize, storedSize; BIT_FILE *bitFile; if (IgnoreBigfiles) return -1; // go there entry = toc->fileEntries + fileNum; fseek(bigFP, entry->offset + entry->nameLength+1, SEEK_SET); length = entry->realLength; dbgAssert(length > 0); if (entry->compressionType) { // expand compressed file data directly into memory bitFile = bitioFileInputStart(bigFP); expandedSize = lzssExpandFileToBuffer(bitFile, address, length); storedSize = bitioFileInputStop(bitFile); dbgAssert(expandedSize == length); dbgAssert(storedSize == entry->storedLength); } else { // load uncompressed file data fread(address, 1, length, bigFP); } return length; } // // baseDirectory // directory to recursively scan for newer files than what's in the bigfile // "C:\homeworld\data", for example // directory // subdir to scan // "", or "Missions\SinglePlayer", for example // mainTOC // table of contents for the bigfile (for reading only) // updateTOC // table of contents for the update bigfile (for reading only) (or NULL) // mainNewerAvailable // array to be set (same number of entries as toc) // 2 = newer file exists in filesystem // 1 = older file exists in filesystem // 0 = file doesn't exist in filesystem // updateNewerAvailable (or NULL) // array to be set (same number of entries as toc) // 2 = newer file exists in filesystem // 1 = older file exists in filesystem // 0 = file doesn't exist in filesystem // // NOTE: Assumes the CRC64 name check is sufficient for uniqueness. // NOTE: Start with newerAvailable array(s) set to all 0's. // NOTE: It is assumed that the stored filenames in the bigfile will // not have the baseDirectory but will have the subdirectories // on their names. // void bigFilesystemCompare(char *baseDirectory, char *directory, bigTOC *mainTOC, bigTOC *updateTOC, unsigned char *mainNewerAvailable, unsigned char *updateNewerAvailable) { struct _finddata_t findData; char filespec[256]; char subpath[256]; long hFile; int fileNum; static long compared = 0; static long newerMain = 0, newerUpdate; if (IgnoreBigfiles || !CompareBigfiles) return; if (!directory[0]) dbgMessagef("\nScanning for newer files in %s", baseDirectory); sprintf(filespec, "%s\\%s%s*.*", baseDirectory, directory, directory[0] ? "\\" : ""); hFile = _findfirst(filespec, &findData); if (hFile == -1) return; do { if (!strcmp(findData.name, ".") || !strcmp(findData.name, "..")) continue; sprintf(subpath, "%s%s%s", directory, directory[0] ? "\\" : "", findData.name); if (findData.attrib & _A_SUBDIR) { // recurse subdirectories bigFilesystemCompare(baseDirectory, subpath, mainTOC, updateTOC, mainNewerAvailable, updateNewerAvailable); --compared; } else { if (updateFP && bigTOCFileExists(updateTOC, subpath, &fileNum)) { if (updateTOC->fileEntries[fileNum].timeStamp < findData.time_write) { // newer one available in filesystem updateNewerAvailable[fileNum] = 2; ++newerUpdate; } else // older one available in filesystem updateNewerAvailable[fileNum] = 1; } if (bigTOCFileExists(mainTOC, subpath, &fileNum)) { if (mainTOC->fileEntries[fileNum].timeStamp < findData.time_write) { // newer one available in filesystem mainNewerAvailable[fileNum] = 2; // this can result in a poopload of messages... //dbgMessagef("\n %s", subpath); ++newerMain; } else // older one available in filesystem mainNewerAvailable[fileNum] = 1; } } ++compared; } while (_findnext(hFile, &findData) == 0); _findclose(hFile); if (!directory[0]) { if (updateFP) { dbgMessagef("\nCompared %d filesystem files to main & update bigfiles.", compared); dbgMessagef("\n%d files found newer than main bigfile in filesystem.", newerMain); dbgMessagef("\n%d files found newer than update bigfile in filesystem.", newerUpdate); } else { dbgMessagef("\nCompared %d filesystem files to bigfile.", compared); dbgMessagef("\n%d files found newer in filesystem.", newerMain); } } } // // Close the main bigfile (and update bigfile, if present). // void bigClose(void) { if (!IgnoreBigfiles) { if (mainFP) fclose(mainFP); if (updateFP) fclose(updateFP); } } // // bigCRC // // Returns the 64-bit CRC of all open bigfile(s). // Upper 32 bits are for the update bigfile (or 0 if not in use). // Lower 32 bits are for the main bigfile. // // NOTE: call AFTER bigOpen // void bigCRC(udword *bigCrc1,udword *bigCrc2) { *bigCrc1 = bigTOCCRC(&mainTOC); *bigCrc2 = updateFP ? bigTOCCRC(&updateTOC) : 0; } #endif // BF_HOMEWORLD