2023-07-30 19:18:50 +00:00
|
|
|
// 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>
|
|
|
|
|
|
|
|
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*/);
|
2023-07-30 19:18:50 +00:00
|
|
|
|
|
|
|
// 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) {
|
2023-07-30 19:18:50 +00:00
|
|
|
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;
|
|
|
|
|
2023-07-30 19:18:50 +00:00
|
|
|
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;
|
|
|
|
unsigned char buf[1<<15];
|
|
|
|
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);
|
2023-07-30 19:18:50 +00:00
|
|
|
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)
|
2023-07-30 19:18:50 +00:00
|
|
|
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);
|
2023-07-30 19:18:50 +00:00
|
|
|
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)
|
2023-07-30 19:18:50 +00:00
|
|
|
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) {
|
2023-07-30 19:18:50 +00:00
|
|
|
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;
|
2023-07-30 19:18:50 +00:00
|
|
|
if( mode[0] == 'w' ) {
|
2023-10-14 19:47:24 +00:00
|
|
|
zip_lockfile(z->out = fp);
|
2023-07-30 19:18:50 +00:00
|
|
|
return z;
|
|
|
|
}
|
|
|
|
if( mode[0] == 'r' || mode[0] == 'a' ) {
|
2023-10-14 19:47:24 +00:00
|
|
|
zip_lockfile(z->in = fp);
|
2023-07-30 19:18:50 +00:00
|
|
|
|
2023-10-13 10:59:44 +00:00
|
|
|
unsigned long long seekcur = ftell(z->in);
|
|
|
|
|
2023-07-30 19:18:50 +00:00
|
|
|
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-07-30 19:18:50 +00:00
|
|
|
}
|
2023-10-13 10:59:44 +00:00
|
|
|
|
|
|
|
jzEndRecord.centralDirectoryOffset += seekcur;
|
|
|
|
|
|
|
|
if(jzReadCentralDirectory(fp, &jzEndRecord, zip__callback, z, (void*)(uintptr_t)seekcur ) != JZ_OK) {
|
2023-07-30 19:18:50 +00:00
|
|
|
REALLOC(z, 0);
|
2023-10-14 19:47:24 +00:00
|
|
|
return ERR(NULL, "Couldn't read ZIP file central directory.");
|
2023-07-30 19:18:50 +00:00
|
|
|
}
|
|
|
|
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;
|
2023-07-30 19:18:50 +00:00
|
|
|
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-07-30 19:18:50 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-07-30 19:18:50 +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);
|
2023-07-30 19:18:50 +00:00
|
|
|
// 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;
|
2023-07-30 19:18:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
// ---
|
|
|
|
|
|
|
|
#if !defined(S_ISDIR)
|
|
|
|
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
|
|
|
#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;
|
|
|
|
|
2023-07-30 19:18:50 +00:00
|
|
|
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;
|
|
|
|
|
2023-07-30 19:18:50 +00:00
|
|
|
// 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
|