v4k-git-backup/engine/split/3rd_archive.h

1625 lines
55 KiB
C
Raw Normal View History

// archive.c pak/zip/tar/dir archivers
// - rlyeh, public domain
#ifndef ARCHIVE_H
#define ARCHIVE_H
#define ARCHIVE_VERSION "v1.0.1"
#endif // ARCHIVE_H
#ifdef ARCHIVE_C
//#pragma once
#define PAK_C
#define ZIP_C
#define TAR_C
#define DIR_C
#endif // ARCHIVE_C
//#line 1 "src/zip.c"
// zip un/packer. based on JUnzip library by Joonas Pihlajamaa (UNLICENSE)
// - rlyeh, public domain.
//
// notes about compression_level:
// - plain integers use DEFLATE. Levels are [0(store)..6(default)..9(max)]
// - compress.c compression flags are also supported. Just use LZMA|5, ULZ|9, LZ4X|3, etc.
// - see zip_put.c for more info.
#ifndef ZIP_H
#define ZIP_H
#include <stdio.h>
#include <stdbool.h>
2023-11-15 19:14:14 +00:00
#include <time.h>
typedef struct zip zip;
zip* zip_open(const char *file, const char *mode /*r,w,a*/);
2023-10-13 10:59:44 +00:00
zip* zip_open_handle(FILE*fp, const char *mode /*r,w,a*/);
// only for (w)rite or (a)ppend mode
bool zip_append_file(zip*, const char *entryname, const char *comment, FILE *in, unsigned compress_level);
bool zip_append_file_timeinfo(zip*, const char *entryname, const char *comment, FILE *in, unsigned compress_level, struct tm *);
bool zip_append_mem(zip*, const char *entryname, const char *comment, const void *in, unsigned inlen, unsigned compress_level);
bool zip_append_mem_timeinfo(zip*, const char *entryname, const char *comment, const void *in, unsigned inlen, unsigned compress_level, struct tm *);
// only for (r)ead mode
int zip_find(zip*, const char *entryname); // convert entry to index. returns <0 if not found.
unsigned zip_count(zip*);
char* zip_name(zip*, unsigned index);
char* zip_modt(zip*, unsigned index);
unsigned zip_size(zip*, unsigned index);
unsigned zip_hash(zip*, unsigned index);
bool zip_file(zip*, unsigned index); // is_file? (dir if name ends with '/'; file otherwise)
bool zip_test(zip*, unsigned index);
char* zip_comment(zip*, unsigned index);
unsigned zip_codec(zip*, unsigned index);
unsigned zip_offset(zip*, unsigned index);
unsigned zip_excess(zip*, unsigned index);
void* zip_extract(zip*, unsigned index); // must free() after use
bool zip_extract_file(zip*, unsigned index, FILE *out);
unsigned zip_extract_inplace(zip*, unsigned index, void *out, unsigned outlen_with_excess);
void zip_close(zip*);
#endif // ZIP_H
// -----------------------------------------------------------------------------
#ifdef ZIP_C
//#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#ifdef _WIN32
#include <io.h> // _chsize_s
#endif
#ifndef REALLOC
#define REALLOC realloc
#endif
#ifndef STRDUP
#define STRDUP strdup
#endif
#ifndef FPRINTF
#define FPRINTF(...) ((void)0) // printf for error logging
#endif
#ifndef ERR
#define ERR(NUM, ...) (FPRINTF(stderr, "" __VA_ARGS__), FPRINTF(stderr, "(%s:%d) %s\n", __FILE__, __LINE__, strerror(errno)), /*fflush(stderr),*/ (NUM)) // (NUM)
#endif
#ifndef COMPRESS
#define COMPRESS(...) ((unsigned)0)
#endif
#ifndef DECOMPRESS
#define DECOMPRESS(...) ((unsigned)0)
#endif
#ifndef BOUNDS
#define BOUNDS(...) ((unsigned)0)
#endif
#ifndef EXCESS
#define EXCESS(...) ((unsigned)0)
#endif
#ifdef COMPRESS_H
#undef COMPRESS
#undef DECOMPRESS
#undef BOUNDS
#undef EXCESS
static unsigned COMPRESS(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags /*[0..1]*/) {
return ( flags > 10 ? mem_encode : deflate_encode )(in, inlen, out, outlen, flags);
}
static unsigned DECOMPRESS(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags) {
return ( flags > 10 ? mem_decode : deflate_decode )(in, inlen, out, outlen);
}
static unsigned BOUNDS(unsigned inlen, unsigned flags) {
return ( flags > 10 ? mem_bounds : deflate_bounds )(inlen, flags);
}
static unsigned EXCESS(unsigned flags) {
return ( flags > 10 ? mem_excess : deflate_excess )(flags);
}
#elif defined DEFLATE_H
#undef COMPRESS
#undef DECOMPRESS
#undef BOUNDS
#undef EXCESS
static unsigned COMPRESS(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags /*[0..1]*/) {
return deflate_encode(in, inlen, out, outlen, flags);
}
static unsigned DECOMPRESS(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags) {
return deflate_decode(in, inlen, out, outlen);
}
static unsigned BOUNDS(unsigned inlen, unsigned flags) {
return deflate_bounds(inlen, flags);
}
static unsigned EXCESS(unsigned flags) {
return deflate_excess(flags);
}
#endif
#pragma pack(push, 1)
typedef struct {
uint32_t signature; // 0x02014B50
uint16_t versionMadeBy; // unsupported
uint16_t versionNeededToExtract; // unsupported
uint16_t generalPurposeBitFlag; // unsupported
uint16_t compressionMethod; // 0-store,8-deflate
uint16_t lastModFileTime;
uint16_t lastModFileDate;
uint32_t crc32;
uint32_t compressedSize;
uint32_t uncompressedSize;
uint16_t fileNameLength;
uint16_t extraFieldLength; // unsupported
uint16_t fileCommentLength; // unsupported
uint16_t diskNumberStart; // unsupported
uint16_t internalFileAttributes; // unsupported
uint32_t externalFileAttributes; // unsupported
uint32_t relativeOffsetOflocalHeader;
} JZGlobalFileHeader;
typedef struct {
uint32_t signature; // 0x06054b50
uint16_t diskNumber; // unsupported
uint16_t centralDirectoryDiskNumber; // unsupported
uint16_t numEntriesThisDisk; // unsupported
uint16_t numEntries;
uint32_t centralDirectorySize;
uint32_t centralDirectoryOffset;
uint16_t zipCommentLength;
// Followed by .ZIP file comment (variable size)
} JZEndRecord;
#pragma pack(pop)
// Verifying that structs are correct sized...
typedef int static_assert_sizeof_JZGlobalFileHeader[sizeof(JZGlobalFileHeader) == 46 ? 1:-1];
typedef int static_assert_sizeof_JZEndRecord[sizeof(JZEndRecord) == 22 ? 1:-1];
enum { sizeof_JZLocalFileHeader = 30 };
// Constants
enum { JZ_OK = 0, JZ_ERRNO = -1, JZ_BUFFER_SIZE = 65536 };
// Callback prototype for central reading function. Returns Z_OK while done.
typedef int (*JZRecordCallback)(FILE *fp, int index, JZGlobalFileHeader *header,
char *filename, void *extra, char *comment, void *user_data);
// Read ZIP file end record. Will move within file. Returns Z_OK, or error code
int jzReadEndRecord(FILE *fp, JZEndRecord *endRecord) {
long fileSize, readBytes, i;
JZEndRecord *er = 0;
if(fseek(fp, 0, SEEK_END)) {
return ERR(JZ_ERRNO, "Couldn't go to end of zip file!");
}
if((fileSize = ftell(fp)) <= sizeof(JZEndRecord)) {
return ERR(JZ_ERRNO, "Too small file to be a zip!");
}
unsigned char jzBuffer[JZ_BUFFER_SIZE]; // maximum zip descriptor size
readBytes = (fileSize < sizeof(jzBuffer)) ? fileSize : sizeof(jzBuffer);
if(fseek(fp, fileSize - readBytes, SEEK_SET)) {
return ERR(JZ_ERRNO, "Cannot seek in zip file!");
}
if(fread(jzBuffer, 1, readBytes, fp) < readBytes) {
return ERR(JZ_ERRNO, "Couldn't read end of zip file!");
}
// Naively assume signature can only be found in one place...
for(i = readBytes - sizeof(JZEndRecord); i >= 0; i--) {
er = (JZEndRecord *)(jzBuffer + i);
if(er->signature == 0x06054B50)
break;
}
if(i < 0 || !er) {
return ERR(JZ_ERRNO, "End record signature not found in zip!");
}
memcpy(endRecord, er, sizeof(JZEndRecord));
JZEndRecord *e = endRecord;
FPRINTF(stdout, "end)\n\tsignature: 0x%X\n", e->signature ); // 0x06054b50
FPRINTF(stdout, "\tdiskNumber: %d\n", e->diskNumber ); // unsupported
FPRINTF(stdout, "\tcentralDirectoryDiskNumber: %d\n", e->centralDirectoryDiskNumber ); // unsupported
FPRINTF(stdout, "\tnumEntriesThisDisk: %d\n", e->numEntriesThisDisk ); // unsupported
FPRINTF(stdout, "\tnumEntries: %d\n", e->numEntries );
FPRINTF(stdout, "\tcentralDirectorySize: %u %#x\n", e->centralDirectorySize, e->centralDirectorySize );
FPRINTF(stdout, "\tcentralDirectoryOffset: %u %#x\n", e->centralDirectoryOffset, e->centralDirectoryOffset );
FPRINTF(stdout, "\tzipCommentLength: %d\n---\n", e->zipCommentLength );
if(endRecord->diskNumber || endRecord->centralDirectoryDiskNumber ||
endRecord->numEntries != endRecord->numEntriesThisDisk) {
return ERR(JZ_ERRNO, "Multifile zips not supported!");
}
return JZ_OK;
}
// Read ZIP file global directory. Will move within file. Returns Z_OK, or error code
// Callback is called for each record, until callback returns zero
2023-10-13 10:59:44 +00:00
int jzReadCentralDirectory(FILE *fp, JZEndRecord *endRecord, JZRecordCallback callback, void *user_data, void *user_data2) {
JZGlobalFileHeader fileHeader;
if(fseek(fp, endRecord->centralDirectoryOffset, SEEK_SET)) {
return ERR(JZ_ERRNO, "Cannot seek in zip file!");
}
for(int i=0; i<endRecord->numEntries; i++) {
FPRINTF(stdout, "%d)\n@-> %lu %#lx\n", i+1, (unsigned long)ftell(fp), (unsigned long)ftell(fp));
long offset = ftell(fp); // store current position
if(fread(&fileHeader, 1, sizeof(JZGlobalFileHeader), fp) < sizeof(JZGlobalFileHeader)) {
return ERR(JZ_ERRNO, "Couldn't read file header #%d!", i);
}
2023-10-13 10:59:44 +00:00
fileHeader.relativeOffsetOflocalHeader += (uintptr_t)user_data2;
JZGlobalFileHeader *g = &fileHeader, copy = *g;
FPRINTF(stdout, "\tsignature: %u %#x\n", g->signature, g->signature); // 0x02014B50
FPRINTF(stdout, "\tversionMadeBy: %u %#x\n", g->versionMadeBy, g->versionMadeBy); // unsupported
FPRINTF(stdout, "\tversionNeededToExtract: %u %#x\n", g->versionNeededToExtract, g->versionNeededToExtract); // unsupported
FPRINTF(stdout, "\tgeneralPurposeBitFlag: %u %#x\n", g->generalPurposeBitFlag, g->generalPurposeBitFlag); // unsupported
FPRINTF(stdout, "\tcompressionMethod: %u %#x\n", g->compressionMethod, g->compressionMethod); // 0-store,8-deflate
FPRINTF(stdout, "\tlastModFileTime: %u %#x\n", g->lastModFileTime, g->lastModFileTime);
FPRINTF(stdout, "\tlastModFileDate: %u %#x\n", g->lastModFileDate, g->lastModFileDate);
FPRINTF(stdout, "\tcrc32: %#x\n", g->crc32);
FPRINTF(stdout, "\tcompressedSize: %u\n", g->compressedSize);
FPRINTF(stdout, "\tuncompressedSize: %u\n", g->uncompressedSize);
FPRINTF(stdout, "\tfileNameLength: %u\n", g->fileNameLength);
FPRINTF(stdout, "\textraFieldLength: %u\n", g->extraFieldLength); // unsupported
FPRINTF(stdout, "\tfileCommentLength: %u\n", g->fileCommentLength); // unsupported
FPRINTF(stdout, "\tdiskNumberStart: %u\n", g->diskNumberStart); // unsupported
FPRINTF(stdout, "\tinternalFileAttributes: %#x\n", g->internalFileAttributes); // unsupported
FPRINTF(stdout, "\texternalFileAttributes: %#x\n", g->externalFileAttributes); // unsupported
FPRINTF(stdout, "\trelativeOffsetOflocalHeader: %u %#x\n", g->relativeOffsetOflocalHeader, g->relativeOffsetOflocalHeader);
if(fileHeader.signature != 0x02014B50) {
return ERR(JZ_ERRNO, "Invalid file header signature %#x #%d!", fileHeader.signature, i);
}
if(fileHeader.fileNameLength + 1 >= JZ_BUFFER_SIZE) {
return ERR(JZ_ERRNO, "Too long file name %u #%d!", fileHeader.fileNameLength, i);
}
// filename
char jzFilename[JZ_BUFFER_SIZE/3];
if(fread(jzFilename, 1, fileHeader.fileNameLength, fp) < fileHeader.fileNameLength) {
return ERR(JZ_ERRNO, "Couldn't read filename #%d!", i);
}
jzFilename[fileHeader.fileNameLength] = '\0'; // NULL terminate
// extra block
unsigned char jzExtra[JZ_BUFFER_SIZE/3];
if(fread(jzExtra, 1, fileHeader.extraFieldLength, fp) < fileHeader.extraFieldLength) {
return ERR(JZ_ERRNO, "Couldn't read extra block #%d!", i);
}
// comment block
char jzComment[JZ_BUFFER_SIZE/3];
if(fread(jzComment, 1, fileHeader.fileCommentLength, fp) < fileHeader.fileCommentLength) {
return ERR(JZ_ERRNO, "Couldn't read comment block #%d!", i);
}
jzComment[fileHeader.fileCommentLength] = '\0'; // NULL terminate
// seek to local file header, then skip file header + filename + extra field length
if(fseek(fp, fileHeader.relativeOffsetOflocalHeader + sizeof_JZLocalFileHeader - 2 - 2, SEEK_SET)) {
return ERR(JZ_ERRNO, "Cannot seek in file!");
}
if(fread(&fileHeader.fileNameLength, 1, 2, fp) < 2) {
return ERR(JZ_ERRNO, "Couldn't read local filename #%d!", i);
}
if(fread(&fileHeader.extraFieldLength, 1, 2, fp) < 2) {
return ERR(JZ_ERRNO, "Couldn't read local extrafield #%d!", i);
}
if(fseek(fp, fileHeader.relativeOffsetOflocalHeader + sizeof_JZLocalFileHeader + fileHeader.fileNameLength + fileHeader.extraFieldLength, SEEK_SET)) {
return ERR(JZ_ERRNO, "Cannot seek in file!");
}
FPRINTF(stdout, "@-> %lu %#lx\n---\n", (unsigned long)ftell(fp), (unsigned long)ftell(fp));
if( JZ_OK != callback(fp, i, &fileHeader, jzFilename, jzExtra, jzComment, user_data) )
break; // keep going while callback returns ok
fseek(fp, offset, SEEK_SET); // return to position
fseek(fp, sizeof(JZGlobalFileHeader) + copy.fileNameLength, SEEK_CUR); // skip entry
fseek(fp, copy.extraFieldLength + copy.fileCommentLength, SEEK_CUR); // skip entry
}
return JZ_OK;
}
// Read data from file stream, described by header, to preallocated buffer. Returns Z_OK, or error code
int jzReadData(FILE *fp, JZGlobalFileHeader *header, void *out) {
if(header->compressionMethod == 0) { // Store - just read it
if(fread(out, 1, header->uncompressedSize, fp) < header->uncompressedSize || ferror(fp))
return JZ_ERRNO;
} else if((header->compressionMethod & 255) == 8) { // Deflate
uint16_t level = header->compressionMethod >> 8;
unsigned outlen = header->uncompressedSize;
unsigned inlen = header->compressedSize;
void *in = REALLOC(0, inlen + 8); // small excess as some decompressors are really wild with output buffers (lz4x)
if(in == NULL) return ERR(JZ_ERRNO, "Could not allocate mem for decompress");
unsigned read = fread(in, 1, inlen, fp);
if(read != inlen) return ERR(JZ_ERRNO, "Could not read file"); // TODO: more robust read loop
unsigned ret = DECOMPRESS(in, inlen, out, outlen, level);
REALLOC(in, 0);
if(!ret) return ERR(JZ_ERRNO, "Could not decompress");
} else {
return JZ_ERRNO;
}
return JZ_OK;
}
#define JZHOUR(t) ((t)>>11)
#define JZMINUTE(t) (((t)>>5) & 63)
#define JZSECOND(t) (((t) & 31) * 2)
#define JZTIME(h,m,s) (((h)<<11) + ((m)<<5) + (s)/2)
#define JZYEAR(t) (((t)>>9) + 1980)
#define JZMONTH(t) (((t)>>5) & 15)
#define JZDAY(t) ((t) & 31)
#define JZDATE(y,m,d) ((((y)-1980)<<9) + ((m)<<5) + (d))
// end of junzip.c ---
struct zip {
FILE *in, *out;
struct zip_entry {
JZGlobalFileHeader header;
char timestamp[40];
char *filename;
uint64_t offset;
void *extra;
char *comment;
} *entries;
unsigned count;
};
uint32_t zip__crc32(uint32_t crc, const void *data, size_t n_bytes) {
// CRC32 routine is from Björn Samuelsson's public domain implementation at http://home.thep.lu.se/~bjorn/crc/
static uint32_t table[256] = {0};
if(!*table) for(uint32_t i = 0; i < 0x100; ++i) {
uint32_t r = i;
for(int j = 0; j < 8; ++j) r = (r & 1 ? 0 : (uint32_t)0xEDB88320L) ^ r >> 1;
table[i] = r ^ (uint32_t)0xFF000000L;
}
for(size_t i = 0; i < n_bytes; ++i) {
crc = table[(uint8_t)crc ^ ((uint8_t*)data)[i]] ^ crc >> 8;
}
return crc;
}
int zip__callback(FILE *fp, int idx, JZGlobalFileHeader *header, char *filename, void *extra, char *comment, void *user_data) {
zip *z = user_data;
unsigned index = z->count;
z->entries = REALLOC(z->entries, (++z->count) * sizeof(struct zip_entry) );
struct zip_entry *e = &z->entries[index];
e->header = *header;
e->filename = STRDUP(filename);
e->offset = ftell(fp);
e->extra = REALLOC(0, header->extraFieldLength);
memcpy(e->extra, extra, header->extraFieldLength);
e->comment = STRDUP(comment);
snprintf(e->timestamp, sizeof(e->timestamp), "%04d/%02d/%02d %02d:%02d:%02d" "%c" "%04d%02d%02d%02d%02d%02d",
JZYEAR(header->lastModFileDate), JZMONTH(header->lastModFileDate), JZDAY(header->lastModFileDate),
JZHOUR(header->lastModFileTime), JZMINUTE(header->lastModFileTime), JZSECOND(header->lastModFileTime),
'\0', // hidden date in base10
JZYEAR(header->lastModFileDate), JZMONTH(header->lastModFileDate), JZDAY(header->lastModFileDate),
JZHOUR(header->lastModFileTime), JZMINUTE(header->lastModFileTime), JZSECOND(header->lastModFileTime)
);
return JZ_OK;
}
// zip read
int ZIP_DEBUG = 0;
int zip_find(zip *z, const char *entryname) {
int zip_debug = ZIP_DEBUG; ZIP_DEBUG = 0;
if(zip_debug) FPRINTF(stdout, "zip_find(%s)\n", entryname);
if( z->in ) for( int i = z->count; --i >= 0; ) { // in case of several copies, grab most recent file (last coincidence)
if(zip_debug) FPRINTF(stdout, "\t%d) %s\n", i, z->entries[i].filename);
if( 0 == strcmp(entryname, z->entries[i].filename)) return i;
}
return -1;
}
bool zip_file(zip *z, unsigned index) { // is_file? (dir if attrib&15 or name ends with '/'; file otherwise)
if( z->in && index < z->count ) {
char *name = zip_name(z, index);
return (name[ strlen(name) ] != '/') && !(z->entries[index].header.externalFileAttributes & 0x10);
}
return 0;
}
unsigned zip_count(zip *z) {
return z->in ? z->count : 0;
}
unsigned zip_hash(zip *z, unsigned index) {
return z->in && index < z->count ? z->entries[index].header.crc32 : 0;
}
char *zip_modt(zip *z, unsigned index) {
return z->in && index < z->count ? z->entries[index].timestamp : 0;
}
char *zip_name(zip *z, unsigned index) {
return z->in && index < z->count ? z->entries[index].filename : NULL;
}
char *zip_comment(zip *z, unsigned index) {
return z->in && index < z->count ? z->entries[index].comment : NULL;
}
unsigned zip_size(zip *z, unsigned index) {
return z->in && index < z->count ? z->entries[index].header.uncompressedSize : 0;
}
unsigned zip_offset(zip *z, unsigned index) {
return z->in && index < z->count ? z->entries[index].offset : 0;
}
unsigned zip_codec(zip *z, unsigned index) {
if( z->in && index < z->count ) {
unsigned cm = z->entries[index].header.compressionMethod;
return cm < 255 ? cm : cm >> 8;
}
return 0;
}
unsigned zip_excess(zip *z, unsigned index) {
if( z->in && index < z->count ) {
unsigned level = z->entries[index].header.compressionMethod;
unsigned flags = level >> 8;
return EXCESS(flags);
}
return 0;
}
unsigned zip_extract_inplace(zip *z, unsigned index, void *out, unsigned outlen) {
if( z->in && index < z->count ) {
JZGlobalFileHeader *header = &(z->entries[index].header);
if( outlen >= header->uncompressedSize ) {
fseek(z->in, z->entries[index].offset, SEEK_SET);
int ret = jzReadData(z->in, header, (char*)out);
return ret == JZ_OK ? header->uncompressedSize : 0;
}
}
return 0;
}
void *zip_extract(zip *z, unsigned index) { // must free()
if( z->in && index < z->count ) {
unsigned outlen = (unsigned)z->entries[index].header.uncompressedSize;
char *out = (char*)REALLOC(0, outlen + 1 + zip_excess(z, index));
unsigned ret = zip_extract_inplace(z, index, out, outlen);
return ret ? (out[outlen] = '\0', out) : (REALLOC(out, 0), out = 0);
}
return NULL;
}
bool zip_extract_file(zip *z, unsigned index, FILE *out) {
void *data = zip_extract(z, index);
if( !data ) return false;
unsigned datalen = (unsigned)z->entries[index].header.uncompressedSize;
bool ok = fwrite(data, 1, datalen, out) == datalen;
REALLOC( data, 0 );
return ok;
}
bool zip_test(zip *z, unsigned index) {
void *ret = zip_extract(z, index);
bool ok = !!ret;
REALLOC(ret, 0);
return ok;
}
// zip append/write
bool zip_append_file(zip *z, const char *entryname, const char *comment, FILE *in, unsigned compress_level) {
if( !entryname ) return ERR(false, "No filename provided");
struct stat st;
struct tm *timeinfo;
stat(entryname, &st);
timeinfo = localtime(&st.st_mtime);
return zip_append_file_timeinfo(z, entryname, comment, in, compress_level, timeinfo);
}
bool zip_append_file_timeinfo(zip *z, const char *entryname, const char *comment, FILE *in, unsigned compress_level, struct tm* timeinfo) {
if( !in ) return ERR(false, "No input file provided");
if( !entryname ) return ERR(false, "No filename provided");
if( !timeinfo ) return ERR(false, "No timeinfo provided");
// @fixme: calc whole crc contents
uint32_t crc = 0;
2023-11-15 19:14:14 +00:00
unsigned char buf[4096];
while(!feof(in) && !ferror(in)) crc = zip__crc32(crc, buf, fread(buf, 1, sizeof(buf), in));
if(ferror(in)) return ERR(false, "Error while calculating CRC, skipping store.");
unsigned index = z->count;
z->entries = REALLOC(z->entries, (++z->count) * sizeof(struct zip_entry));
if(z->entries == NULL) return ERR(false, "Failed to allocate new entry!");
struct zip_entry *e = &z->entries[index], zero = {0};
*e = zero;
e->filename = STRDUP(entryname);
e->comment = comment ? STRDUP(comment) : 0;
e->header.signature = 0x02014B50;
e->header.versionMadeBy = 10; // random stuff
e->header.versionNeededToExtract = 10;
e->header.generalPurposeBitFlag = 0;
e->header.lastModFileTime = JZTIME(timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
e->header.lastModFileDate = JZDATE(timeinfo->tm_year+1900,timeinfo->tm_mon+1,timeinfo->tm_mday);
e->header.crc32 = crc;
e->header.uncompressedSize = ftell(in);
e->header.fileNameLength = strlen(entryname);
e->header.extraFieldLength = 0;
e->header.fileCommentLength = comment ? strlen(comment) : 0;
e->header.diskNumberStart = 0;
e->header.internalFileAttributes = 0;
e->header.externalFileAttributes = 0x20; // whatever this is
e->header.relativeOffsetOflocalHeader = ftell(z->out);
#if defined _MSC_VER || (defined __TINYC__ && defined _WIN32)
static __declspec(thread)
#else
static __thread
#endif
void* comp = 0, *data = 0;
// if(comp) comp = REALLOC(comp, 1); // re-entry optimization: hopefully the allocator will optimize this out (N>1-byte)
// if(data) data = REALLOC(data, 1); // re-entry optimization: hopefully the allocator will optimize this out (N>1-byte)
if(!compress_level) goto dont_compress;
// Read whole file and and use compress(). Simple but won't handle GB files well.
unsigned dataSize = e->header.uncompressedSize, compSize = BOUNDS(e->header.uncompressedSize, compress_level);
2023-10-14 19:47:24 +00:00
comp = REALLOC(comp, compSize);
if(comp == NULL) goto cant_compress;
2023-10-14 19:47:24 +00:00
data = REALLOC(data, dataSize + 8); // small excess as some compressors are really wild when reading from buffers (lz4x)
if(data == NULL) goto cant_compress; else memset((char*)data + dataSize, 0, 8);
fseek(in, 0, SEEK_SET); // rewind
size_t bytes = fread(data, 1, dataSize, in);
if(bytes != dataSize) {
return ERR(false, "Failed to read file in full (%lu vs. %ld bytes)", (unsigned long)bytes, dataSize);
}
compSize = COMPRESS(data, (unsigned)dataSize, comp, (unsigned)compSize, compress_level);
if(!compSize) goto cant_compress;
if(compSize >= (dataSize * 0.98) ) goto dont_compress;
uint16_t cl = 8 | (compress_level > 10 ? compress_level << 8 : 0);
e->header.compressedSize = compSize;
e->header.compressionMethod = cl;
goto common;
cant_compress:
dont_compress:;
e->header.compressedSize = ftell(in);
e->header.compressionMethod = 0; // store method
common:;
// write local header
uint32_t signature = 0x04034B50;
fwrite(&signature, 1, sizeof(signature), z->out);
fwrite(&(e->header.versionNeededToExtract), 1, sizeof_JZLocalFileHeader - sizeof(signature), z->out);
// write filename
fwrite(entryname, 1, strlen(entryname), z->out);
// write comment
// if( comment ) fwrite(comment, 1, strlen(comment), z->out);
if(e->header.compressionMethod) {
// store compressed blob
fwrite(comp, compSize, 1, z->out);
} else {
// store uncompressed blob
fseek(in, 0, SEEK_SET);
while(!feof(in) && !ferror(in)) {
size_t bytes = fread(buf, 1, sizeof(buf), in);
fwrite(buf, 1, bytes, z->out);
}
}
// REALLOC(comp, 0); // see re-entry optimization above
// REALLOC(data, 0); // see re-entry optimization above
return true;
}
bool zip_append_mem(zip *z, const char *entryname, const char *comment, const void *in, unsigned inlen, unsigned compress_level) {
if( !entryname ) return ERR(false, "No filename provided");
struct stat st;
struct tm *timeinfo;
stat(entryname, &st);
timeinfo = localtime(&st.st_mtime);
return zip_append_mem_timeinfo(z, entryname, comment, in, inlen, compress_level, timeinfo);
}
bool zip_append_mem_timeinfo(zip *z, const char *entryname, const char *comment, const void *in, unsigned inlen, unsigned compress_level, struct tm* timeinfo) {
if( !in ) return ERR(false, "No input file provided");
if( !entryname ) return ERR(false, "No filename provided");
if( !timeinfo ) return ERR(false, "No timeinfo provided");
uint32_t crc = zip__crc32(0, in, inlen);
unsigned index = z->count;
z->entries = REALLOC(z->entries, (++z->count) * sizeof(struct zip_entry));
if(z->entries == NULL) return ERR(false, "Failed to allocate new entry!");
struct zip_entry *e = &z->entries[index], zero = {0};
*e = zero;
e->filename = STRDUP(entryname);
e->comment = comment ? STRDUP(comment) : 0;
e->header.signature = 0x02014B50;
e->header.versionMadeBy = 10; // random stuff
e->header.versionNeededToExtract = 10;
e->header.generalPurposeBitFlag = 0;
e->header.lastModFileTime = JZTIME(timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
e->header.lastModFileDate = JZDATE(timeinfo->tm_year+1900,timeinfo->tm_mon+1,timeinfo->tm_mday);
e->header.crc32 = crc;
e->header.uncompressedSize = inlen;
e->header.fileNameLength = strlen(entryname);
e->header.extraFieldLength = 0;
e->header.fileCommentLength = comment ? strlen(comment) : 0;
e->header.diskNumberStart = 0;
e->header.internalFileAttributes = 0;
e->header.externalFileAttributes = 0x20; // whatever this is
e->header.relativeOffsetOflocalHeader = ftell(z->out);
#if defined _MSC_VER || (defined __TINYC__ && defined _WIN32)
static __declspec(thread)
#else
static __thread
#endif
void* comp = 0, *data = 0;
// if(comp) comp = REALLOC(comp, 1); // re-entry optimization: hopefully the allocator will optimize this out (N>1-byte)
// if(data) data = REALLOC(data, 1); // re-entry optimization: hopefully the allocator will optimize this out (N>1-byte)
if(!compress_level) goto dont_compress;
// Read whole file and and use compress(). Simple but won't handle GB files well.
unsigned dataSize = e->header.uncompressedSize, compSize = BOUNDS(e->header.uncompressedSize, compress_level);
2023-10-14 19:47:24 +00:00
comp = REALLOC(comp, compSize);
if(comp == NULL) goto cant_compress;
2023-10-14 19:47:24 +00:00
data = REALLOC(data, dataSize + 8); // small excess as some compressors are really wild when reading from buffers (lz4x)
if(data == NULL) goto cant_compress; else memset((char*)data + dataSize, 0, 8);
size_t bytes = inlen;
memcpy(data, in, inlen);
if(bytes != dataSize) {
return ERR(false, "Failed to read file in full (%lu vs. %ld bytes)", (unsigned long)bytes, dataSize);
}
compSize = COMPRESS(data, (unsigned)dataSize, comp, (unsigned)compSize, compress_level);
if(!compSize) goto cant_compress;
if(compSize >= (dataSize * 0.98) ) goto dont_compress;
uint16_t cl = 8 | (compress_level > 10 ? compress_level << 8 : 0);
e->header.compressedSize = compSize;
e->header.compressionMethod = cl;
goto common;
cant_compress:
dont_compress:;
e->header.compressedSize = inlen;
e->header.compressionMethod = 0; // store method
common:;
// write local header
uint32_t signature = 0x04034B50;
fwrite(&signature, 1, sizeof(signature), z->out);
fwrite(&(e->header.versionNeededToExtract), 1, sizeof_JZLocalFileHeader - sizeof(signature), z->out);
// write filename
fwrite(entryname, 1, strlen(entryname), z->out);
// write comment
// if( comment ) fwrite(comment, 1, strlen(comment), z->out);
if(e->header.compressionMethod) {
// store compressed blob
fwrite(comp, compSize, 1, z->out);
} else {
// store uncompressed blob
fwrite(in, 1, inlen, z->out);
}
// REALLOC(comp, 0); // see re-entry optimization above
// REALLOC(data, 0); // see re-entry optimization above
return true;
}
// zip common
2023-10-14 19:47:24 +00:00
#if 1
# define zip_lockfile(f) (void)(f)
# define zip_unlockfile(f) (void)(f)
#else
# if (defined(__TINYC__) && defined(_WIN32))
# define zip_lockfile(f) (void)(f)
# define zip_unlockfile(f) (void)(f)
# elif defined _MSC_VER
# define zip_lockfile(f) _lock_file(f)
# define zip_unlockfile(f) _unlock_file(f)
# else
# define zip_lockfile(f) flockfile(f)
# define zip_unlockfile(f) funlockfile(f)
# endif
#endif
2023-10-13 10:59:44 +00:00
zip* zip_open_handle(FILE *fp, const char *mode) {
if( !fp ) return ERR(NULL, "cannot open file for %s mode", mode);
zip zero = {0}, *z = (zip*)REALLOC(0, sizeof(zip));
2023-10-14 19:47:24 +00:00
if( !z ) return ERR(NULL, "out of mem"); else *z = zero;
if( mode[0] == 'w' ) {
2023-10-14 19:47:24 +00:00
zip_lockfile(z->out = fp);
return z;
}
if( mode[0] == 'r' || mode[0] == 'a' ) {
2023-10-14 19:47:24 +00:00
zip_lockfile(z->in = fp);
2023-10-13 10:59:44 +00:00
unsigned long long seekcur = ftell(z->in);
JZEndRecord jzEndRecord = {0};
if(jzReadEndRecord(fp, &jzEndRecord) != JZ_OK) {
REALLOC(z, 0);
2023-10-14 19:47:24 +00:00
return ERR(NULL, "Couldn't read ZIP file end record.");
}
2023-10-13 10:59:44 +00:00
jzEndRecord.centralDirectoryOffset += seekcur;
if(jzReadCentralDirectory(fp, &jzEndRecord, zip__callback, z, (void*)(uintptr_t)seekcur ) != JZ_OK) {
REALLOC(z, 0);
2023-10-14 19:47:24 +00:00
return ERR(NULL, "Couldn't read ZIP file central directory.");
}
if( mode[0] == 'a' ) {
// resize (by truncation)
size_t resize = jzEndRecord.centralDirectoryOffset;
int fd = fileno(fp);
if( fd != -1 ) {
#ifdef _WIN32
int ok = 0 == _chsize_s( fd, resize );
#else
int ok = 0 == ftruncate( fd, (off_t)resize );
#endif
fflush(fp);
fseek( fp, 0L, SEEK_END );
}
2023-10-14 19:47:24 +00:00
z->out = z->in;
z->in = NULL;
}
return z;
}
REALLOC(z, 0);
2023-10-14 19:47:24 +00:00
return ERR(NULL, "Unknown open mode %s", mode);
}
2023-10-13 10:59:44 +00:00
zip* zip_open(const char *file, const char *mode /*r,w,a*/) {
struct stat buffer;
int exists = (stat(file, &buffer) == 0);
if( mode[0] == 'a' && !exists ) mode = "wb";
FILE *fp = fopen(file, mode[0] == 'w' ? "wb" : mode[0] == 'a' ? "a+b" : "rb");
2023-10-14 19:47:24 +00:00
if (!fp) return NULL;
if (mode[0] == 'a') fseek(fp, 0L, SEEK_SET);
zip *z = zip_open_handle(fp, mode);
if (!z) return fclose(fp), NULL;
return z;
2023-10-13 10:59:44 +00:00
}
void zip_close(zip* z) {
if( z->out && z->count ) {
// prepare end record
JZEndRecord end = {0};
end.signature = 0x06054b50;
end.diskNumber = 0;
end.centralDirectoryDiskNumber = 0;
end.numEntriesThisDisk = z->count;
end.numEntries = z->count;
end.centralDirectoryOffset = ftell(z->out);
// flush global directory: global file+filename each
for(unsigned i = 0; i < z->count; i++) {
struct zip_entry *h = &z->entries[i];
JZGlobalFileHeader *g = &h->header;
fwrite(g, 1, sizeof(JZGlobalFileHeader), z->out);
fwrite(h->filename, 1, g->fileNameLength, z->out);
fwrite(h->extra, 1, g->extraFieldLength, z->out);
fwrite(h->comment, 1, g->fileCommentLength, z->out);
}
end.centralDirectorySize = ftell(z->out) - end.centralDirectoryOffset;
end.zipCommentLength = 0;
// flush end record
fwrite(&end, 1, sizeof(end), z->out);
}
2023-10-14 19:47:24 +00:00
if( z->out ) zip_unlockfile(z->out), fclose(z->out);
if( z->in ) zip_unlockfile(z->in), fclose(z->in);
// clean up
for(unsigned i = 0; i < z->count; ++i ) {
REALLOC(z->entries[i].filename, 0);
if(z->entries[i].extra) REALLOC(z->entries[i].extra, 0);
if(z->entries[i].comment) REALLOC(z->entries[i].comment, 0);
}
if(z->entries) REALLOC(z->entries, 0);
zip zero = {0}; *z = zero; REALLOC(z, 0);
}
#endif // ZIP_C
//#line 1 "src/tar.c"
// gnu tar and ustar extraction
// - rlyeh, public domain.
#ifndef TAR_H
#define TAR_H
typedef struct tar tar;
tar *tar_open(const char *filename, const char *mode);
int tar_find(tar*, const char *entryname); // returns entry number; or <0 if not found.
unsigned tar_count(tar*);
char* tar_name(tar*, unsigned index);
unsigned tar_size(tar*, unsigned index);
unsigned tar_offset(tar*, unsigned index);
void* tar_extract(tar*, unsigned index); // must free() after use
void tar_close(tar *t);
#endif
// -----------------------------------------------------------------------------
#ifdef TAR_C
//#pragma once
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifndef STRDUP
#define STRDUP strdup
#endif
#ifndef REALLOC
#define REALLOC realloc
#endif
#ifndef ERR
#define ERR(NUM, ...) (fprintf(stderr, "" __VA_ARGS__), fprintf(stderr, "(%s:%d)\n", __FILE__, __LINE__), fflush(stderr), (NUM)) // (NUM)
#endif
struct tar {
FILE *in;
unsigned count;
struct tar_entry {
char *filename;
unsigned size;
size_t offset;
} *entries;
};
// equivalent to sscanf(buf, 8, "%.7o", &size); or (12, "%.11o", &modtime)
// ignores everything after first null or space, including trailing bytes
uint64_t tar__octal( const char *src, const char *eof ) {
uint64_t sum = 0, mul = 1;
const char *ptr = eof;
while( ptr-- >= src ) eof = ( 0 != ptr[1] && 32 != ptr[1] ) ? eof : ptr;
while( eof-- >= src ) sum += (uint8_t)(eof[1] - '0') * mul, mul *= 8;
return sum;
}
typedef int (*tar_callback)(const char *filename, unsigned inlen, size_t offset, void *userdata);
int tar__push_entry(const char *filename, unsigned inlen, size_t offset, void *userdata) {
tar *t = (tar *)userdata;
unsigned index = t->count;
t->entries = REALLOC(t->entries, (++t->count) * sizeof(struct tar_entry));
struct tar_entry *e = &t->entries[index];
e->filename = STRDUP(filename);
e->size = inlen;
e->offset = offset;
return 1;
}
int tar__parse( FILE *in, tar_callback yield, void *userdata ) {
enum {
name = 0, // (null terminated)
mode = 100, // (octal)
uid = 108, // (octal)
gid = 116, // (octal)
size = 124, // (octal)
modtime = 136, // (octal)
checksum = 148, // (octal)
type = 156, // \0|'0':file,1:hardlink,2:symlink,3:chardev,4:blockdev,5:dir,6:fifo,L:longnameblocknext
linkname = 157, // if !ustar link indicator
magic = 257, // if ustar "ustar" -- 6th character may be space or null, else zero
version = 263, // if ustar "00", else zero
uname = 265, // if ustar owner username, else zero
gname = 297, // if ustar owner groupname, else zero
devmajor = 329, // if ustar device major number, else zero
devminor = 337, // if ustar device minor number , else zero
path = 345, // if ustar filename prefix, else zero
padding = 500, // if ustar relevant for checksum, else zero
total = 512
};
// handle both regular tar and ustar tar filenames until end of tar is found
char header[512], entry[512], blank[512] = {0};
while( !ferror(in) ) {
if( 512 != fread(header, 1, 512, in ) ) break;
if( memcmp( header, blank, 512 ) ) { // if not end of tar
if( !memcmp( header+magic, "ustar", 5 ) ) { // if valid ustar
int namelen = strlen(header+name), pathlen = strlen(header+path); // read filename
snprintf(entry, 512, "%.*s" "%s" "%.*s",
pathlen < 155 ? pathlen : 155, header+path,
pathlen ? "/" : "",
namelen < 100 ? namelen : 100, header+name );
switch( header[type] ) {
default: // unsupported file type
break; case '5': //yield(entry.back()!='/'?entry+'/':entry,0);// directory
break; case 'L': strcpy(entry, header+name); fread(header,1,512,in); // gnu tar long filename
break; case '0': case 0: { // regular file
uint64_t len = tar__octal(header+size, header+modtime); // decode octal size
int cont = yield(entry, len, ftell(in), userdata); // yield entry
fseek(in,len,SEEK_CUR); // skip blob
fseek(in,(512 - (len & 511)) & 511,SEEK_CUR); // skip padding
}
}
} else return ERR(0, "not a .tar file");
} else return ferror(in) ? ERR(0, "file error") : 1;
}
return ERR(0, "read error");
}
// ---
tar *tar_open(const char *filename, const char *mode) {
if(mode[0] != 'r') return ERR(NULL, "(w) and (a) not supported for now");
FILE *in = fopen(filename, "rb");
if(!in) return ERR(NULL, "cant open file '%s' for reading", filename);
tar zero = {0}, *t = REALLOC(0, sizeof(tar));
if( !t ) { fclose(in); return ERR(NULL, "out of mem"); }
*t = zero;
t->in = in;
2023-10-13 10:59:44 +00:00
return tar__parse(in, tar__push_entry, t) ? t : NULL;
}
int tar_find(tar *t, const char *entryname) {
if( t->in ) for( int i = t->count; --i >= 0; ) { // in case of several copies, grab most recent file (last coincidence)
if( 0 == strcmp(entryname, t->entries[i].filename)) return i;
}
return -1;
}
unsigned tar_count(tar *t) {
return t ? t->count : 0;
}
char* tar_name(tar *t, unsigned index) {
return t && index < t->count ? t->entries[index].filename : 0;
}
unsigned tar_size(tar *t, unsigned index) {
return t && index < t->count ? t->entries[index].size : 0;
}
unsigned tar_offset(tar *t, unsigned index) {
return t && index < t->count ? (unsigned)t->entries[index].offset : 0;
}
void *tar_extract(tar *t, unsigned index) {
if( t && index < t->count ) {
fseek(t->in, t->entries[index].offset, SEEK_SET);
size_t len = t->entries[index].size;
void *data = REALLOC(0, t->entries[index].size);
fread(data, 1, len, t->in);
return data;
}
return 0;
}
void tar_close(tar *t) {
fclose(t->in);
for( int i = 0; i < t->count; ++i) {
REALLOC(t->entries[i].filename, 0);
}
tar zero = {0};
*t = zero;
REALLOC(t, 0);
}
#ifdef TAR_DEMO
int main( int argc, char **argv ) {
if(argc <= 1) exit(printf("%s file.tar [file_to_view]\n", argv[0]));
tar *t = tar_open(argv[1], "rb");
if( t ) {
for( int i = 0; i < tar_count(t); ++i ) {
printf("%d) %s (%u bytes)\n", i+1, tar_name(t,i), tar_size(t,i));
char *data = tar_extract(t,i);
if(argc>2) if(0==strcmp(argv[2],tar_name(t,i))) printf("%.*s\n", tar_size(t,i), data);
free(data);
}
tar_close(t);
}
}
#define main main__
#endif //TAR_DEMO
#endif //TAR_C
//#line 1 "src/pak.c"
// pak file reading/writing/appending.
// - rlyeh, public domain.
//
// ## PAK
// - [ref] https://quakewiki.org/wiki/.pak (public domain).
// - Header: 12 bytes
// - "PACK" 4-byte
// - directory offset uint32
// - directory size uint32 (number of files by dividing this by 64, which is sizeof(pak_entry))
//
// - File Directory Entry (Num files * 64 bytes)
// - Each Directory Entry: 64 bytes
// - file name 56-byte null-terminated string. Includes path. Example: "maps/e1m1.bsp".
// - file offset uint32 from beginning of pak file.
// - file size uint32
#ifndef PAK_H
#define PAK_H
typedef struct pak pak;
pak* pak_open(const char *fname, const char *mode /*a,r,w*/);
// (w)rite or (a)ppend modes only
int pak_append_file(pak*, const char *filename, FILE *in);
int pak_append_data(pak*, const char *filename, const void *in, unsigned inlen);
// (r)ead only mode
int pak_find(pak*,const char *fname); // return <0 if error; index otherwise.
unsigned pak_count(pak*);
unsigned pak_size(pak*,unsigned index);
unsigned pak_offset(pak*, unsigned index);
char *pak_name(pak*,unsigned index);
void *pak_extract(pak*, unsigned index); // must free() after use
void pak_close(pak*);
#endif
// ---
#ifdef PAK_C
//#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifndef REALLOC
#define REALLOC realloc
#endif
#ifndef ERR
#define ERR(NUM, ...) (fprintf(stderr, "" __VA_ARGS__), fprintf(stderr, "(%s:%d)\n", __FILE__, __LINE__), fflush(stderr), (NUM)) // (NUM)
#endif
#include <stdint.h>
static inline uint32_t pak_swap32( uint32_t t ) { return (t >> 24) | (t << 24) | ((t >> 8) & 0xff00) | ((t & 0xff00) << 8); }
#if defined(_M_IX86) || defined(_M_X64) // #ifdef LITTLE
#define htob32(x) pak_swap32(x)
#define btoh32(x) pak_swap32(x)
#define htol32(x) (x)
#define ltoh32(x) (x)
#else
#define htob32(x) (x)
#define btoh32(x) (x)
#define htol32(x) pak_swap32(x)
#define ltoh32(x) pak_swap32(x)
#endif
#pragma pack(push, 1)
typedef struct pak_header {
char id[4];
uint32_t offset;
uint32_t size;
} pak_header;
typedef struct pak_file {
char name[56];
uint32_t offset;
uint32_t size;
} pak_file;
#pragma pack(pop)
typedef int static_assert_sizeof_pak_header[sizeof(pak_header) == 12 ? 1:-1];
typedef int static_assert_sizeof_pak_file[sizeof(pak_file) == 64 ? 1:-1];
typedef struct pak {
FILE *in, *out;
int dummy;
pak_file *entries;
unsigned count;
} pak;
pak *pak_open(const char *fname, const char *mode) {
struct stat buffer;
int exists = (stat(fname, &buffer) == 0);
if(mode[0] == 'a' && !exists ) mode = "wb";
if(mode[0] != 'w' && mode[0] != 'r' && mode[0] != 'a') return NULL;
FILE *fp = fopen(fname, mode[0] == 'w' ? "wb" : mode[0] == 'r' ? "rb" : "r+b");
if(!fp) return ERR(NULL, "cant open file '%s' in '%s' mode", fname, mode);
pak *p = malloc(sizeof(pak)), zero = {0};
if(!p) return fclose(fp), ERR(NULL, "out of mem");
*p = zero;
if( mode[0] == 'r' || mode[0] == 'a' ) {
pak_header header = {0};
if( fread(&header, 1, sizeof(pak_header), fp) != sizeof(pak_header) ) {
return fclose(fp), ERR(NULL, "read error");
}
if( memcmp(header.id, "PACK", 4) ) {
return fclose(fp), ERR(NULL, "not a .pak file");
}
header.offset = ltoh32(header.offset);
header.size = ltoh32(header.size);
unsigned num_files = header.size / sizeof(pak_file);
if( fseek(fp, header.offset, SEEK_SET) != 0 ) {
return fclose(fp), ERR(NULL, "read error");
}
p->count = num_files;
p->entries = REALLOC(0, num_files * sizeof(pak_file));
if( fread(p->entries, num_files, sizeof(pak_file), fp) != sizeof(pak_file) ) {
goto fail;
}
for( unsigned i = 0; i < num_files; ++i ) {
pak_file *e = &p->entries[i];
e->offset = ltoh32(e->offset);
e->size = ltoh32(e->size);
}
if( mode[0] == 'a' ) {
// resize (by truncation)
size_t resize = header.offset;
int fd = fileno(fp);
if( fd != -1 ) {
#ifdef _WIN32
int ok = 0 == _chsize_s( fd, resize );
#else
int ok = 0 == ftruncate( fd, (off_t)resize );
#endif
fflush(fp);
fseek( fp, 0L, SEEK_END );
}
p->out = fp;
p->in = NULL;
} else {
p->in = fp;
}
return p;
}
if(mode[0] == 'w') {
p->out = fp;
// write temporary header
char header[12] = {0};
if( fwrite(header, 1,12, p->out) != 12) goto fail;
return p;
}
fail:;
if(fp) fclose(fp);
if(p->entries) REALLOC(p->entries, 0);
if(p) REALLOC(p, 0);
return NULL;
}
int pak_append_data(pak *p, const char *filename, const void *in, unsigned inlen) {
if(!p->out) return ERR(0, "read-only pak file");
// index meta
unsigned index = p->count++;
p->entries = REALLOC(p->entries, p->count * sizeof(pak_file));
pak_file *e = &p->entries[index], zero = {0};
*e = zero;
snprintf(e->name, 55, "%s", filename); // @todo: verify 56 chars limit
e->size = inlen;
e->offset = ftell(p->out);
// write blob
fwrite(in, 1, inlen, p->out);
return !ferror(p->out);
}
int pak_append_file(pak *p, const char *filename, FILE *in) {
// index meta
unsigned index = p->count++;
p->entries = REALLOC(p->entries, p->count * sizeof(pak_file));
pak_file *e = &p->entries[index], zero = {0};
*e = zero;
snprintf(e->name, 55, "%s", filename); // @todo: verify 56 chars limit
e->offset = ftell(p->out);
char buf[1<<15];
while(!feof(in) && !ferror(in)) {
size_t bytes = fread(buf, 1, sizeof(buf), in);
fwrite(buf, 1, bytes, p->out);
}
e->size = ftell(p->out) - e->offset;
return !ferror(p->out);
}
void pak_close(pak *p) {
if(p->out) {
// write toc
uint32_t seek = 0 + 12, dirpos = (uint32_t)ftell(p->out), dirlen = p->count * 64;
for(unsigned i = 0; i < p->count; ++i) {
pak_file *e = &p->entries[i];
// write name (truncated if needed), and trailing zeros
char zero[56] = {0};
int namelen = strlen(e->name);
fwrite( e->name, 1, namelen >= 56 ? 55 : namelen, p->out );
fwrite( zero, 1, namelen >= 56 ? 1 : 56 - namelen, p->out );
// write offset + length pair
uint32_t pseek = htol32(seek); fwrite( &pseek, 1,4, p->out );
uint32_t psize = htol32(e->size); fwrite( &psize, 1,4, p->out );
seek += e->size;
}
// patch header
fseek(p->out, 0L, SEEK_SET);
fwrite("PACK", 1,4, p->out);
dirpos = htol32(dirpos); fwrite( &dirpos, 1,4, p->out );
dirlen = htol32(dirlen); fwrite( &dirlen, 1,4, p->out );
}
// close streams
if(p->in) fclose(p->in);
if(p->out) fclose(p->out);
// clean up
for(unsigned i = 0; i < p->count; ++i) {
pak_file *e = &p->entries[i];
}
REALLOC(p->entries, 0);
// delete
pak zero = {0};
*p = zero;
REALLOC(p, 0);
}
int pak_find(pak *p, const char *filename) {
if( p->in ) {
for( int i = p->count; --i >= 0; ) {
if(!strcmp(p->entries[i].name, filename)) return i;
}
}
return -1;
}
unsigned pak_count(pak *p) {
return p->in ? p->count : 0;
}
unsigned pak_offset(pak *p, unsigned index) {
return p->in && index < p->count ? p->entries[index].offset : 0;
}
unsigned pak_size(pak *p, unsigned index) {
return p->in && index < p->count ? p->entries[index].size : 0;
}
char *pak_name(pak *p, unsigned index) {
return p->in && index < p->count ? p->entries[index].name : NULL;
}
void *pak_extract(pak *p, unsigned index) {
if( p->in && index < p->count ) {
pak_file *e = &p->entries[index];
if( fseek(p->in, e->offset, SEEK_SET) != 0 ) {
return ERR(NULL, "cant seek");
}
void *buffer = REALLOC(0, e->size);
if( !buffer ) {
return ERR(NULL, "out of mem");
}
if( fread(buffer, 1, e->size, p->in) != e->size ) {
REALLOC(buffer, 0);
return ERR(NULL, "cant read");
}
return buffer;
}
return NULL;
}
#ifdef PAK_DEMO
int main(int argc, char **argv) {
puts("creating test.pak archive (3) ...");
pak *p = pak_open("test.pak", "wb");
if( p ) {
pak_append_data(p, "/index.txt", "just a file", strlen("just a file"));
pak_append_data(p, "/file/name1.txt", "just another file #1", strlen("just another file #1"));
pak_append_data(p, "/file/name2.txt", "just another file #2", strlen("just another file #2"));
pak_close(p);
}
puts("appending file to test.pak (1) ...");
p = pak_open("test.pak", "a+b");
if( p ) {
pak_append_data(p, "/new/file", "this is an appended file", strlen("this is an appended file"));
pak_close(p);
}
const char *fname = argc > 1 ? argv[1] : "test.pak";
printf("listing %s archive ...\n", fname);
p = pak_open(fname, "rb");
if( p ) {
for( unsigned i = 0; i < pak_count(p); ++i ) {
printf(" %d) @%08x %11u %s ", i+1, pak_offset(p,i), pak_size(p,i), pak_name(p,i));
void *data = pak_extract(p,i);
printf("\r%c\n", data ? 'Y':'N');
if(argc > 2 && data)
if(i == pak_find(p,argv[2]))
printf("%.*s\n", (int)pak_size(p,i), (char*)data);
free(data);
}
pak_close(p);
}
puts("ok");
}
#endif // PAK_DEMO
#endif // PAK_C
//#line 1 "src/dir.c"
// directory iteration.
// - rlyeh, public domain.
#ifndef DIR_H
#define DIR_H
typedef struct dir dir;
dir *dir_open(const char *filename, const char *mode); // recursive 'r'
int dir_find(dir*, const char *entryname); // returns entry number; or <0 if not found.
unsigned dir_count(dir*);
char* dir_name(dir*, unsigned index);
unsigned dir_size(dir*, unsigned index);
unsigned dir_file(dir*, unsigned index); // dir_isfile? bool?
void* dir_read(dir*, unsigned index); // must free() after use
void dir_close(dir*);
#endif
// -----------------------------------------------------------------------------
#ifdef DIR_C
//#pragma once
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
# if defined _WIN32 && defined(__TINYC__)
#include <windows.h> // tcc
#elif defined _WIN32
#include <winsock2.h> // msc+gcc
#else
#include <dirent.h>
#endif
#ifndef STRDUP
#define STRDUP strdup
#endif
#ifndef REALLOC
#define REALLOC realloc
#endif
#ifndef ERR
#define ERR(NUM, ...) (fprintf(stderr, "" __VA_ARGS__), fprintf(stderr, "(%s:%d)\n", __FILE__, __LINE__), fflush(stderr), (NUM)) // (NUM)
#endif
typedef struct dir_entry {
char *filename;
size_t size;
size_t is_dir : 1;
} dir_entry;
struct dir {
dir_entry *entry;
unsigned count;
bool recursive;
};
// ---
2023-11-05 15:30:11 +00:00
#ifndef S_ISDIR
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#endif
#ifndef S_ISREG
#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
#endif
int dir_yield(dir *d, const char *pathfile, char *name, int namelen) {
int ok = 0;
#ifdef _WIN32
WIN32_FIND_DATAA fdata = { 0 };
snprintf(name, namelen, "%s/*", pathfile);
for( HANDLE h = FindFirstFileA(name, &fdata ); h != INVALID_HANDLE_VALUE; ok = (FindClose( h ), h = INVALID_HANDLE_VALUE, 1)) {
for( int next = 1; next; next = FindNextFileA(h, &fdata) != 0 ) {
int is_dir = (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0;
2023-10-10 08:52:29 +00:00
if( is_dir && fdata.cFileName[0] == '.' ) continue;
snprintf(name, namelen, "%s/%s%s", pathfile, fdata.cFileName, is_dir ? "/" : "");
struct stat st; if( !is_dir ) if(stat(name, &st) < 0) continue;
// add
dir_entry de = { STRDUP(name), is_dir ? 0 : st.st_size, is_dir };
d->entry = (dir_entry*)REALLOC(d->entry, ++d->count * sizeof(dir_entry));
d->entry[d->count-1] = de;
// recurse
if (is_dir && d->recursive) { char pf[512]; snprintf(pf, 512, "%.*s", (int)strlen(name) - 1, name); name[0] = 0; dir_yield(d, pf, name, namelen); }
}
}
#else
snprintf(name, namelen, "%s/", pathfile);
for( DIR *dir = opendir(name); dir; ok = (closedir(dir), dir = 0, 1)) {
for( struct dirent *ep; (ep = readdir(dir)) != NULL; ) {
snprintf(name, namelen, "%s/%s", pathfile, ep->d_name);
struct stat st; if( stat(name, &st) < 0 ) continue;
2023-10-10 08:52:29 +00:00
DIR *tmp = opendir(/*ep->d_*/name); int is_dir = !!tmp; if(tmp) closedir(tmp); // @todo:optimizeme (maybe use stat instead)
if( is_dir && ep->d_name[0] == '.' ) continue;
// add
dir_entry de = { STRDUP(name), is_dir ? 0 : st.st_size, is_dir };
d->entry = (dir_entry*)REALLOC(d->entry, ++d->count * sizeof(dir_entry));
d->entry[d->count-1] = de;
// recurse
if (is_dir && d->recursive) { char pf[512]; snprintf(pf, 512, "%s", name); name[0] = 0; dir_yield(d, pf, name, namelen); }
}
}
#endif
return ok;
}
dir *dir_open(const char *pathfile, const char *mode) {
dir *d = (dir*)REALLOC(0, sizeof(dir)), zero = {0}; *d = zero;
d->recursive = (mode[0] == 'R' || mode[0] == 'r');
char *clean = STRDUP( pathfile );
for( int i = 0; clean[i]; ++i ) if(clean[i] == '\\') clean[i] = '/';
for( int len = strlen(clean); clean[--len] == '/'; ) clean[len] = '\0';
char buffer[2048];
dir_yield(d, clean, buffer, 2048);
REALLOC(clean, 0);
return d;
}
int dir_find(dir *d, const char *entryname) {
for( int i = d->count; --i >= 0; ) { // in case of several copies, grab most recent file (last coincidence)
if( 0 == strcmp(entryname, d->entry[i].filename)) return i;
}
return -1;
}
unsigned dir_count(dir *d) {
return d ? d->count : 0;
}
char* dir_name(dir *d, unsigned index) {
return d && index < d->count ? d->entry[index].filename : 0;
}
unsigned dir_size(dir *d, unsigned index) {
return d && index < d->count ? (unsigned)d->entry[index].size : 0;
}
unsigned dir_file(dir *d, unsigned index) {
return d && index < d->count ? (unsigned)!d->entry[index].is_dir : 0;
}
void *dir_read(dir *d, unsigned index) {
if( d && index < d->count ) {
void *data = 0;
for( FILE *fp = fopen(d->entry[index].filename, "rb"); fp; fclose(fp), fp = 0) {
size_t len = d->entry[index].size;
data = REALLOC(0, len);
if( data && fread(data, 1, len, fp) != len ) {
data = REALLOC(data, 0);
}
}
return data;
}
return 0;
}
void dir_close(dir *d) {
for( int i = 0; i < d->count; ++i) {
REALLOC(d->entry[i].filename, 0);
}
dir zero = {0};
*d = zero;
REALLOC(d, 0);
}
#ifdef DIR_DEMO
int main( int argc, char **argv ) {
dir *d = dir_open(argc > 1 ? argv[1] : "./", "rb");
if( d ) {
for( int i = 0; i < dir_count(d); ++i ) {
if( dir_file(d,i) )
printf("%3d) %11d %s\n", i + 1, dir_size(d,i), dir_name(d,i));
else
printf("%3d) %11s %s\n", i + 1, "<dir>", dir_name(d,i));
char *data = dir_read(d,i);
if(argc > 2 && !strcmp(argv[2],dir_name(d,i))) printf("%.*s\n", dir_size(d,i), data);
free(data);
}
dir_close(d);
}
}
#define main main__
#endif //DIR_DEMO
#endif //DIR_C