diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a5e7c18..aedafd1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ set(PHYSFS_SRCS src/physfs_archiver_qpak.c src/physfs_archiver_wad.c src/physfs_archiver_csm.c + src/physfs_archiver_tar.c src/physfs_archiver_zip.c src/physfs_archiver_slb.c src/physfs_archiver_iso9660.c @@ -130,6 +131,11 @@ if(NOT PHYSFS_ARCHIVE_CSM) add_definitions(-DPHYSFS_SUPPORTS_CSM=0) endif() +option(PHYSFS_ARCHIVE_TAR "Enable POSIX TAR / Chasm: The Rift [Demo] Remastered csm.bin support" TRUE) +if(NOT PHYSFS_ARCHIVE_TAR) + add_definitions(-DPHYSFS_SUPPORTS_TAR=0) +endif() + option(PHYSFS_ARCHIVE_HOG "Enable Descent I/II HOG support" TRUE) if(NOT PHYSFS_ARCHIVE_HOG) add_definitions(-DPHYSFS_SUPPORTS_HOG=0) @@ -325,6 +331,7 @@ message_bool_option("7zip support" PHYSFS_ARCHIVE_7Z) message_bool_option("GRP support" PHYSFS_ARCHIVE_GRP) message_bool_option("WAD support" PHYSFS_ARCHIVE_WAD) message_bool_option("CSM support" PHYSFS_ARCHIVE_CSM) +message_bool_option("TAR support" PHYSFS_ARCHIVE_TAR) message_bool_option("HOG support" PHYSFS_ARCHIVE_HOG) message_bool_option("MVL support" PHYSFS_ARCHIVE_MVL) message_bool_option("QPAK support" PHYSFS_ARCHIVE_QPAK) diff --git a/src/Makefile.os2 b/src/Makefile.os2 index dc9ece41..facd7cfe 100644 --- a/src/Makefile.os2 +++ b/src/Makefile.os2 @@ -26,6 +26,7 @@ SRCS = physfs.c & physfs_archiver_slb.c & physfs_archiver_iso9660.c & physfs_archiver_csm.c & + physfs_archiver_tar.c & physfs_archiver_vdf.c diff --git a/src/physfs.c b/src/physfs.c index a45011d1..6037e23e 100644 --- a/src/physfs.c +++ b/src/physfs.c @@ -10,7 +10,7 @@ #define __PHYSICSFS_INTERNAL__ #include "physfs_internal.h" - +#include #if defined(_MSC_VER) /* this code came from https://stackoverflow.com/a/8712996 */ int __PHYSFS_msvc_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) @@ -1191,6 +1191,9 @@ static int initStaticArchivers(void) #if PHYSFS_SUPPORTS_CSM REGISTER_STATIC_ARCHIVER(CSM); #endif + #if PHYSFS_SUPPORTS_TAR + REGISTER_STATIC_ARCHIVER(TAR); + #endif #if PHYSFS_SUPPORTS_SLB REGISTER_STATIC_ARCHIVER(SLB); #endif diff --git a/src/physfs_archiver_tar.c b/src/physfs_archiver_tar.c new file mode 100644 index 00000000..e9ad4f02 --- /dev/null +++ b/src/physfs_archiver_tar.c @@ -0,0 +1,108 @@ +/* + * tar support routines for PhysicsFS. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * based on code by Uli Köhler: https://techoverflow.net/2013/03/29/reading-tar-files-in-c/ + */ + +#define __PHYSICSFS_INTERNAL__ +#include "physfs_internal.h" + +#if PHYSFS_SUPPORTS_TAR +#include "physfs_tar.h" + +static bool TAR_loadEntries(PHYSFS_Io *io, void *arc) +{ + union block zero_block; + union block current_block; + PHYSFS_uint64 count = 0; + + memset(zero_block.buffer, 0, sizeof(BLOCKSIZE)); + memset(current_block.buffer, 0, sizeof(BLOCKSIZE)); + + /* read header block until zero-only terminated block */ + for(; __PHYSFS_readAll(io, current_block.buffer, BLOCKSIZE); count++) + { + if( memcmp(current_block.buffer, zero_block.buffer, BLOCKSIZE) == 0 ) + return true; + + /* verify magic */ + switch(TAR_magic(¤t_block)) + { + case POSIX_FORMAT: + TAR_posix_block(io, arc, ¤t_block, &count); + break; + case OLDGNU_FORMAT: + break; + case GNU_FORMAT: + break; + case V7_FORMAT: + break; + case USTAR_FORMAT: + break; + case STAR_FORMAT: + break; + default: + break; + } + } + + return false; +} + +static void *TAR_openArchive(PHYSFS_Io *io, const char *name, + int forWriting, int *claimed) +{ + void *unpkarc = NULL; + union block first; + enum archive_format format; + + assert(io != NULL); /* shouldn't ever happen. */ + + BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL); + + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, first.buffer, BLOCKSIZE), NULL); + format = TAR_magic(&first); + io->seek(io, 0); + *claimed = format == DEFAULT_FORMAT ? 0 : 1; + + unpkarc = UNPK_openArchive(io, 0, 1); + BAIL_IF_ERRPASS(!unpkarc, NULL); + + if (!TAR_loadEntries(io, unpkarc)) + { + UNPK_abandonArchive(unpkarc); + return NULL; + } /* if */ + + + return unpkarc; +} /* TAR_openArchive */ + + +const PHYSFS_Archiver __PHYSFS_Archiver_TAR = +{ + CURRENT_PHYSFS_ARCHIVER_API_VERSION, + { + "TAR", + "POSIX tar archives / Chasm: the Rift Remastered", + "Jon Daniel ", + "http://www.gnu.org/software/tar/", + 0, + }, + TAR_openArchive, + UNPK_enumerate, + UNPK_openRead, + UNPK_openWrite, + UNPK_openAppend, + UNPK_remove, + UNPK_mkdir, + UNPK_stat, + UNPK_closeArchive +}; + +#endif /* defined PHYSFS_SUPPORTS_TAR */ + +/* end of physfs_archiver_tar.c ... */ + diff --git a/src/physfs_internal.h b/src/physfs_internal.h index b29830ec..beff35d2 100644 --- a/src/physfs_internal.h +++ b/src/physfs_internal.h @@ -89,6 +89,7 @@ extern const PHYSFS_Archiver __PHYSFS_Archiver_HOG; extern const PHYSFS_Archiver __PHYSFS_Archiver_MVL; extern const PHYSFS_Archiver __PHYSFS_Archiver_WAD; extern const PHYSFS_Archiver __PHYSFS_Archiver_CSM; +extern const PHYSFS_Archiver __PHYSFS_Archiver_TAR; extern const PHYSFS_Archiver __PHYSFS_Archiver_SLB; extern const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660; extern const PHYSFS_Archiver __PHYSFS_Archiver_VDF; @@ -204,6 +205,9 @@ void __PHYSFS_smallFree(void *ptr); #ifndef PHYSFS_SUPPORTS_CSM #define PHYSFS_SUPPORTS_CSM PHYSFS_SUPPORTS_DEFAULT #endif +#ifndef PHYSFS_SUPPORTS_TAR +#define PHYSFS_SUPPORTS_TAR PHYSFS_SUPPORTS_DEFAULT +#endif #ifndef PHYSFS_SUPPORTS_QPAK #define PHYSFS_SUPPORTS_QPAK PHYSFS_SUPPORTS_DEFAULT #endif diff --git a/src/physfs_tar.h b/src/physfs_tar.h new file mode 100644 index 00000000..cfc0cb78 --- /dev/null +++ b/src/physfs_tar.h @@ -0,0 +1,283 @@ +#ifndef _INCLUDE_PHYSFS_TAR_H_ +#define _INCLUDE_PHYSFS_TAR_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +#if STDC_HEADERS +# define IN_CTYPE_DOMAIN(c) 1 +#else +# define IN_CTYPE_DOMAIN(c) ((unsigned) (c) <= 0177) +#endif + +#define LG_8 3 +#define LG_64 6 +#define LG_256 8 +#define ISOCTAL(c) ((c)>='0'&&(c)<='7') +#define ISODIGIT(c) ((unsigned) (c) - '0' <= 7 ) +#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint(c)) +#define ISSPACE(c) (IN_CTPYE_DOMAIN (c) && isspace(c)) + +#define BLOCKSIZE 512 /* tar basic block size */ + +#define TMAGIC "ustar" /* ustar and a null */ +#define OLDGNU_MAGIC "ustar " /* 7 chars and a null */ +#define TMAGLEN 6 + +#define TVERSION "00" /* 00 and no null */ +#define TVERSLEN 2 + +enum archive_format +{ + DEFAULT_FORMAT, /* format to be decided later */ + V7_FORMAT, /* old V7 tar format */ + OLDGNU_FORMAT, /* old GNU format */ + USTAR_FORMAT, /* POSIX.1-1988 (ustar) format */ + POSIX_FORMAT, /* POSIX.1-2001 format */ + STAR_FORMAT, /* Star format defined in 1994 */ + GNU_FORMAT, /* new GNU format */ +}; + +enum typefield { + REGTYPE = '0', /* regular file */ + AREGTYPE = '\0', /* regular file */ + LNKTYPE = '1', /* link */ + SYMTYPE = '2', /* reserved */ + CHRTYPE = '3', /* character special */ + BLKTYPE = '4', /* block special */ + DIRTYPE = '5', /* directory */ + FIFOTYPE = '6', /* FIFO special */ + CONTTYPE = '7', /* reserved */ + XHDTYPE = 'x', /* Extended header referring to the next file in the archive */ + XGLTYPE = 'g', /* Global extended header */ + GNUTYPE_DUMPDIR = 'D', /* names of files been in the dir at the time of the dump */ + GNUTYPE_LONGLINK = 'K', /* next file has long linkname. */ + GNUTYPE_LONGNAME = 'L', /* next file has long name. */ + GNUTYPE_MULTIVOL = 'M', /* continuation file started on another volume. */ + GNUTYPE_SPARSE = 'S', /* This is for sparse files. */ + GNUTYPE_VOLHDR = 'V', /* This file is a tape/volume header. Ignore it on extraction. */ + SOLARIS_XHDTYPE = 'X', /* Solaris extended header */ +}; + +enum mode { + TOEXEC = 1 << 0, /* execute/search by other */ + TOWRITE = 1 << 1, /* write by other */ + TOREAD = 1 << 2, /* read by other */ + TGEXEC = 1 << 3, /* execute/search by group */ + TGWRITE = 1 << 4, /* write by group */ + TGREAD = 1 << 5, /* read by group */ + TUEXEC = 1 << 6, /* execute/search by owner */ + TUWRITE = 1 << 7, /* write by owner */ + TUREAD = 1 << 8, /* read by owner */ + TSVTX = 1 << 9, /* reserved */ + TSGID = 1 << 10, /* set GID on execution */ + TSUID = 1 << 11, /* set UID on execution */ +}; + +/* POSIX header. */ +struct posix_header +{ /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[TMAGLEN]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ + /* 500 */ +}; + +/* tar Header Block, from POSIX 1003.1-1990. */ +union block +{ + char buffer[BLOCKSIZE]; + struct posix_header header; +}; + +static PHYSFS_uint64 TAR_decodeOctal(char* data, size_t size) { + unsigned char* currentPtr = (unsigned char*) data + size; + PHYSFS_uint64 sum = 0; + PHYSFS_uint64 currentMultiplier = 1; + unsigned char* checkPtr = currentPtr; + + for (; checkPtr >= (unsigned char*) data; checkPtr--) { + if ((*checkPtr) == 0 || (*checkPtr) == ' ') { + currentPtr = checkPtr - 1; + } + } + for (; currentPtr >= (unsigned char*) data; currentPtr--) { + sum += (*currentPtr - 48) * currentMultiplier; + currentMultiplier *= 8; + } + return sum; +} + +static bool TAR_encodeOctal(char* data, size_t size, PHYSFS_uint64 i) { + if( snprintf(data, size, "%llo", i) < 0 ) + return false; + return true; +} + +static int TAR_magic(union block* block) { + if(strcmp(block->header.magic, OLDGNU_MAGIC) == 0 && strncmp(block->header.version, TVERSION, 2) == 0) + return (int)OLDGNU_FORMAT; + if(strncmp(block->header.magic, TMAGIC, TMAGLEN - 1) == 0) + return (int)POSIX_FORMAT; + return (int)DEFAULT_FORMAT; +} + +static size_t TAR_fileSize(union block* block) { + return TAR_decodeOctal(block->header.size, sizeof(block->header.size)); +} + +static bool TAR_checksum(union block* block) { + PHYSFS_sint64 unsigned_sum = 0; + PHYSFS_sint64 signed_sum = 0; + PHYSFS_uint64 reference_chksum = 0; + char orig_chksum[8]; + int i = 0; + + memcpy(orig_chksum, block->header.chksum, 8); + memset(block->header.chksum, ' ', 8); + + for(; i < BLOCKSIZE; i++) { + unsigned_sum += ((unsigned char*) block->buffer)[i]; + signed_sum += ((signed char*) block->buffer)[i]; + } + memcpy(block->header.chksum, orig_chksum, 8); + reference_chksum = TAR_decodeOctal(orig_chksum, 12); + return (reference_chksum == unsigned_sum || reference_chksum == signed_sum); +} + +time_t TAR_time(union block* block) +{ + return TAR_decodeOctal(block->header.mtime, 12); +} + +mode_t TAR_mode(union block* block) +{ + PHYSFS_uint64 v = TAR_decodeOctal(block->header.mode, 8); + + mode_t mode = ((v & TSUID ? S_ISUID : 0) + | (v & TSGID ? S_ISGID : 0) + | (v & TSVTX ? S_ISVTX : 0) + | (v & TUREAD ? S_IRUSR : 0) + | (v & TUWRITE ? S_IWUSR : 0) + | (v & TUEXEC ? S_IXUSR : 0) + | (v & TGREAD ? S_IRGRP : 0) + | (v & TGWRITE ? S_IWGRP : 0) + | (v & TGEXEC ? S_IXGRP : 0) + | (v & TOREAD ? S_IROTH : 0) + | (v & TOWRITE ? S_IWOTH : 0) + | (v & TOEXEC ? S_IXOTH : 0)); + return mode; +} + +bool TAR_set_mode(union block* block, mode_t mode) +{ + + PHYSFS_uint64 dst = ((mode & S_ISUID ? TSUID : 0) + | (mode & S_ISGID ? TSGID : 0) + | (mode & S_ISVTX ? TSVTX : 0) + | (mode & S_IRUSR ? TUREAD : 0) + | (mode & S_IWUSR ? TUWRITE : 0) + | (mode & S_IXUSR ? TUEXEC : 0) + | (mode & S_IRGRP ? TGREAD : 0) + | (mode & S_IWGRP ? TGWRITE : 0) + | (mode & S_IXGRP ? TGEXEC : 0) + | (mode & S_IROTH ? TOREAD : 0) + | (mode & S_IWOTH ? TOWRITE : 0) + | (mode & S_IXOTH ? TOEXEC : 0)); + + if(!TAR_encodeOctal(block->header.mode, 8, dst)) + return false; + return true; +} + +bool TAR_posix_block(PHYSFS_Io* io, void* arc, union block* block, PHYSFS_uint64* count) +{ + static bool long_name = false; + const PHYSFS_Allocator* allocator = PHYSFS_getAllocator(); + char name[PATH_MAX] = { 0 }; + PHYSFS_sint64 time = 0; + PHYSFS_uint64 size = 0; + PHYSFS_uint64 pos = 0; + PHYSFS_uint64 pad = 0; + char* buf = NULL; + + /* verify checksum */ + if(!TAR_checksum(block)) + return false; + + /* get time */ + time = TAR_time(block); + + memset(name, '\0', PATH_MAX); + /* support prefix */ + if(strlen(block->header.prefix) > 0) + { + strcpy(name, block->header.prefix); + name[strlen(name)] = '/'; + } + strcpy(&name[strlen(name)], block->header.name); + + /* add file type entry */ + if (block->header.typeflag == REGTYPE || block->header.typeflag == 0) { + /* support long file names */ + if(long_name) { + strcpy(&name[0], block->header.name); + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, block->buffer, BLOCKSIZE), 0); + long_name = false; + (*count)++; + } + size = TAR_fileSize(block); + pos = ((*count) + 1) * BLOCKSIZE; + pad = (BLOCKSIZE - (size % BLOCKSIZE)) % BLOCKSIZE; + buf = allocator->Malloc(size + pad); + /* add entry to arc */ + BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, size + pad), 0); + (*count) += ((size + pad) / BLOCKSIZE ); + allocator->Free(buf); + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, time, time, pos, size), 0); + } + /* add directory type entry */ + else if(block->header.typeflag == DIRTYPE) + { + BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 1, time, time, 0, 0), 0); + } + /* long name mode */ + else if(block->header.typeflag == GNUTYPE_LONGNAME) + { + long_name = true; + } + else + { + // UNHANDLED + } + return true; +} + +#endif /* _INCLUDE_PHYSFS_TAR_H_ */ + +/* end of physfs_tar.h ... */ +