Mercurial > hg > dmlib
view tools/packed.c @ 994:e8de4fbc03b6
Clean up packed a bit.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 28 Feb 2015 15:59:39 +0200 |
parents | 985225a93aeb |
children | acda8cfc9119 |
line wrap: on
line source
/* * PACKed - PACKfile EDitor * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2011 Tecnic Software productions (TNSP) */ #include "dmlib.h" #include "dmargs.h" #include "dmpack.h" #include "dmfile.h" #include "dmres.h" #include "dmmutex.h" #include <zlib.h> #define SET_MAX_FILES (4096) #define SET_DEFAULT_PACK "data.pak" enum { CMD_NONE = 0, CMD_CREATE, CMD_ADD, CMD_LIST, CMD_EXTRACT } DCOMMAND; enum { PACK_EXTRACTED = 0x0001, }; int nsrcFilenames = 0, nexcFilenames = 0; char * srcFilenames[SET_MAX_FILES]; char * excFilenames[SET_MAX_FILES]; char * optPackFilename = NULL; BOOL optCompress = TRUE; int optCommand = CMD_NONE; int optDefResFlags = 0; static const DMOptArg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 1, 'p', "pack", "Set pack filename (default: " SET_DEFAULT_PACK ")", OPT_ARGREQ }, { 2, 'c', "create", "Create and add files to PACK", OPT_NONE }, { 3, 'a', "add", "Add files to PACK", OPT_NONE }, { 4, 'l', "list", "List files in PACK", OPT_NONE }, { 5, 'e', "extract", "Extract files from PACK", OPT_NONE }, { 6, 'n', "nocompress", "No compression", OPT_NONE }, { 7, 'v', "verbose", "Increase verbosity", OPT_NONE }, { 8, 'f', "resflags", "Set default resource flags (-f 0xff)", OPT_ARGREQ }, { 9, 'x', "exclude", "Exclude name/glob pattern from add", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp() { dmPrintBanner(stdout, dmProgName, "[options] [-p <packfilename>] [filename[s]]"); dmArgsPrintHelp(stdout, optList, optListN, 0); fprintf(stdout, "\n" "Examples:\n" "$ %s -p test.pak -l -- list files in test.pak\n" "$ %s -a foobar.jpg -- add foobar.jpg in " SET_DEFAULT_PACK "\n" "$ %s -e foobar.jpg -- extract foobar.jpg from " SET_DEFAULT_PACK "\n", dmProgName, dmProgName, dmProgName); } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { (void) optArg; switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: optPackFilename = optArg; break; case 2: optCommand = CMD_CREATE; break; case 3: optCommand = CMD_ADD; break; case 4: optCommand = CMD_LIST; break; case 5: optCommand = CMD_EXTRACT; break; case 6: optCompress = FALSE; break; case 7: dmVerbosity++; break; case 8: { unsigned int i; if (!dmGetIntVal(optArg, &i)) { dmErrorMsg("Invalid flags value '%s'.\n", optArg); return FALSE; } optDefResFlags = i; } break; case 9: if (nexcFilenames < SET_MAX_FILES) { excFilenames[nsrcFilenames] = currArg; nexcFilenames++; } else { dmErrorMsg("Maximum number of exclusion patterns (%d) exceeded!\n", SET_MAX_FILES); return FALSE; } break; default: dmErrorMsg("Unknown argument '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (nsrcFilenames < SET_MAX_FILES) { srcFilenames[nsrcFilenames] = currArg; nsrcFilenames++; } else { dmErrorMsg("Maximum number of input files (%d) exceeded!\n", SET_MAX_FILES); return FALSE; } return TRUE; } DMPackEntry *dmPackEntryCopy(const DMPackEntry *src) { DMPackEntry *node = dmPackEntryNew(); if (node == NULL) return NULL; strncpy(node->filename, src->filename, sizeof(node->filename)); node->filename[sizeof(node->filename) - 1] = 0; node->size = src->size; node->offset = src->offset; node->length = src->length; node->flags = src->flags; return node; } int dmPackWrite(DMPackFile * pack) { DMPackEntry *node; DMPackFileHeader hdr; if (pack == NULL) return DMERR_OK; if (pack->file == NULL) return DMERR_FOPEN; // Compute directory offset and number of entries memcpy(&hdr.ident, DPACK_IDENT, sizeof(hdr.ident)); hdr.version = DPACK_VERSION; hdr.dirEntries = 0; hdr.dirOffset = sizeof(hdr.ident) + sizeof(hdr.version) + sizeof(hdr.dirEntries) + sizeof(hdr.dirOffset); node = pack->entries; while (node != NULL) { hdr.dirEntries++; hdr.dirOffset += node->length; node = node->next; } // Write PACK header if (fseek(pack->file, 0L, SEEK_SET) != 0) return DMERR_FSEEK; if (!dm_fwrite_str(pack->file, (Uint8 *) & hdr.ident, sizeof(hdr.ident)) || !dm_fwrite_le16(pack->file, hdr.version) || !dm_fwrite_le32(pack->file, hdr.dirEntries) || !dm_fwrite_le32(pack->file, hdr.dirOffset)) return DMERR_FWRITE; // Write the directory if (fseek(pack->file, hdr.dirOffset, SEEK_SET) != 0) return DMERR_FSEEK; node = pack->entries; while (node != NULL) { // Write one entry if (!dm_fwrite_str(pack->file, node->filename, sizeof(node->filename)) || !dm_fwrite_le32(pack->file, node->size) || !dm_fwrite_le32(pack->file, node->offset) || !dm_fwrite_le32(pack->file, node->length) || !dm_fwrite_le32(pack->file, node->flags)) return DMERR_FWRITE; node = node->next; } return DMERR_OK; } int dmPackCreate(const char *filename, DMPackFile ** pack) { // Allocate packfile-structure *pack = (DMPackFile *) dmCalloc(1, sizeof(DMPackFile)); if (*pack == NULL) return DMERR_MALLOC; // Open the file (*pack)->file = fopen(filename, "wb"); if ((*pack)->file == NULL) { dmFree(*pack); return DMERR_FOPEN; } (*pack)->filename = dm_strdup(filename); // Set the result return DMERR_OK; } int dmPackAddFile(DMPackFile * pack, const char *filename, BOOL doCompress, const Uint32 flags, DMPackEntry ** ppEntry) { z_stream zstr; off_t startOffs; unsigned int zstrSize; FILE *inFile = NULL; Uint8 *inBuffer = NULL, *outBuffer = NULL; DMPackEntry entry, *node; int zres, ret = DMERR_OK; BOOL zinit = FALSE; *ppEntry = NULL; if (pack == NULL) return DMERR_NULLPTR; if (pack->file == NULL) return DMERR_FOPEN; // Compute starting offset startOffs = sizeof(DMPackFileHeader); node = pack->entries; while (node != NULL) { startOffs += node->length; node = node->next; } // Seek to the position if (fseek(pack->file, startOffs, SEEK_SET) != 0) return DMERR_INVALID; // Read file data if ((inFile = fopen(filename, "rb")) == NULL) return DMERR_FOPEN; // Allocate temporary buffer if ((inBuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE)) == NULL || (outBuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE)) == NULL) { ret = DMERR_MALLOC; goto out; } // Read (and possibly compress) the data zstrSize = 0; zstr.zalloc = (alloc_func) Z_NULL; zstr.zfree = (free_func) Z_NULL; zstr.opaque = (voidpf) Z_NULL; zres = deflateInit(&zstr, doCompress ? Z_DEFAULT_COMPRESSION : 0); if (zres != Z_OK) { ret = DMERR_COMPRESSION; goto out; } zinit = TRUE; // Initialize compression streams zres = Z_OK; while (!feof(inFile) && zres == Z_OK) { zstr.avail_in = fread(inBuffer, sizeof(Uint8), DPACK_TMPSIZE, inFile); zstr.next_in = inBuffer; zstr.next_out = outBuffer; zstr.avail_out = DPACK_TMPSIZE; zstr.total_out = 0; zres = deflate(&zstr, Z_FULL_FLUSH); if (zres == Z_OK && zstr.total_out > 0) { zstrSize += zstr.total_out; if (fwrite(outBuffer, sizeof(Uint8), zstr.total_out, pack->file) != zstr.total_out) { ret = DMERR_FWRITE; goto out; } } } // Create directory entry strncpy(entry.filename, filename, sizeof(entry.filename)); entry.filename[sizeof(entry.filename) - 1] = 0; entry.size = zstr.total_in; entry.offset = startOffs; entry.length = zstrSize; entry.flags = flags; // Add directory entry if ((*ppEntry = dmPackEntryCopy(&entry)) == NULL) { ret = DMERR_MALLOC; goto out; } dmPackEntryInsert(&pack->entries, *ppEntry); out: // Cleanup if (zinit) deflateEnd(&zstr); dmFree(inBuffer); dmFree(outBuffer); fclose(inFile); if (inFile != NULL) fclose(inFile); return ret; } /* * EXTRACT a file from the PACK */ int dmPackExtractFile(DMPackFile *pack, DMPackEntry * entry) { z_stream zstr; FILE *outFile; Uint8 *inBuffer, *outBuffer; size_t inDataLeft; int ret; if (pack == NULL) return DMERR_OK; if (pack->file == NULL) return DMERR_FOPEN; // Seek to the position if (fseek(pack->file, entry->offset, SEEK_SET) != 0) return DMERR_INVALID; // Open destination file if ((outFile = fopen(entry->filename, "wb")) == NULL) return -1; // Allocate temporary buffer if ((inBuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE)) == NULL) { fclose(outFile); return DMERR_MALLOC; } if ((outBuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE)) == NULL) { dmFree(inBuffer); fclose(outFile); return DMERR_MALLOC; } // Read and uncompress the data zstr.zalloc = (alloc_func) Z_NULL; zstr.zfree = (free_func) Z_NULL; zstr.opaque = (voidpf) Z_NULL; ret = inflateInit(&zstr); if (ret != Z_OK) { dmFree(inBuffer); dmFree(outBuffer); fclose(outFile); return DMERR_COMPRESSION; } // Initialize compression streams inDataLeft = entry->length; ret = Z_OK; while (inDataLeft > 0 && ret == Z_OK) { if (inDataLeft >= DPACK_TMPSIZE) zstr.avail_in = fread(inBuffer, sizeof(Uint8), DPACK_TMPSIZE, pack->file); else zstr.avail_in = fread(inBuffer, sizeof(Uint8), inDataLeft, pack->file); inDataLeft -= zstr.avail_in; zstr.next_in = inBuffer; while (zstr.avail_in > 0 && ret == Z_OK) { zstr.next_out = outBuffer; zstr.avail_out = DPACK_TMPSIZE; zstr.total_out = 0; ret = inflate(&zstr, Z_FULL_FLUSH); if (zstr.total_out > 0) { fwrite(outBuffer, sizeof(Uint8), zstr.total_out, outFile); } } } // Cleanup inflateEnd(&zstr); dmFree(inBuffer); dmFree(outBuffer); fclose(outFile); return DMERR_OK; } /* Compare a string to a pattern. Case-SENSITIVE version. * The matching pattern can consist of any normal characters plus * wildcards ? and *. "?" matches any character and "*" matches * any number of characters. */ BOOL dm_strmatch(const char *str, const char *pattern) { BOOL didMatch = TRUE, isAnyMode = FALSE, isEnd = FALSE; const char *tmpPattern = NULL; // Check given pattern and string if (str == NULL || pattern == NULL) return FALSE; // Start comparision do { didMatch = FALSE; switch (*pattern) { case '?': // Any single character matches if (*str) { didMatch = TRUE; pattern++; str++; } break; case '*': didMatch = TRUE; pattern++; if (!*pattern) isEnd = TRUE; isAnyMode = TRUE; tmpPattern = pattern; break; case 0: if (isAnyMode) { if (*str) str++; else isEnd = TRUE; } else { if (*str) { if (tmpPattern) { isAnyMode = TRUE; pattern = tmpPattern; } else didMatch = FALSE; } else isEnd = TRUE; } break; default: if (isAnyMode) { if (*pattern == *str) { isAnyMode = FALSE; didMatch = TRUE; } else { if (*str) { didMatch = TRUE; str++; } } } else { if (*pattern == *str) { didMatch = TRUE; if (*pattern) pattern++; if (*str) str++; } else { if (tmpPattern) { didMatch = TRUE; isAnyMode = TRUE; pattern = tmpPattern; } } } if (!*str && !*pattern) isEnd = TRUE; break; } // switch } while (didMatch && !isEnd); return didMatch; } BOOL dmCheckExcluded(const char *filename) { int i; for (i = 0; i < nexcFilenames; i++) { if (dm_strmatch(filename, excFilenames[i])) return TRUE; } return FALSE; } int main(int argc, char *argv[]) { int i, res = 0; DMPackFile *pack = NULL; #ifndef __WIN32 stderr = stdout; #endif // Parse arguments dmInitProg("packed", "Pack File Editor", "0.5", NULL, NULL); dmVerbosity = 1; if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) exit(1); // Check PACK filename if (optPackFilename == NULL) optPackFilename = SET_DEFAULT_PACK; if (optCommand == CMD_NONE) { argShowHelp(); dmErrorMsg("Nothing to do.\n"); exit(0); return 0; } dmMsg(1, "Processing %s ...\n", optPackFilename); // Execute command switch (optCommand) { case CMD_CREATE: case CMD_ADD: switch (optCommand) { case CMD_CREATE: dmMsg(1, "Creating new PACK\n"); res = dmPackCreate(optPackFilename, &pack); break; case CMD_ADD: dmMsg(1, "Opening existing PACK\n"); res = dmPackOpen(optPackFilename, &pack, FALSE); break; } // Add files into PACK if (res == DMERR_OK) { dmMsg(1, "Adding files...\n"); for (i = 0; i < nsrcFilenames; i++) if (!dmCheckExcluded(srcFilenames[i])) { DMPackEntry *node = NULL; int res = dmPackAddFile(pack, srcFilenames[i], optCompress, optDefResFlags, &node); if (res != DMERR_OK) { dmPrint(1, "%-32s [ERROR:%d]\n", srcFilenames[i], res); } else { dmPrint(1, "%-32s ['%s', s=%d, c=%d, o=%ld, f=0x%04x]\n", srcFilenames[i], node->filename, node->size, node->length, node->offset, node->flags); } } dmMsg(1, "w=%d\n", dmPackWrite(pack)); dmMsg(1, "c=%d\n", dmPackClose(pack)); } else { dmErrorMsg("Could not open packfile, error #%d: %s\n", res, dmErrorStr(res)); } break; case CMD_LIST: // List files in PACK res = dmPackOpen(optPackFilename, &pack, TRUE); if (res == DMERR_OK) { DMPackEntry *node; for (i = 0, node = pack->entries; node; i++) node = node->next; dmMsg(1, "%d files total\n", i); dmPrint(0, "%-32s | %8s | %8s | %8s | %s\n", "Name", "Size", "CSize", "Offset", "ResFlags"); for (node = pack->entries; node != NULL; node = node->next) { BOOL match; // Check for matches if (nsrcFilenames > 0) { match = FALSE; for (i = 0; i < nsrcFilenames && !match; i++) { match = dm_strmatch(node->filename, srcFilenames[i]); } } else match = TRUE; if (match) { dmPrint(0, "%-32s | %8d | %8d | %08x | %04x\n", node->filename, node->size, node->length, node->offset, node->flags); } } dmMsg(1, "c=%d\n", dmPackClose(pack)); } else dmErrorMsg("Could not open packfile, error #%d: %s\n", res, dmErrorStr(res)); break; case CMD_EXTRACT: // Extract files from PACK res = dmPackOpen(optPackFilename, &pack, TRUE); if (res == DMERR_OK) { DMPackEntry *node; FILE *resFile = fopen(DMRES_RES_FILE, "w"); if (resFile == NULL) { int err = dmGetErrno(); dmErrorMsg("Could not create resource output file '%s' #%d: %s\n", DMRES_RES_FILE, err, dmErrorStr(err)); } for (node = pack->entries; node != NULL; node = node->next) { BOOL match; // Check for matches if (nsrcFilenames > 0) { match = FALSE; for (i = 0; (i < nsrcFilenames) && !match; i++) { match = dm_strmatch(node->filename, srcFilenames[i]); } } else match = TRUE; if (match && (node->privFlags & PACK_EXTRACTED) == 0) { // Mark as done node->privFlags |= PACK_EXTRACTED; // Print one entry dmPrint(0, "Extracting: %-32s [siz=%d, cmp=%d, offs=0x%08x, flags=0x%04x]\n", node->filename, node->size, node->length, node->offset, node->flags); dmPackExtractFile(pack, node); } } dmMsg(1, "c=%d\n", dmPackClose(pack)); if (resFile != NULL) fclose(resFile); } else dmErrorMsg("Could not open packfile, error #%d: %s\n", res, dmErrorStr(res)); break; } return 0; }