#include #include // S_IRUSR #include #include #include #include #include "pack.h" #include "md5.h" // == Store data in a funky format. ======================= // Format: // -- Index (in 512 byte chunks). // -- Magic number (16 bytes). // -- Number of files (uint32_t). // -- Files in format name/location. // -- File name (128 bytes max, ending in \0). // -- File location (uint32_t). // -- File data in format Size/Data. // -- File size (uint32_t). // -- File data (char*). // -- File MD5 (16 byte char*). // -- EOF // // -- Write magic number and number of files. // -- Write the index. // -- Pack the files. // ======================================================== // The read/write block size. #define BLOCKSIZE 128*1024 // Max filename length. #define MAX_FILENAME 100 #define LOG(str, args...)(fprintf(stdout, str"\n", ## args)) #define WARN(str, args...)(fprintf(stderr, "[%d] "str"\n", SDL_GetTicks(), ## args)) #define ERR(str, args...) (fprintf(stderr, "%s:%d: "str"\n", __FILE__, __LINE__, ## args)) #ifdef DEBUG # undef DEBUG # define DEBUG(str, args...) LOG(str, ## args) # define DEBUGGING #else # define DEBUG(str, args...) do {;} while(0) #endif #define PERMS S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH const uint64_t magic = 0x25524573; // sER% // Grab the filesize. static off_t getfilesize(const char* filename) { struct stat file; if(!stat(filename, &file)) return file.st_size; ERR("Unable to get filesize of %s", filename); return 0; } // Return true if filename is a Packfile. int pack_check(char* filename) { int fd = open(filename, O_RDONLY); if(fd == -1) { ERR("Error opening %s: %s", filename, strerror(errno)); return -1; } char* buf = malloc(sizeof(magic)); if(read(fd, buf, sizeof(magic)) != sizeof(magic)) { ERR("Error reading magic number: %s", strerror(errno)); free(buf); return -1; } int ret = (memcmp(buf, &magic, sizeof(magic))==0) ? 0 : 1; free(buf); return ret; } // Pack nfiles, infiles into outfile. #define WRITE(f,b,n) if(write(f,b,n)==-1) { \ ERR("Error writing to file: %s", strerror(errno)); \ free(buf); return -1; } int pack_files(char* outfile, char** infiles, uint32_t nfiles) { void* buf; struct stat file; int i, namesize; int outfd, infd; uint32_t indexsize, pointer; int bytes; const uint8_t b = '\0'; for(namesize = 0, i = 0; i < nfiles; i++) { // Make sure file exists before writing. if(stat(infiles[i], &file)) { ERR("File %s does not exist", infiles[i]); return -1; } if(strlen(infiles[i]) < MAX_FILENAME) { ERR("filename '%s' is too long, should be only %d characters", infiles[i], MAX_FILENAME); return -1; } namesize += strlen(infiles[i]); } indexsize = (sizeof(magic) + 4 + // Magic number and number of files. namesize + // Total length of file names. (1+4)*nfiles); // File size and extra end of string char '\0'. DEBUG("Index size is %d", indexsize); // Create the output file. outfd = creat(outfile, PERMS); if(outfd == -1) { ERR("Unable to open %s for writing", outfile); return -1; } // Index! buf = malloc(BLOCKSIZE); // Magic number. WRITE(outfd, &magic, sizeof(magic)); DEBUG("Wrote magic number"); // Number of files. WRITE(outfd, &nfiles, sizeof(nfiles)); DEBUG("Wrote number of files: %d", nfiles); // Create file dependent index part. pointer = indexsize; for(i = 0; i < nfiles; i++) { WRITE(outfd, infiles[i], strlen(infiles[i])); DEBUG("File: '%s' at %d", infiles[i], pointer); WRITE(outfd, &b, 1); WRITE(outfd, &pointer, 4); pointer += getfilesize(infiles[i]) + 8; // Set pointer to be next file pos. } // Data! md5_state_t md5; md5_byte_t* md5val = malloc(16); for(i = 0; i < nfiles; i++) { bytes = (uint32_t)getfilesize(infiles[i]); WRITE(outfd, &bytes, 4); // filesize. DEBUG("About to write '%s' of %d bytes", infiles[i], bytes); infd = open(infiles[i], O_RDONLY); md5_init(&md5); while((bytes = read(infd, buf, BLOCKSIZE))) { WRITE(outfd, buf, bytes); // Data. md5_append(&md5, buf, bytes); } md5_finish(&md5, md5val); WRITE(outfd, md5val, sizeof(md5val)); close(infd); DEBUG("Wrote file '%s'", infiles[i]); } free(md5val); close(outfd); free(buf); DEBUG("Packfile success\n\t%d files\n\t%d bytes", nfiles, (int)getfilesize(outfile)); return 0; } #undef WRITE // Opens the filename in packfile for reading. #define READ(f,b,n) if(read(f,b,n) != n) { \ ERR("Too few bytes read. Expected more."); \ free(buf); return -1; } int pack_open(Packfile* file, char* packfile, char* filename) { int i, j; uint32_t nfiles; char* buf = (char*)malloc(MAX_FILENAME); file = (Packfile*)malloc(sizeof(Packfile)); file->start = file->end = 0; file->fd = open(packfile, O_RDONLY); if(file->fd == -1) { ERR("Error opening %s: %s", filename, strerror(errno)); return -1; } READ(file->fd, buf, sizeof(magic)); // Make sure it's a packfile. if(memcmp(buf, &magic, sizeof(magic))) { ERR("File %s is not a valid packfile", filename); return -1; } READ(file->fd, &nfiles, 4); for(i = 0; i < nfiles; i++) { // Start to search files. j = 0; READ(file->fd, &buf[j], 1); // Get the name. while(buf[j++] != '\0') READ(file->fd, &buf[j], 1); if(strcmp(filename, buf) == 0) { // We found the file. READ(file->fd, &file->start, 4); DEBUG("'%s' found at %d", filename, file->start); break; } lseek(file->fd, 4, SEEK_CUR); // Ignore the file location. } free(buf); if(file->start) { // Go to the beginning of the file. if(lseek(file->fd, file->start, SEEK_SET) != file->start) { ERR("Failure to seek to file start: %s", strerror(errno)); return -1; } if(read(file->fd, &file->end, 4) != 4) { ERR("Too few bytes read. Expected more."); return -1; } DEBUG("\t%d bytes", file->end); file->pos = file->start; file->end += file->start; } return 0; } #undef READ // Read count bytes from file and put them into buf. ssize_t pack_read(Packfile* file, void* buf, size_t count) { if(file->pos + count > file->end) count = file->end - file->pos; // Can't go past the end! if(count == 0) return 0; int bytes; bytes = read(file->fd, buf, count); file->pos += bytes; return bytes; } // Close the packfile. int pack_close(Packfile* file) { int i = close(file->fd); free(file); return (i) ? -1 : 0; }