From 32aca79221529367fd117824ff4d69af19220526 Mon Sep 17 00:00:00 2001 From: janwas Date: Wed, 28 Dec 2005 20:29:22 +0000 Subject: [PATCH] allocator: add single_calloc (generalization of code previously in wposix for WDIR); add da_reserve; expand pool allocator to allow variable size entries byte_order: add FOURCC_BE and _LE versions (needed for zip) ogl: quick hack: prevent crash on laptop by providing a pglDrawRangeElementsEXT stub file, h_mgr: update pool_alloc call site tex: fix incorrect indexing/stride for mipmap generation vfs_mount: disable archives (zip code is WIP) zip: heavy WIP. add support for writing archives (needed for thesis); revised Zip read code. sped up archive open. further major changes pending. wposix: moved allocator code (see above) This was SVN commit r3298. --- source/lib/allocators.cpp | 129 +++- source/lib/allocators.h | 53 +- source/lib/byte_order.h | 11 +- source/lib/ogl.cpp | 7 + source/lib/res/file/file.cpp | 2 +- source/lib/res/file/vfs_mount.cpp | 2 + source/lib/res/file/zip.cpp | 1187 ++++++++++++++++++----------- source/lib/res/graphics/tex.cpp | 24 +- source/lib/res/h_mgr.cpp | 2 +- source/lib/sysdep/win/wposix.cpp | 28 +- 10 files changed, 963 insertions(+), 482 deletions(-) diff --git a/source/lib/allocators.cpp b/source/lib/allocators.cpp index 064719bb99..98d680bb70 100644 --- a/source/lib/allocators.cpp +++ b/source/lib/allocators.cpp @@ -16,11 +16,69 @@ // http://www.stud.uni-karlsruhe.de/~urkt/ #include "precompiled.h" + #include "posix.h" +#include "sysdep/cpu.h" // CAS #include "allocators.h" +//----------------------------------------------------------------------------- +// allocator optimized for single instances +//----------------------------------------------------------------------------- + +// intended for applications that frequently alloc/free a single +// fixed-size object. caller provides static storage and an in-use flag; +// we use that memory if available and otherwise fall back to the heap. +// if the application only has one object in use at a time, malloc is +// avoided; this is faster and avoids heap fragmentation. +// +// thread-safe. + +void* single_calloc(void* storage, volatile uintptr_t* in_use_flag, size_t size) +{ + // sanity check + debug_assert(*in_use_flag == 0 || *in_use_flag == 1); + + void* p; + + // successfully reserved the single instance + if(CAS(in_use_flag, 0, 1)) + p = storage; + // already in use (rare) - allocate from heap + else + p = malloc(size); + + memset(p, 0, size); + return p; +} + + +void single_free(void* storage, volatile uintptr_t* in_use_flag, void* p) +{ + // sanity check + debug_assert(*in_use_flag == 0 || *in_use_flag == 1); + + if(p == storage) + { + if(CAS(in_use_flag, 1, 0)) + { + // ok, flag has been reset to 0 + } + else + debug_warn("in_use_flag out of sync (double free?)"); + } + // was allocated from heap + else + { + // single instance may have been freed by now - cannot assume + // anything about in_use_flag. + + free(p); + } +} + + //----------------------------------------------------------------------------- // dynamic (expandable) array //----------------------------------------------------------------------------- @@ -224,6 +282,20 @@ LibError da_set_size(DynArray* da, size_t new_size) } +// make sure at least bytes starting at are committed and +// ready for use. +LibError da_reserve(DynArray* da, size_t size) +{ + // default to page size (the OS won't commit less anyway); + // grab more if request requires it. + const size_t expand_amount = MIN(4*KiB, size); + + if(da->pos + size > da->cur_size) + return da_set_size(da, da->cur_size + expand_amount); + return ERR_OK; +} + + // change access rights of the array memory; used to implement // write-protection. affects the currently committed pages as well as // all subsequently added pages. @@ -266,7 +338,7 @@ LibError da_read(DynArray* da, void* data, size_t size) // starts at offset DynArray.pos and advances this. LibError da_append(DynArray* da, const void* data, size_t size) { - RETURN_ERR(da_set_size(da, da->pos+size)); + RETURN_ERR(da_reserve(da, size)); memcpy2(da->base+da->pos, data, size); da->pos += size; return ERR_OK; @@ -279,7 +351,7 @@ LibError da_append(DynArray* da, const void* data, size_t size) // design parameters: // - O(1) alloc and free; -// - fixed-size blocks; +// - fixed XOR variable size blocks; // - doesn't preallocate the entire pool; // - returns sequential addresses. @@ -308,15 +380,18 @@ static void* freelist_pop(void** pfreelist) static const size_t POOL_CHUNK = 4*KiB; -// ready

for use. pool_alloc will return chunks of memory that -// are exactly bytes. is the upper limit [bytes] on +// ready

for use. is the upper limit [bytes] on // pool size (this is how much address space is reserved). // -// note: el_size must at least be enough for a pointer (due to freelist -// implementation) but not exceed the expand-by amount. +// can be 0 to allow variable-sized allocations +// (which cannot be freed individually); +// otherwise, it specifies the number of bytes that will be +// returned by pool_alloc (whose size parameter is then ignored). +// in the latter case, size must at least be enough for a pointer +// (due to freelist implementation). LibError pool_create(Pool* p, size_t max_size, size_t el_size) { - if(el_size < sizeof(void*) || el_size > POOL_CHUNK) + if(el_size != 0 && el_size < sizeof(void*)) CHECK_ERR(ERR_INVALID_PARAM); RETURN_ERR(da_alloc(&p->da, max_size)); @@ -355,8 +430,17 @@ bool pool_contains(Pool* p, void* el) // return an entry from the pool, or 0 if it would have to be expanded and // there isn't enough memory to do so. // exhausts the freelist before returning new entries to improve locality. -void* pool_alloc(Pool* p) +// +// if the pool was set up with fixed-size elements, is ignored; +// otherwise, bytes are allocated. +void* pool_alloc(Pool* p, size_t size) { + // if pool allows variable sizes, go with the size parameter, + // otherwise the pool el_size setting. + const size_t el_size = p->el_size? p->el_size : size; + + // note: this can never happen in pools with variable-sized elements + // because they disallow pool_free. void* el = freelist_pop(&p->freelist); if(el) goto have_el; @@ -364,12 +448,11 @@ void* pool_alloc(Pool* p) // alloc a new entry { // expand, if necessary - if(p->pos + p->el_size > p->da.cur_size) - if(da_set_size(&p->da, p->da.cur_size + POOL_CHUNK) < 0) - return 0; + if(da_reserve(&p->da, el_size) < 0) + return 0; el = p->da.base + p->pos; - p->pos += p->el_size; + p->pos += el_size; } have_el: @@ -379,8 +462,19 @@ have_el: // make available for reuse in the given pool. +// +// this is not allowed if the pool was set up for variable-size elements. +// (copying with fragmentation would defeat the point of a pool - simplicity) +// we could allow this, but instead warn and bail to make sure it +// never happens inadvertently (leaking memory in the pool). void pool_free(Pool* p, void* el) { + if(p->el_size == 0) + { + debug_warn("pool is set up for variable-size items"); + return; + } + if(pool_contains(p, el)) freelist_push(&p->freelist, el); else @@ -388,6 +482,16 @@ void pool_free(Pool* p, void* el) } +// "free" all allocations that ensued from the given Pool. +// this resets it as if freshly pool_create-d, but doesn't release the +// underlying memory. +void pool_free_all(Pool* p) +{ + p->pos = 0; + p->freelist = 0; +} + + //----------------------------------------------------------------------------- // bucket allocator //----------------------------------------------------------------------------- @@ -396,6 +500,7 @@ void pool_free(Pool* p, void* el) // - variable-size allocations; // - no reuse of allocations, can only free all at once; // - no init necessary; +// - never relocates; // - no fixed limit. // must be constant and power-of-2 to allow fast modulo. diff --git a/source/lib/allocators.h b/source/lib/allocators.h index 4dea296008..df1e4a8092 100644 --- a/source/lib/allocators.h +++ b/source/lib/allocators.h @@ -22,6 +22,23 @@ #include "lib/posix.h" // PROT_* constants for da_set_prot +// +// allocator optimized for single instances +// + +// intended for applications that frequently alloc/free a single +// fixed-size object. caller provides static storage and an in-use flag; +// we use that memory if available and otherwise fall back to the heap. +// if the application only has one object in use at a time, malloc is +// avoided; this is faster and avoids heap fragmentation. +// +// thread-safe. + +extern void* single_calloc(void* storage, volatile uintptr_t* in_use_flag, size_t size); + +extern void single_free(void* storage, volatile uintptr_t* in_use_flag, void* p); + + // // dynamic (expandable) array // @@ -57,6 +74,10 @@ extern LibError da_free(DynArray* da); // new_size (rounded up to the next page size multiple) is met. extern LibError da_set_size(DynArray* da, size_t new_size); +// make sure at least bytes starting at are committed and +// ready for use. +extern LibError da_reserve(DynArray* da, size_t size); + // change access rights of the array memory; used to implement // write-protection. affects the currently committed pages as well as // all subsequently added pages. @@ -85,7 +106,7 @@ extern LibError da_append(DynArray* da, const void* data_src, size_t size); // design parameters: // - O(1) alloc and free; -// - fixed-size blocks; +// - fixed XOR variable size blocks; // - doesn't preallocate the entire pool; // - returns sequential addresses. @@ -93,21 +114,27 @@ extern LibError da_append(DynArray* da, const void* data_src, size_t size); struct Pool { DynArray da; + + // size of elements; see pool_create. size_t el_size; // all bytes in da up to this mark are in circulation or freelist. size_t pos; // pointer to freelist (opaque); see freelist_*. + // never used (remains 0) if elements are of variable size. void* freelist; }; -// ready

for use. pool_alloc will return chunks of memory that -// are exactly bytes. is the upper limit [bytes] on +// ready

for use. is the upper limit [bytes] on // pool size (this is how much address space is reserved). // -// note: el_size must at least be enough for a pointer (due to freelist -// implementation) but not exceed the expand-by amount. +// can be 0 to allow variable-sized allocations +// (which cannot be freed individually); +// otherwise, it specifies the number of bytes that will be +// returned by pool_alloc (whose size parameter is then ignored). +// in the latter case, size must at least be enough for a pointer +// (due to freelist implementation). extern LibError pool_create(Pool* p, size_t max_size, size_t el_size); // free all memory that ensued from

. all elements are made unusable @@ -122,11 +149,24 @@ extern bool pool_contains(Pool* p, void* el); // return an entry from the pool, or 0 if it would have to be expanded and // there isn't enough memory to do so. // exhausts the freelist before returning new entries to improve locality. -extern void* pool_alloc(Pool* p); +// +// if the pool was set up with fixed-size elements, is ignored; +// otherwise, bytes are allocated. +extern void* pool_alloc(Pool* p, size_t size); // make available for reuse in the given pool. +// +// this is not allowed if the pool was set up for variable-size elements. +// (copying with fragmentation would defeat the point of a pool - simplicity) +// we could allow this, but instead warn and bail to make sure it +// never happens inadvertently (leaking memory in the pool). extern void pool_free(Pool* p, void* el); +// "free" all allocations that ensued from the given Pool. +// this resets it as if freshly pool_create-d, but doesn't release the +// underlying memory. +extern void pool_free_all(Pool* p); + // // bucket allocator @@ -136,6 +176,7 @@ extern void pool_free(Pool* p, void* el); // - variable-size allocations; // - no reuse of allocations, can only free all at once; // - no init necessary; +// - never relocates; // - no fixed limit. // opaque! do not read/write any fields! diff --git a/source/lib/byte_order.h b/source/lib/byte_order.h index 7bccb59c50..6aedb204a3 100644 --- a/source/lib/byte_order.h +++ b/source/lib/byte_order.h @@ -9,12 +9,15 @@ // the additional u8 cast ensures each character is treated as unsigned // (otherwise, they'd be promoted to signed int before the u32 cast, // which would break things). +#define FOURCC_BE(a,b,c,d) ( ((u32)(u8)a) << 24 | ((u32)(u8)b) << 16 | \ + ((u32)(u8)c) << 8 | ((u32)(u8)d) << 0 ) +#define FOURCC_LE(a,b,c,d) ( ((u32)(u8)a) << 0 | ((u32)(u8)b) << 8 | \ + ((u32)(u8)c) << 16 | ((u32)(u8)d) << 24 ) + #if BYTE_ORDER == BIG_ENDIAN -#define FOURCC(a,b,c,d) ( ((u32)(u8)a) << 24 | ((u32)(u8)b) << 16 | \ - ((u32)(u8)c) << 8 | ((u32)(u8)d) << 0 ) +# define FOURCC FOURCC_BE #else -#define FOURCC(a,b,c,d) ( ((u32)(u8)a) << 0 | ((u32)(u8)b) << 8 | \ - ((u32)(u8)c) << 16 | ((u32)(u8)d) << 24 ) +# define FOURCC FOURCC_LE #endif diff --git a/source/lib/ogl.cpp b/source/lib/ogl.cpp index 5ac0ba8167..dfc417d8a0 100755 --- a/source/lib/ogl.cpp +++ b/source/lib/ogl.cpp @@ -351,6 +351,10 @@ LibError ogl_get_gfx_info() } +static void __stdcall emu_glDrawRangeElementsEXT(GLenum, GLuint, GLuint, GLsizei, GLenum, GLvoid*) +{ +} + // call after each video mode change, since thereafter extension functions // may have changed [address]. void oglInit() @@ -368,6 +372,9 @@ void oglInit() importExtensionFunctions(); +if(!pglDrawRangeElementsEXT) +pglDrawRangeElementsEXT = emu_glDrawRangeElementsEXT; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &ogl_max_tex_size); glGetIntegerv(GL_MAX_TEXTURE_UNITS, &ogl_max_tex_units); } diff --git a/source/lib/res/file/file.cpp b/source/lib/res/file/file.cpp index a7078dc6bb..d65e6ac896 100755 --- a/source/lib/res/file/file.cpp +++ b/source/lib/res/file/file.cpp @@ -726,7 +726,7 @@ static inline void aiocb_pool_shutdown() static inline aiocb* aiocb_pool_alloc() { ONCE(aiocb_pool_init()); - return (aiocb*)pool_alloc(&aiocb_pool); + return (aiocb*)pool_alloc(&aiocb_pool, 0); } static inline void aiocb_pool_free(void* cb) diff --git a/source/lib/res/file/vfs_mount.cpp b/source/lib/res/file/vfs_mount.cpp index ec819cd3d9..08eaca5dfc 100644 --- a/source/lib/res/file/vfs_mount.cpp +++ b/source/lib/res/file/vfs_mount.cpp @@ -247,6 +247,8 @@ typedef Archives::const_iterator ArchiveCIt; // was successfully added to the list. see comments below. static LibError enqueue_archive(const char* name, const char* P_archive_dir, Archives* archives) { +archives=0;// HACK HACK HACK: disables zip files (WIP) + // caller doesn't want us to check if this is a Zip file. this is the // case in all subdirectories of the mount point, since checking for all // mounted files would be slow. see mount_dir_tree. diff --git a/source/lib/res/file/zip.cpp b/source/lib/res/file/zip.cpp index b023e6359a..0c66a30500 100755 --- a/source/lib/res/file/zip.cpp +++ b/source/lib/res/file/zip.cpp @@ -17,7 +17,7 @@ // http://www.stud.uni-karlsruhe.de/~urkt/ // components: -// - z_*: Zip-specific code +// - za_*: Zip archive handling // passes the list of files in an archive to lookup. // - lookup_*: file lookup // per archive: return file info (e.g. offset, size), given filename. @@ -34,17 +34,18 @@ #include "precompiled.h" +#include + +#include + #include "lib.h" #include "zip.h" #include "../res.h" #include "byte_order.h" +#include "allocators.h" #include "timer.h" -#include - - -#include // provision for removing all ZLib code (all inflate calls will fail). // used for checking DLL dependency; might also simulate corrupt Zip files. @@ -66,21 +67,26 @@ /////////////////////////////////////////////////////////////////////////////// // -// z_*: Zip-specific code +// za_*: Zip archive handling // passes the list of files in an archive to lookup. // /////////////////////////////////////////////////////////////////////////////// +static const off_t LFH_FIXUP = BIT(31); // convenience container for location / size of file in archive. +// separate from ZFile to minimize size of file table. struct ZLoc { - off_t ofs; + off_t ofs; // bit 31 set if fixup needed off_t csize; // = 0 if not compressed - off_t ucsize; + // these are returned by zip_stat: + off_t ucsize; time_t mtime; + const char* fn; + // why csize? // file I/O may be N-buffered, so it's good to know when the raw data // stops, or else we potentially overshoot by N-1 blocks. @@ -96,26 +102,86 @@ struct ZLoc // Zip file data structures and signatures -static const char cdfh_id[] = {'P','K','\1','\2'}; -static const char lfh_id[] = {'P','K','\3','\4'}; -static const char ecdr_id[] = {'P','K','\5','\6'}; -// lengths include the id field! +static u32 cdfh_magic = FOURCC_LE('P','K','\1','\2'); +static u32 lfh_magic = FOURCC_LE('P','K','\3','\4'); +static u32 ecdr_magic = FOURCC_LE('P','K','\5','\6'); + const size_t CDFH_SIZE = 46; -const size_t LFH_SIZE = 30; +const size_t LFH_SIZE = 30; const size_t ECDR_SIZE = 22; +enum ZipCompressionMethod +{ + Z_CM_STORED = 0, // no compression + Z_CM_DEFLATE = 8 +}; + +struct LFH +{ + u32 magic; + u16 x1; // version needed + u16 flags; + u16 method; + u32 mtime; // last modified time (DOS FAT format) + u32 crc; + u32 csize; + u32 ucsize; + u16 fn_len; + u16 extra_len; +}; + + +struct CDFH +{ + u32 magic; + u32 x1; // versions + u16 flags; + u16 method; + u32 mtime; // last modified time (DOS FAT format) + u32 crc; + u32 csize; + u32 ucsize; + u16 fn_len; + u16 e_len; + u16 c_len; + u32 x2; // spanning + u32 x3; // attributes + u32 lfh_ofs; +}; + + +struct ECDR +{ + u32 magic; + u8 x1[6]; // multiple-disk support + u16 cd_entries; + u32 cd_size; + u32 cd_ofs; + u16 comment_len; +}; + + + + + + + + + + + // return false if file is obviously not a valid Zip archive, // otherwise true. used as early-out test in lookup_init (see call site). -static inline bool z_is_header(const u8* file, size_t size) +static inline bool za_is_header(const u8* file, size_t size) { // make sure it's big enough to check the header and for - // z_find_ecdr to succeed (if smaller, it's definitely bogus). + // za_find_ecdr to succeed (if smaller, it's definitely bogus). if(size < ECDR_SIZE) return false; // check "header" (first LFH) signature - return *(u32*)file == *(u32*)&lfh_id; + return ((LFH*)file)->magic == lfh_magic; } @@ -124,7 +190,7 @@ static inline bool z_is_header(const u8* file, size_t size) // the given ID (fourcc). (includes ID field) bytes must // remain before EOF - this makes sure the record is completely in the file. // used by z_find_ecdr and z_extract_cdfh. -static const u8* z_find_id(const u8* file, size_t size, const u8* start, const char id[5], size_t record_size) +static const u8* za_find_id(const u8* file, size_t size, const u8* start, u32 magic, size_t record_size) { ssize_t bytes_left = (ssize_t)((file+size) - start - record_size); @@ -135,7 +201,7 @@ static const u8* z_find_id(const u8* file, size_t size, const u8* start, const c while(bytes_left-- >= 0) { // found it - if(*(u32*)p == *(u32*)id) + if(*(u32*)p == magic) { #ifndef NDEBUG if(p != start) @@ -146,7 +212,7 @@ static const u8* z_find_id(const u8* file, size_t size, const u8* start, const c p++; // be careful not to increment before comparison; - // id may already be at . + // magic may already be found at . } // passed EOF, didn't find it. @@ -158,11 +224,11 @@ static const u8* z_find_id(const u8* file, size_t size, const u8* start, const c // find "End of Central Dir Record" in file. // z_is_header has made sure size >= ECDR_SIZE. // return -1 on failure (output param invalid), otherwise 0. -static LibError z_find_ecdr(const u8* file, size_t size, const u8*& ecdr_) +static LibError za_find_ecdr(const u8* file, size_t size, const ECDR*& ecdr_) { // early out: check expected case (ECDR at EOF; no file comment) - const u8* ecdr = file + size - ECDR_SIZE; - if(*(u32*)ecdr == *(u32*)&ecdr_id) + const ECDR* ecdr = (const ECDR*)(file + size - ECDR_SIZE); + if(ecdr->magic == ecdr_magic) goto found_ecdr; // goto scoping @@ -171,7 +237,7 @@ static LibError z_find_ecdr(const u8* file, size_t size, const u8*& ecdr_) // (the Zip archive comment field, up to 64k, may follow ECDR). // if the zip file is < 66000 bytes, scan the whole file. const u8* start = file + size - MIN(66000u, size); - ecdr = z_find_id(file, size, start, ecdr_id, ECDR_SIZE); + ecdr = (const ECDR*)za_find_id(file, size, start, ecdr_magic, ECDR_SIZE); if(!ecdr) return ERR_CORRUPTED; } @@ -187,15 +253,18 @@ found_ecdr: // /////////////////////////////////////////////////////////////////////////////// -static time_t convert_dos_date(u16 fatdate, u16 fattime) +static time_t time_t_from_FAT(u32 fat_timedate) { + const uint fat_time = bits(fat_timedate, 0, 15); + const uint fat_date = bits(fat_timedate, 15, 31); + struct tm t; // struct tm format: - t.tm_sec = bits(fattime, 0,4) * 2; // [0,59] - t.tm_min = bits(fattime, 5,10); // [0,59] - t.tm_hour = bits(fattime, 11,15); // [0,23] - t.tm_mday = bits(fatdate, 0,4); // [1,31] - t.tm_mon = bits(fatdate, 5,8)-1; // [0,11] - t.tm_year = bits(fatdate, 9,15) + 80; // since 1900 + t.tm_sec = bits(fat_time, 0,4) * 2; // [0,59] + t.tm_min = bits(fat_time, 5,10); // [0,59] + t.tm_hour = bits(fat_time, 11,15); // [0,23] + t.tm_mday = bits(fat_date, 0,4); // [1,31] + t.tm_mon = bits(fat_date, 5,8)-1; // [0,11] + t.tm_year = bits(fat_date, 9,15) + 80; // since 1900 t.tm_isdst = -1; // unknown - let libc determine debug_assert(t.tm_year < 138); @@ -208,88 +277,79 @@ static time_t convert_dos_date(u16 fatdate, u16 fattime) } +static u32 FAT_from_time_t(time_t time) +{ + struct tm* t = gmtime(&time); + + uint fat_time = 0; + fat_time |= (t->tm_sec/2); + fat_time |= (t->tm_min) << 5; + fat_time |= (t->tm_hour) << 11; + + uint fat_date = 0; + fat_date |= (t->tm_mday); + fat_date |= (t->tm_mon+1) << 5; + fat_date |= (t->tm_year-80) << 9; + + u32 fat_timedate = (fat_date << 16) | fat_time; + return fat_timedate; +} + + /////////////////////////////////////////////////////////////////////////////// -static bool z_cdfh_is_valid_file(u16 method, size_t csize, size_t ucsize, - const u8* lfh) +static bool za_cdfh_is_valid_file(u16 method, u32 csize, u32 ucsize) { - // compression method is unknown (neither deflated nor stored) - if(method & ~8) + // compression method is unknown/unsupported + if(method != Z_CM_STORED && method != Z_CM_DEFLATE) return false; - // it's a directory entry (we only want files). + // it's a directory entry (we only want files) if(!csize && !ucsize) return false; - // LFH signature not found - if(!lfh) - return false; - return true; } + enum z_extract_cdfh_ret { - Z_CDFH_ABORT = -1, // next CDFH not found; abort. Z_CDFH_FILE_OK = 0, // valid file; add to lookup. Z_CDFH_SKIPPED = 1 // not valid file, but have next CDFH; continue. }; // read the current CDFH. if a valid file, return its filename and ZLoc. -// finally, advance to next CDFH. // return -1 on error (output params invalid), or 0 on success. -// called by z_enum_files, which passes the output to lookup. -// -// [30ms] -static z_extract_cdfh_ret z_extract_cdfh(const u8* file, size_t size, // in - const u8*& cdfh, const char*& fn, size_t& fn_len, ZLoc* loc) // out +// called by za_enum_files, which passes the output to lookup. +static z_extract_cdfh_ret za_extract_cdfh(const CDFH* cdfh, + const char*& fn, size_t& fn_len, ZLoc* loc, size_t& ofs_to_next_cdfh) { - // scan for next CDFH (at or beyond current cdfh position) - cdfh = z_find_id(file, size, cdfh, cdfh_id, CDFH_SIZE); - if(!cdfh) // no (further) CDFH found: - return Z_CDFH_ABORT; // caller will abort. - // extract fields from CDFH - const u16 method = read_le16(cdfh+10); - const u16 fattime = read_le16(cdfh+12); - const u16 fatdate = read_le16(cdfh+14); - const u32 csize = read_le32(cdfh+20); - const u32 ucsize = read_le32(cdfh+24); - const u16 fn_len_ = read_le16(cdfh+28); - const u16 e_len = read_le16(cdfh+30); - const u16 c_len = read_le16(cdfh+32); - const u32 lfh_ofs = read_le32(cdfh+42); + const u16 method = read_le16(&cdfh->method); + const u32 fat_mtime = read_le32(&cdfh->mtime); + const u32 csize = read_le32(&cdfh->csize); + const u32 ucsize = read_le32(&cdfh->ucsize); + const u16 fn_len_ = read_le16(&cdfh->fn_len); + const u16 e_len = read_le16(&cdfh->e_len); + const u16 c_len = read_le16(&cdfh->c_len); + const u32 lfh_ofs = read_le32(&cdfh->lfh_ofs); const char* fn_ = (const char*)cdfh+CDFH_SIZE; // not 0-terminated! - // find corresponding LFH, needed to calculate file offset - // (its extra field may not match that reported by CDFH!). - // TODO: this is slow, due to seeking backwards. - // optimization: calculate only on demand (i.e. open, not mount)? - const u8* lfh = z_find_id(file, size, (u8*)file+lfh_ofs, lfh_id, LFH_SIZE); + // return offset to where next CDFH should be (caller will scan for it) + ofs_to_next_cdfh = CDFH_SIZE + fn_len_ + e_len + c_len; - // advance CDFH; we now know where the next CDFH entry should be, - // but will still scan ahead for its id on next call. - cdfh += CDFH_SIZE + fn_len_ + e_len + c_len; - - if(!z_cdfh_is_valid_file(method, csize, ucsize, lfh)) + if(!za_cdfh_is_valid_file(method, csize, ucsize)) return Z_CDFH_SKIPPED; - // get actual file ofs (see above) - const u16 lfh_fn_len = read_le16(lfh+26); - const u16 lfh_e_len = read_le16(lfh+28); - const off_t file_ofs = lfh_ofs + LFH_SIZE + lfh_fn_len + lfh_e_len; - // LFH doesn't have a comment field! - // write out entry data fn = fn_; fn_len = fn_len_; - loc->ofs = file_ofs; - loc->csize = (off_t)(method? csize : 0); - // if not compressed, csize = 0 (see zfile_compressed) + loc->ofs = lfh_ofs | LFH_FIXUP; + loc->csize = (method != Z_CM_STORED)? csize : 0; loc->ucsize = (off_t)ucsize; - loc->mtime = convert_dos_date(fatdate, fattime); + loc->mtime = time_t_from_FAT(fat_mtime); return Z_CDFH_FILE_OK; } @@ -303,26 +363,26 @@ static z_extract_cdfh_ret z_extract_cdfh(const u8* file, size_t size, // in // HACK: call back with negative index the first time; its abs. value is // the number of entries in the archive. lookup needs to know this so it can // preallocate memory. having lookup_init call z_get_num_files and then -// z_enum_files would require passing around a ZipInfo struct, or searching +// za_enum_files would require passing around a ZipInfo struct, or searching // for the ECDR twice - both ways aren't nice. nor is expanding on demand - // we try to minimize allocations (faster, less fragmentation). -// + // fn (filename) is not necessarily 0-terminated! // loc is only valid during the callback! must be copied or saved. typedef LibError (*CDFH_CB)(uintptr_t user, i32 idx, const char* fn, size_t fn_len, const ZLoc* loc); -static LibError z_enum_files(const u8* file, const size_t size, const CDFH_CB cb, const uintptr_t user) +static LibError za_enum_files(const u8* file, const size_t size, const CDFH_CB cb, const uintptr_t user) { // find "End of Central Directory Record" - const u8* ecdr; - CHECK_ERR(z_find_ecdr(file, size, ecdr)); + const ECDR* ecdr; + CHECK_ERR(za_find_ecdr(file, size, ecdr)); // call back with number of entries in archives (an upper bound // for valid files; we're not interested in the directory entries). // we'd have to scan through the central dir to count them out; we'll // just skip them and waste a bit of preallocated memory. - const i32 num_entries = read_le16(ecdr+10); + const i32 num_entries = read_le16(&ecdr->cd_entries); // .. callback expects -num_entries < 0. // if it's 0, the callback would treat it as an index => crash. // ERR_FAIL means we'll no longer be called. @@ -331,8 +391,8 @@ static LibError z_enum_files(const u8* file, const size_t size, const CDFH_CB cb CHECK_ERR(cb(user, -num_entries, 0, 0, 0)); // iterate through CDFH - const u32 cd_ofs = read_le32(ecdr+16); - const u8* cdfh = (const u8*)file + cd_ofs; + const u32 cd_ofs = read_le32(&ecdr->cd_ofs); + const CDFH* cdfh = (const CDFH*)(file + cd_ofs); i32 idx = 0; // only incremented when valid, so we don't leave holes // in lookup's arrays (bad locality). @@ -340,31 +400,180 @@ static LibError z_enum_files(const u8* file, const size_t size, const CDFH_CB cb for(i32 i = 0; i < num_entries; i++) { + // scan for next CDFH (at or beyond current cdfh position) + cdfh = (CDFH*)za_find_id(file, size, (const u8*)cdfh, cdfh_magic, CDFH_SIZE); + if(!cdfh) // no (further) CDFH found: + return ERR_CORRUPTED; // abort. + const char* fn; size_t fn_len; ZLoc loc; + size_t ofs_to_next_cdfh; - z_extract_cdfh_ret ret = z_extract_cdfh(file, size, cdfh, fn, fn_len, &loc); + z_extract_cdfh_ret ret = za_extract_cdfh(cdfh, fn, fn_len, &loc, ofs_to_next_cdfh); // valid file if(ret == Z_CDFH_FILE_OK) { - LibError cb_ret = cb(user, idx, fn, fn_len, &loc); + LibError cb_ret = cb(user, i, fn, fn_len, &loc); if(cb_ret != INFO_CB_CONTINUE) return cb_ret; idx++; // see rationale above } - // next CDFH not found (Zip archive corrupted) - else if(ret == Z_CDFH_ABORT) - return ERR_CORRUPTED; - // skipping this CDFH (e.g. if directory) - else - ; + // else: skipping this CDFH (e.g. if directory) + + cdfh = (const CDFH*)((u8*)cdfh + ofs_to_next_cdfh); } return ERR_OK; } +static void fixup() +{ +/* + // find corresponding LFH, needed to calculate file offset + // (its extra field may not match that reported by CDFH!). + // TODO: this is slow, due to seeking backwards. + // optimization: calculate only on demand (i.e. open, not mount)? + const u8* lfh = za_find_id(file, size, (u8*)file+lfh_ofs, lfh_magic, LFH_SIZE); + + // get actual file ofs (see above) + const u16 lfh_fn_len = read_le16(lfh+26); + const u16 lfh_e_len = read_le16(lfh+28); + const off_t file_ofs = lfh_ofs + LFH_SIZE + lfh_fn_len + lfh_e_len; + // LFH doesn't have a comment field! +*/ +} + + +struct ZipArchive +{ + File f; + off_t cur_file_size; + + Pool cdfhs; + uint cd_entries; +}; + +struct ZipEntry +{ + char path[PATH_MAX]; + size_t ucsize; + time_t mtime; + ZipCompressionMethod method; + size_t csize; + void* cdata; +}; + +LibError zip_archive_create(const char* zip_filename, ZipArchive* za) +{ + memset(za, 0, sizeof(*za)); + RETURN_ERR(file_open(zip_filename, 0, &za->f)); + RETURN_ERR(pool_create(&za->cdfhs, 10*MiB, 0)); + return ERR_OK; +} + + +static inline u32 u32_from_size_t(size_t x) +{ + debug_assert(x <= 0xFFFFFFFF); + return (u32)(x & 0xFFFFFFFF); +} + +static inline u16 u16_from_size_t(size_t x) +{ + debug_assert(x <= 0xFFFF); + return (u16)(x & 0xFFFF); +} + + +LibError zip_archive_add(ZipArchive* za, const ZipEntry* ze) +{ + const char* fn = ze->path; + const size_t fn_len = strlen(fn); + const size_t ucsize = ze->ucsize; + const u32 fat_mtime = FAT_from_time_t(ze->mtime); + const u16 method = (u16)ze->method; + const size_t csize = ze->csize; + void* cdata = ze->cdata; + + const off_t lfh_ofs = za->cur_file_size; + + // write (LFH, filename, file contents) to archive + const size_t lfh_size = sizeof( LFH); + const LFH lfh = + { + lfh_magic, + 0, // x1 + 0, // flags + method, + fat_mtime, + 0, // crc + u32_from_size_t(csize), + u32_from_size_t(ucsize), + u16_from_size_t(fn_len), + 0 // e_len + }; + file_io(&za->f, lfh_ofs, lfh_size, (void*)&lfh); + file_io(&za->f, lfh_ofs+lfh_size, fn_len, (void*)fn); + file_io(&za->f, lfh_ofs+(off_t)(lfh_size+fn_len), csize, (void*)cdata); + za->cur_file_size += (off_t)(lfh_size+fn_len+csize); + + // append a CDFH to the central dir (in memory) + const size_t cdfh_size = sizeof(CDFH); + CDFH* cdfh = (CDFH*)pool_alloc(&za->cdfhs, cdfh_size+fn_len); + if(cdfh) + { + cdfh->magic = cdfh_magic; + cdfh->x1 = 0; + cdfh->flags = 0; + cdfh->method = method; + cdfh->mtime = fat_mtime; + cdfh->crc = 0; + cdfh->csize = u32_from_size_t(csize); + cdfh->ucsize = u32_from_size_t(ucsize); + cdfh->fn_len = u16_from_size_t(fn_len); + cdfh->e_len = 0; + cdfh->c_len = 0; + cdfh->x2 = 0; + cdfh->x3 = 0; + cdfh->lfh_ofs = lfh_ofs; + memcpy2((char*)cdfh+cdfh_size, fn, fn_len); + + za->cd_entries++; + } + + return ERR_OK; +} + + +LibError zip_archive_finish(ZipArchive* za) +{ + const size_t cd_size = za->cdfhs.da.pos; + + // append an ECDR to the CDFH list (this allows us to + // write out both to the archive file in one burst) + ECDR* ecdr = (ECDR*)pool_alloc(&za->cdfhs, sizeof(ECDR)); + if(!ecdr) + return ERR_NO_MEM; + ecdr->magic = ecdr_magic; + memset(ecdr->x1, 0, sizeof(ecdr->x1)); + ecdr->cd_entries = za->cd_entries; + ecdr->cd_size = (u32)cd_size; + ecdr->cd_ofs = za->cur_file_size; + ecdr->comment_len = 0; + + file_io(&za->f, za->cur_file_size, za->cdfhs.da.pos, za->cdfhs.da.base); + + (void)file_close(&za->f); + (void)pool_destroy(&za->cdfhs); + return ERR_OK; +} + + + + + /////////////////////////////////////////////////////////////////////////////// // // lookup_*: file lookup @@ -373,244 +582,15 @@ static LibError z_enum_files(const u8* file, const size_t size, const CDFH_CB cb /////////////////////////////////////////////////////////////////////////////// -// current file-lookup implementation: -// store each file's ZEnt in an array. check the next entry first; if that's -// not what we're looking for, find its index via map. -// -// rationale: -// - we don't export a "key" (currently array index) that would allow faster -// file lookup. this would only be useful if higher-level code were to -// store the key and use it more than once. also, lookup is currently fast -// enough. finally, this would also make our file enumerate callback -// incompatible with the others (due to the extra key param). -// -// - we don't bother with a directory tree to speed up lookup. the above -// is fast enough: O(1) if accessed sequentially, otherwise O(log(files)). - - -struct ZEnt -{ - const char* fn; // currently allocated individually - - ZLoc loc; -}; - - -typedef std::map LookupIdx; -typedef LookupIdx::iterator LookupIdxIt; - -// per-archive information for mapping filename -> file info -struct LookupInfo -{ - ZEnt* ents; - FnHash* fn_hashes; - // split out of ZEnt for more efficient search - // (=> ZEnt is power-of-2, back-to-back fn_hashes) - // - // currently both share one memory allocation; only mem_free() ents! - - i32 num_entries; - // .. in above arrays (used to check indices) - - i32 num_files; - // actual number of valid files! (see z_enum_files) - - i32 next_file; - // for last-file-opened optimization. - // we store index of next file instead of the last one opened - // to avoid trouble on first call (don't want last == -1). - - // don't know size of std::map, and this struct is - // included in a control block (ZArchive). - // allocate dynamically to be safe. - LookupIdx* idx; -}; - - -// add file to the lookup data structure. -// called from z_enum_files in order (0 <= idx < num_entries). -// the first call notifies us of # entries, so we can allocate memory. -// -// notes: -// - fn (filename) is not necessarily 0-terminated! -// - loc is only valid during the callback! must be copied or saved. -// -// [40ms] -static LibError lookup_add_file_cb(uintptr_t user, i32 idx, - const char* fn, size_t fn_len, const ZLoc* loc) -{ - LookupInfo* li = (LookupInfo*)user; - - // HACK: on first call, idx is negative and tells us how many - // entries are in the archive (so we can allocate memory). - // see z_enum_files for why it's done this way. - if(idx < 0) - { - const i32 num_entries = -idx; - - // both arrays in one allocation (more efficient) - const size_t ents_size = (num_entries * sizeof(ZEnt)); - const size_t array_size = ents_size + (num_entries * sizeof(FnHash)); - void* p = mem_alloc(array_size, 4*KiB); - if(!p) - return ERR_NO_MEM; - - li->num_entries = num_entries; - li->num_files = 0; - // will count below, since some entries aren't files. - li->ents = (ZEnt*)p; - li->fn_hashes = (FnHash*)((char*)p + ents_size); - return INFO_CB_CONTINUE; - } - - // adding a regular file. - - debug_assert(idx < li->num_entries); - FnHash fn_hash = fnv_lc_hash(fn, fn_len); - - // fill ZEnt - ZEnt* ent = li->ents + idx; - ent->loc = *loc; - // .. copy filename (needs to be 0-terminated) - // note: Zip paths only have '/' terminators; no need to convert. - char* fn_copy = (char*)malloc(fn_len+1); - if(!fn_copy) - return ERR_NO_MEM; - memcpy2(fn_copy, fn, fn_len); - fn_copy[fn_len] = '\0'; - ent->fn = fn_copy; - - li->num_files++; - li->fn_hashes[idx] = fn_hash; - (*li->idx)[fn_hash] = idx; - - return INFO_CB_CONTINUE; -} - - -// initialize lookup data structure for the given Zip archive: -// adds all files to the index. -static LibError lookup_init(LookupInfo* li, const u8* file, const size_t size) -{ - LibError err; - - // check if it's even a Zip file. - // the VFS blindly opens files when mounting; it needs to open - // all archives, but doesn't know their extension (e.g. ".pk3"). - if(!z_is_header(file, size)) - return ERR_UNKNOWN_FORMAT; - - li->next_file = 0; - li->idx = new LookupIdx; - // ents, fn_hashes, num_files are initialized in lookup_add_file_cb - - err = z_enum_files(file, size, lookup_add_file_cb, (uintptr_t)li); - if(err < 0) - { - delete li->idx; - return err; - } - - return ERR_OK; -} - - -static LibError lookup_validate(const LookupInfo* li) -{ - if(debug_is_pointer_bogus(li->ents) || debug_is_pointer_bogus(li->fn_hashes)) - return ERR_1; - if(li->num_files > li->num_entries || li->next_file > li->num_entries) - return ERR_2; - if(li->num_entries < 0 || li->num_files < 0 || li->next_file < 0) - return ERR_3; - if(debug_is_pointer_bogus(li->idx)) - return ERR_4; - return ERR_OK; -} - - -// free lookup data structure. -// (no use-after-free checking - that's handled by the VFS) -static void lookup_free(LookupInfo* li) -{ - // free memory allocated for filenames - for(i32 i = 0; i < li->num_files; i++) - { - free((void*)li->ents[i].fn); - li->ents[i].fn = 0; - } - - li->num_files = 0; - - delete li->idx; - - // frees both ents and fn_hashes! (they share an allocation) - (void)mem_free(li->ents); -} - - -// look up ZLoc, given filename (untrusted!). -static LibError lookup_get_file_info(LookupInfo* li, const char* fn, ZLoc* loc) -{ - const FnHash fn_hash = fnv_lc_hash(fn); - - const FnHash* fn_hashes = li->fn_hashes; - const i32 num_files = li->num_files; - i32 i = li->next_file; - - // early-out: check if the next entry is what we want - if(i < num_files && fn_hashes[i] == fn_hash) - goto have_idx; - - // .. no - consult index - { - LookupIdxIt it = li->idx->find(fn_hash); - // not found: error - if(it == li->idx->end()) - return ERR_FILE_NOT_FOUND; - - i = it->second; - debug_assert(0 <= i && i < li->num_files); - } - -have_idx: - - // indicate that this is the most recent entry touched - li->next_file = i+1; - - *loc = li->ents[i].loc; - return ERR_OK; -} - - -// successively call for each valid file in the index, -// passing the complete path and . -// if it returns a nonzero value, abort and return that, otherwise 0. -static LibError lookup_enum_files(LookupInfo* li, FileCB cb, uintptr_t user) -{ - struct stat s; - memset(&s, 0, sizeof(s)); - - const ZEnt* ent = li->ents; - for(i32 i = 0; i < li->num_files; i++, ent++) - { - s.st_mode = S_IFREG; - s.st_size = (off_t)ent->loc.ucsize; - s.st_mtime = ent->loc.mtime; - - LibError ret = cb(ent->fn, &s, user); - if(ret != INFO_CB_CONTINUE) - return ret; - } - - return ERR_OK; -} - - -static uint lookup_get_num_files(const LookupInfo* li) -{ - return li->num_files; -} + // rationale: + // - we don't export a "key" (currently array index) that would allow faster + // file lookup. this would only be useful if higher-level code were to + // store the key and use it more than once. also, lookup is currently fast + // enough. finally, this would also make our file enumerate callback + // incompatible with the others (due to the extra key param). + // + // - we don't bother with a directory tree to speed up lookup. the above + // is fast enough: O(1) if accessed sequentially, otherwise O(log(files)). /////////////////////////////////////////////////////////////////////////////// @@ -625,7 +605,11 @@ struct ZArchive { File f; - LookupInfo li; + ZLoc* ents; + // number of valid entries in above array (see lookup_add_file_cb) + i32 num_files; + + Bucket fn_storage; // note: we need to keep track of what resources reload() allocated, // so the dtor can free everything correctly. @@ -637,6 +621,77 @@ struct ZArchive H_TYPE_DEFINE(ZArchive); + + +// look up ZLoc, given filename (untrusted!). +static LibError archive_get_file_info(ZArchive* za, const char* fn, uintptr_t memento, ZLoc*& loc) +{ + if(memento) + { + loc = (ZLoc*)memento; + return ERR_OK; + } + else + { + for(i32 i = 0; i < za->num_files; i++) + if(!strcmp(za->ents[i].fn, fn)) + { + loc = &za->ents[i]; + return ERR_OK; + } + } + + return ERR_FILE_NOT_FOUND; +} + + + +// add file to the lookup data structure. +// called from za_enum_files in order (0 <= idx < num_entries). +// the first call notifies us of # entries, so we can allocate memory. +// +// notes: +// - fn (filename) is not necessarily 0-terminated! +// - loc is only valid during the callback! must be copied or saved. +static LibError archive_add_file_cb(uintptr_t user, i32 i, + const char* fn, size_t fn_len, const ZLoc* loc) +{ + ZArchive* za = (ZArchive*)user; + + // HACK: on first call, i is negative and tells us how many + // entries are in the archive (so we can allocate memory). + // see za_enum_files for why it's done this way. + if(i < 0) + { + const i32 num_entries = -i; + + za->ents = (ZLoc*)mem_alloc(num_entries * sizeof(ZLoc), 32); + if(!za->ents) + return ERR_NO_MEM; + return INFO_CB_CONTINUE; + } + + // adding a regular file. + + ZLoc* ent = &za->ents[i]; + *ent = *loc; + // .. copy filename (needs to be 0-terminated) + // note: Zip paths only have '/' terminators; no need to convert. + char* fn_copy = (char*)bucket_alloc(&za->fn_storage, fn_len+1); + if(!fn_copy) + return ERR_NO_MEM; + memcpy2(fn_copy, fn, fn_len); + fn_copy[fn_len] = '\0'; + ent->fn = fn_copy; + + za->num_files++; + return INFO_CB_CONTINUE; +} + + + + + static void ZArchive_init(ZArchive*, va_list) { } @@ -645,7 +700,9 @@ static void ZArchive_dtor(ZArchive* za) { if(za->is_loaded) { - lookup_free(&za->li); + (void)mem_free(za->ents); + bucket_free_all(&za->fn_storage); + za->is_loaded = 0; } if(za->is_mapped) @@ -667,12 +724,19 @@ static LibError ZArchive_reload(ZArchive* za, const char* fn, Handle) RETURN_ERR(file_open(fn, FILE_CACHE_BLOCK, &za->f)); za->is_open = 1; - void* file; size_t size; - RETURN_ERR(file_map(&za->f, file, size)); + void* file_; size_t size; + RETURN_ERR(file_map(&za->f, file_, size)); + const u8* file = (const u8*)file_; za->is_mapped = 1; - RETURN_ERR(lookup_init(&za->li, (u8*)file, size)); + // check if it's even a Zip file. + // the VFS blindly opens files when mounting; it needs to open + // all archives, but doesn't know their extension (e.g. ".pk3"). + if(!za_is_header(file, size)) + return ERR_UNKNOWN_FORMAT; + za->is_loaded = 1; + RETURN_ERR(za_enum_files(file, size, archive_add_file_cb, (uintptr_t)za)); // we map the file only for convenience when loading; // extraction is via aio (faster, better mem use). @@ -685,13 +749,18 @@ static LibError ZArchive_reload(ZArchive* za, const char* fn, Handle) static LibError ZArchive_validate(const ZArchive* za) { RETURN_ERR(file_validate(&za->f)); - RETURN_ERR(lookup_validate(&za->li)); + + if(debug_is_pointer_bogus(za->ents)) + return ERR_1; + if(za->num_files < 0) + return ERR_2; + return ERR_OK; } static LibError ZArchive_to_string(const ZArchive* za, char* buf) { - snprintf(buf, H_STRING_LEN, "(%d files)", lookup_get_num_files(&za->li)); + snprintf(buf, H_STRING_LEN, "(%d files)", za->num_files); return ERR_OK; } @@ -720,10 +789,29 @@ LibError zip_enum(const Handle ha, const FileCB cb, const uintptr_t user) { H_DEREF(ha, ZArchive, za); - return lookup_enum_files(&za->li, cb, user); + struct stat s; + memset(&s, 0, sizeof(s)); + + for(i32 i = 0; i < za->num_files; i++) + { + const ZLoc* ent = &za->ents[i]; + + s.st_mode = S_IFREG; + s.st_size = (off_t)ent->ucsize; + s.st_mtime = ent->mtime; + + LibError ret = cb(ent->fn, &s, user); + if(ret != INFO_CB_CONTINUE) + return ret; + } + + return ERR_OK; } + + + /////////////////////////////////////////////////////////////////////////////// // // inf_*: in-memory inflate routines (zlib wrapper) @@ -751,69 +839,231 @@ static LibError LibError_from_zlib(int err) UNREACHABLE; } +enum ZLibContextType +{ + COMPRESSION, + DECOMPRESSION +}; + +enum DecompressMode +{ + DM_ZLIB, + DM_MEMCPY +}; // must be dynamically allocated - need one for every open ZFile, // and z_stream is large. -struct InfCtx +struct ZLibContext { z_stream zs; - void* in_buf; - // 0 until inf_inflate called with free_in_buf = true. - // mem_free-d after consumed by inf_inflate, or by inf_free. - // note: necessary; can't just use next_in-total_in, because - // we may inflate in chunks. - // - // can't have this owned (i.e. allocated) by inf_, because - // there can be several IOs in-flight and therefore buffers of - // compressed data. we'd need a list if stored here; having the - // IOs store them and pass them to us is more convenient. + ZLibContextType type; - bool compressed; + DecompressMode mode; + + // 0 until zlib_feed_decompressor called with free_in_buf = true. + // mem_free-d after consumed by zlib_feed_decompressor, or by inf_free. + // note: necessary; can't just use next_in-total_in, because + // we may inflate in chunks. + // + // can't have this owned (i.e. allocated) by inf_, because + // there can be several IOs in-flight and therefore buffers of + // compressed data. we'd need a list if stored here; having the + // IOs store them and pass them to us is more convenient. + void* in_buf; }; -// allocate a new context. -static uintptr_t inf_init_ctx(bool compressed) -{ -#ifdef NO_ZLIB - return 0; -#else - // allocate ZLib stream - const size_t size = round_up(sizeof(InfCtx), 32); - // be nice to allocator - InfCtx* ctx = (InfCtx*)calloc(1, size); - if(inflateInit2(&ctx->zs, -MAX_WBITS) != Z_OK) - // -MAX_WBITS indicates no zlib header present - return 0; - ctx->compressed = compressed; - - return (uintptr_t)ctx; -#endif -} +static ZLibContext single_ctx; +static uintptr_t single_ctx_in_use; -// convenience - both inf_inflate and inf_free use this. -static void free_in_buf(InfCtx* ctx) +// convenience - both zlib_feed_decompressor and inf_free use this. +static void free_in_buf(ZLibContext* ctx) { mem_free(ctx->in_buf); ctx->in_buf = 0; } -// subsequent calls to inf_inflate will unzip into . -LibError inf_set_dest(uintptr_t _ctx, void* out, size_t out_size) +static uintptr_t zlib_create_ctx(ZLibContextType type) { #ifdef NO_ZLIB - return -1; + return 0; #else - InfCtx* ctx = (InfCtx*)_ctx; + ZLibContext* ctx = (ZLibContext*)single_calloc(&single_ctx, &single_ctx_in_use, sizeof(single_ctx)); + if(!ctx) + return 0; + + ctx->type = type; + z_stream* zs = &ctx->zs; + zs->next_in = 0; + zs->zalloc = 0; + zs->zfree = 0; + zs->opaque = 0; + + const int windowBits = -MAX_WBITS; // max window size; omit ZLib header + int err; + + if(type == COMPRESSION) + { + const int level = Z_BEST_COMPRESSION; + const int memLevel = 8; // default; total mem ~= 256KiB + const int strategy = Z_DEFAULT_STRATEGY; // normal data - not RLE + err = deflateInit2(&ctx->zs, level, Z_DEFLATED, windowBits, memLevel, strategy); + } + else + { + err = inflateInit2(zs, windowBits); + } + + if(err != Z_OK) + { + debug_warn("failed"); + single_free(&single_ctx, &single_ctx_in_use, ctx); + return 0; + } + + return (uintptr_t)ctx; +#endif +} + + +static void zlib_destroy_ctx(uintptr_t zlib_ctx) +{ +#ifdef NO_ZLIB + return ERR_NOT_IMPLEMENTED; +#else + ZLibContext* ctx = (ZLibContext*)zlib_ctx; + z_stream* zs = &ctx->zs; + int err; + + if(ctx->type == COMPRESSION) + { + err = deflateEnd(zs); + } + else + { + free_in_buf(ctx); + + // can have both input or output data remaining + // (if not all data in uncompressed stream was needed) + + err = inflateEnd(zs); + } + + if(err != Z_OK) + debug_warn("in/deflateEnd reports error"); + + single_free(&single_ctx, &single_ctx_in_use, ctx); +#endif +} + + +//----------------------------------------------------------------------------- + +static LibError zlib_prepare_compress(uintptr_t zlib_ctx, size_t total_ucsize) +{ +#ifdef NO_ZLIB + return ERR_NOT_IMPLEMENTED; +#else + ZLibContext* ctx = (ZLibContext*)zlib_ctx; + z_stream* zs = &ctx->zs; + int err; + + err = deflateReset(zs); + debug_assert(err == Z_OK); + + size_t max_csize = (size_t)deflateBound(zs, (uLong)total_ucsize); + void* cdata = mem_alloc(max_csize, 32*KiB); + if(!cdata) + return ERR_NO_MEM; + + zs->next_out = (Byte*)cdata; + zs->avail_out = (uInt)max_csize; + + return ERR_OK; +#endif +} + + +static LibError zlib_feed_compressor(uintptr_t zlib_ctx, void* in, size_t in_size) +{ +#ifdef NO_ZLIB + return ERR_NOT_IMPLEMENTED; +#else + ZLibContext* ctx = (ZLibContext*)zlib_ctx; + z_stream* zs = &ctx->zs; + + // since output buffer is guaranteed to be big enough, + // no input data should 'survive' the deflate call. + if(zs->avail_in) + debug_warn("previous input buffer remains"); + + zs->avail_in = (uInt)in_size; + zs->next_in = (Byte*)in; + + const size_t prev_avail_out = zs->avail_out; + int err = deflate(zs, 0); + const size_t avail_out = zs->avail_out; + + // check how many bytes were output. + // + // note: zlib may not always output data, e.g. if passed very little + // data in one block due to misalignment. in that case, return 0 + // ("no data output"), which doesn't cause caller to abort. + debug_assert(avail_out <= prev_avail_out); + const ssize_t nread = (ssize_t)(prev_avail_out - avail_out); + if(!nread && err != Z_OK) + return ERR_FAIL; +// TODO: return zlib error + return ERR_OK; +#endif +} + + +static LibError zlib_finish_compress(uintptr_t zlib_ctx, void** cdata, size_t* csize) +{ +#ifdef NO_ZLIB + return ERR_NOT_IMPLEMENTED; +#else + ZLibContext* ctx = (ZLibContext*)zlib_ctx; + z_stream* zs = &ctx->zs; + int err; + + // notify zlib that no more data is forthcoming and have it flush output. + // our output buffer has enough space due to use of deflateBound; + // therefore, deflate must return Z_STREAM_END. + err = deflate(zs, Z_FINISH); + if(err != Z_STREAM_END) + debug_warn("deflate: unexpected Z_FINISH behavior"); + + *cdata = zs->next_out - zs->total_out; + *csize = zs->total_out; + return ERR_OK; +#endif +} + + +//----------------------------------------------------------------------------- + + +// subsequent calls to zlib_feed_decompressor will unzip into . +static LibError zlib_prepare_decompress(uintptr_t zlib_ctx, DecompressMode mode, void* out, size_t out_size) +{ +#ifdef NO_ZLIB + return ERR_NOT_IMPLEMENTED; +#else + ZLibContext* ctx = (ZLibContext*)zlib_ctx; + z_stream* zs = &ctx->zs; + + ctx->mode = mode; if(zs->next_out || zs->avail_out) { debug_warn("ctx already in use!"); - return ERR_FAIL; + return ERR_LOGIC; } zs->next_out = (Byte*)out; zs->avail_out = (uInt)out_size; @@ -827,13 +1077,13 @@ TIMER_ADD_CLIENT(tc_zip_memcpy); // unzip into output buffer. returns bytes written // (may be 0, if not enough data is passed in), or < 0 on error. -ssize_t inf_inflate(uintptr_t _ctx, void* in, size_t in_size, bool free_in_buf = false) +static ssize_t zlib_feed_decompressor(uintptr_t _ctx, void* in, size_t in_size, bool free_in_buf = false) { #ifdef NO_ZLIB - return -1; + return ERR_NOT_IMPLEMENTED; #else - InfCtx* ctx = (InfCtx*)_ctx; + ZLibContext* ctx = (ZLibContext*)_ctx; z_stream* zs = &ctx->zs; @@ -852,7 +1102,7 @@ ssize_t inf_inflate(uintptr_t _ctx, void* in, size_t in_size, bool free_in_buf = LibError err = ERR_OK; - if(ctx->compressed) + if(ctx->mode == DM_ZLIB) { TIMER_ACCRUE(tc_zip_inflate); int ret = inflate(zs, Z_SYNC_FLUSH); @@ -886,39 +1136,116 @@ ssize_t inf_inflate(uintptr_t _ctx, void* in, size_t in_size, bool free_in_buf = size_t avail_out = zs->avail_out; debug_assert(avail_out <= prev_avail_out); - // make sure output buffer size didn't magically increase + // make sure output buffer size didn't magically increase ssize_t nread = (ssize_t)(prev_avail_out - avail_out); if(!nread) return (err < 0)? err : 0; - // try to pass along the ZLib error code, but make sure - // it isn't treated as 'bytes output', i.e. > 0. + // try to pass along the ZLib error code, but make sure + // it isn't treated as 'bytes output', i.e. > 0. return nread; #endif } -// free the given context. -LibError inf_free_ctx(uintptr_t _ctx) + + +//----------------------------------------------------------------------------- + + + + + +//----------------------------------------------------------------------------- +// archive builder +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +static LibError trace_get_next_file(void* trace, uint i, const char* path) { -#ifdef NO_ZLIB - return -1; -#else - InfCtx* ctx = (InfCtx*)_ctx; - z_stream* zs = &ctx->zs; - - free_in_buf(ctx); - - // can have both input or output data remaining - // (if not all data in uncompressed stream was needed) - - inflateEnd(zs); - free(ctx); - return ERR_OK; -#endif + return ERR_DIR_END; } +static ssize_t compress_cb(uintptr_t ctx, void* buf, size_t buf_size) +{ + uintptr_t zlib_ctx = ctx; + + (void)zlib_feed_compressor(zlib_ctx, buf, buf_size); +// TODO: echo into second buffer, in case compression isnt working out + return (ssize_t)buf_size; +} + +static LibError read_and_compress_file(uintptr_t zlib_ctx, ZipEntry* ze) +{ + const char* fn = ze->path; + +// TODO: decide if compressible + + struct stat s; + RETURN_ERR(file_stat(fn, &s)); + const size_t ucsize = s.st_size; + + RETURN_ERR(zlib_prepare_compress(zlib_ctx, ucsize)); + + File f; + RETURN_ERR(file_open(fn, 0, &f)); + ssize_t ucbytes_read = file_io(&f, 0, ucsize, 0, compress_cb, zlib_ctx); + UNUSED2(ucbytes_read); + (void)file_close(&f); + + void* cdata; size_t csize; + (void)zlib_finish_compress(zlib_ctx, &cdata, &csize); + + ze->ucsize = ucsize; + ze->mtime = s.st_mtime; + ze->method = Z_CM_DEFLATE; + ze->csize = csize; + ze->cdata = cdata; + + return ERR_OK; +} + +static void build_optimized_archive(const char* zip_filename, void* trace) +{ + ZipArchive za; + zip_archive_create(zip_filename, &za); + + uintptr_t zlib_ctx = zlib_create_ctx(COMPRESSION); + uint trace_i = 0; + uint queued_files = 0, committed_files = 0; + + for(;;) + { + ZipEntry ze; // TODO: QUEUE + const int max_readqueue_depth = 1; + for(uint i = 0; i < max_readqueue_depth; i++) + { + LibError ret = trace_get_next_file(trace, trace_i, ze.path); + if(ret == ERR_DIR_END) + break; + + WARN_ERR(read_and_compress_file(zlib_ctx, &ze)); + queued_files++; + } + + if(committed_files == queued_files) + break; + zip_archive_add(&za, &ze); + committed_files++; + } + + + zlib_destroy_ctx(zlib_ctx); + + + zip_archive_finish(&za); +} + + + + + /////////////////////////////////////////////////////////////////////////////// // // zip_*: file from Zip archive @@ -945,13 +1272,12 @@ LibError zip_stat(Handle ha, const char* fn, struct stat* s) memset(s, 0, sizeof(struct stat)); H_DEREF(ha, ZArchive, za); - LookupInfo* li = &za->li; - ZLoc loc; - CHECK_ERR(lookup_get_file_info(li, fn, &loc)); + ZLoc* loc; + CHECK_ERR(archive_get_file_info(za, fn, 0, loc)); - s->st_size = loc.ucsize; - s->st_mtime = loc.mtime; + s->st_size = loc->ucsize; + s->st_mtime = loc->mtime; return ERR_OK; } @@ -980,23 +1306,22 @@ LibError zip_validate(const ZFile* zf) LibError zip_open(const Handle ha, const char* fn, int flags, ZFile* zf) { // zero output param in case we fail below. - memset(zf, 0, sizeof(ZFile)); + memset(zf, 0, sizeof(*zf)); H_DEREF(ha, ZArchive, za); - LookupInfo* li = (LookupInfo*)&za->li; - ZLoc loc; - // don't want ZFile to contain a ZEnt struct - + ZLoc* loc; + // don't want ZFile to contain a ZLoc struct - // its ucsize member must be 'loose' for compatibility with File. // => need to copy ZLoc fields into ZFile. - RETURN_ERR(lookup_get_file_info(li, fn, &loc)); + RETURN_ERR(archive_get_file_info(za, fn, 0, loc)); zf->flags = flags; - zf->ucsize = loc.ucsize; - zf->ofs = loc.ofs; - zf->csize = loc.csize; + zf->ucsize = loc->ucsize; + zf->ofs = loc->ofs; + zf->csize = loc->csize; zf->ha = ha; - zf->inf_ctx = inf_init_ctx(zfile_compressed(zf)); + zf->inf_ctx = 0; zf->is_mapped = 0; CHECK_ZFILE(zf); return ERR_OK; @@ -1008,7 +1333,8 @@ LibError zip_close(ZFile* zf) { CHECK_ZFILE(zf); // other ZFile fields don't need to be freed/cleared - return inf_free_ctx(zf->inf_ctx); + zlib_destroy_ctx(zf->inf_ctx); + return ERR_OK; } @@ -1048,7 +1374,7 @@ LibError zip_io_issue(ZFile* zf, off_t user_ofs, size_t max_output_size, void* u void* buf = user_buf; const off_t ofs = zf->ofs + zf->last_read_ofs; - // needed before align check below + // needed before align check below if(zfile_compressed(zf)) { @@ -1059,8 +1385,8 @@ LibError zip_io_issue(ZFile* zf, off_t user_ofs, size_t max_output_size, void* u // if there's anything left in the inf_ctx buffer, return that. // required! if data remaining in buffer expands to fill max output, // we must not read more cdata - nowhere to store it. - CHECK_ERR(inf_set_dest(io->inf_ctx, io->user_buf, io->max_output_size)); - ssize_t bytes_inflated = inf_inflate(io->inf_ctx, 0, 0); + CHECK_ERR(zlib_prepare_decompress(io->inf_ctx, DM_ZLIB, io->user_buf, io->max_output_size)); + ssize_t bytes_inflated = zlib_feed_decompressor(io->inf_ctx, 0, 0); CHECK_ERR(bytes_inflated); if(bytes_inflated == (ssize_t)max_output_size) { @@ -1081,6 +1407,9 @@ LibError zip_io_issue(ZFile* zf, off_t user_ofs, size_t max_output_size, void* u } // else: not compressed; we'll just read directly from the archive file. // no need to clamp to EOF - that's done already by the VFS. + { + io->inf_ctx = 0; + } zf->last_read_ofs += (off_t)size; @@ -1115,11 +1444,11 @@ LibError zip_io_wait(ZipIo* io, void*& buf, size_t& size) if(io->inf_ctx) { - inf_set_dest(io->inf_ctx, buf, size); + zlib_prepare_decompress(io->inf_ctx, DM_ZLIB, buf, size); // we allocated the compressed data input buffer and // want it freed when it's consumed. const bool want_input_buf_freed = true; - ssize_t bytes_inflated = inf_inflate(io->inf_ctx, raw_buf, raw_size, want_input_buf_freed); + ssize_t bytes_inflated = zlib_feed_decompressor(io->inf_ctx, raw_buf, raw_size, want_input_buf_freed); CHECK_ERR(bytes_inflated); } else @@ -1128,6 +1457,8 @@ LibError zip_io_wait(ZipIo* io, void*& buf, size_t& size) size = raw_size; } + // TODO update what we return - check LFH and skip tat ------------------------------------------------------------- + return ERR_OK; } @@ -1174,7 +1505,7 @@ static ssize_t read_cb(uintptr_t ctx, void* buf, size_t size) { CBParams* p = (CBParams*)ctx; - ssize_t ucsize = inf_inflate(p->inf_ctx, buf, size); + ssize_t ucsize = zlib_feed_decompressor(p->inf_ctx, buf, size); if(p->user_cb) { @@ -1216,12 +1547,12 @@ ssize_t zip_read(ZFile* zf, off_t ofs, size_t size, void* p, FileIOCB cb, uintpt // (avoid the Zip inflate start/finish stuff below) //const bool compressed = zfile_compressed(zf); // if(!compressed) -// return file_io(&za->f, ofs, csize, p); - // no need to set last_raw_ofs - only checked if compressed. + // return file_io(&za->f, ofs, csize, p); + // no need to set last_raw_ofs - only checked if compressed. // compressed - CHECK_ERR(inf_set_dest(zf->inf_ctx, p, size)); + CHECK_ERR(zlib_prepare_decompress(zf->inf_ctx, DM_ZLIB, p, size)); /* static bool once = false; @@ -1232,7 +1563,7 @@ ssize_t zip_read(ZFile* zf, off_t ofs, size_t size, void* p, FileIOCB cb, uintpt uintptr_t xctx = inf_init_ctx(); size_t xsize = za->f.size; void* xbuf=mem_alloc(xsize, 65536); - inf_set_dest(xctx, xbuf, xsize); + zlib_prepare_decompress(xctx, xbuf, xsize); const IOCBParams xparams = { xctx, false, 0, 0 }; double t1 = get_time(); file_io(&za->f,0, xsize, 0, io_cb, (uintptr_t)&xparams); diff --git a/source/lib/res/graphics/tex.cpp b/source/lib/res/graphics/tex.cpp index 5be73f9024..e01956beee 100755 --- a/source/lib/res/graphics/tex.cpp +++ b/source/lib/res/graphics/tex.cpp @@ -116,6 +116,8 @@ static void create_level(uint level, uint level_w, uint level_h, const u8* restrict level_data, size_t level_data_size, void* restrict ctx) { CreateLevelData* cld = (CreateLevelData*)ctx; + const size_t src_w = cld->prev_level_w; + const size_t src_h = cld->prev_level_h; const u8* src = cld->prev_level_data; u8* dst = (u8*)level_data; @@ -128,36 +130,42 @@ static void create_level(uint level, uint level_w, uint level_h, else { const uint num_components = cld->num_components; - const size_t dx = num_components, dy = dx*level_w*2; + const size_t dx = num_components, dy = dx*src_w; // special case: image is too small for 2x2 filter if(cld->prev_level_w == 1 || cld->prev_level_h == 1) { - for(uint y = 0; y < level_h; y++) + // image is either a horizontal or vertical line. + // their memory layout is the same (packed pixels), so no special + // handling is needed; just pick max dimension. + for(uint y = 0; y < MAX(src_w, src_h); y += 2) { for(uint i = 0; i < num_components; i++) { - *dst++ = (src[0]+src[dy]+1)/2; + *dst++ = (src[0]+src[dx]+1)/2; src += 1; } - src += dy; + + src += dx; // skip to next pixel (since box is 2x2) } } // normal else { - for(uint y = 0; y < level_h; y++) + for(uint y = 0; y < src_h; y += 2) { - for(uint x = 0; x < level_w; x++) + for(uint x = 0; x < src_w; x += 2) { for(uint i = 0; i < num_components; i++) { *dst++ = (src[0]+src[dx]+src[dy]+src[dx+dy]+2)/4; src += 1; } - src += dx; + + src += dx; // skip to next pixel (since box is 2x2) } - src += dy; + + src += dy; // skip to next row (since box is 2x2) } } diff --git a/source/lib/res/h_mgr.cpp b/source/lib/res/h_mgr.cpp index b056ffb53e..c09e7154a5 100755 --- a/source/lib/res/h_mgr.cpp +++ b/source/lib/res/h_mgr.cpp @@ -429,7 +429,7 @@ static void fn_store(HDATA* hd, const char* fn) if(hd->type->user_size+size <= HDATA_USER_SIZE) hd->fn = (const char*)hd->user + hd->type->user_size; else if(size <= FN_POOL_EL_SIZE) - hd->fn = (const char*)pool_alloc(&fn_pool); + hd->fn = (const char*)pool_alloc(&fn_pool, 0); // in case none of the above applied and/or were successful: // fall back to heap alloc. diff --git a/source/lib/sysdep/win/wposix.cpp b/source/lib/sysdep/win/wposix.cpp index 29650d0529..f925c9593a 100755 --- a/source/lib/sysdep/win/wposix.cpp +++ b/source/lib/sysdep/win/wposix.cpp @@ -24,7 +24,7 @@ #include "lib.h" #include "posix.h" #include "win_internal.h" -#include "sysdep/cpu.h" +#include "allocators.h" // cast intptr_t to HANDLE; centralized for easier changing, e.g. avoiding @@ -332,11 +332,9 @@ struct WDIR }; -// suballocator - satisfies most requests with a reusable static instance. -// this avoids hundreds of alloc/free which would fragment the heap. -// to guarantee thread-safety, we fall back to malloc if the instance is -// already in use. (it's important to avoid suprises since this is such a -// low-level routine). +// suballocator - satisfies most requests with a reusable static instance, +// thus speeding up allocation and avoiding heap fragmentation. +// thread-safe. static WDIR global_wdir; static uintptr_t global_wdir_is_in_use; @@ -344,26 +342,12 @@ static uintptr_t global_wdir_is_in_use; // zero-initializes the WDIR (code below relies on this) static inline WDIR* wdir_alloc() { - WDIR* d; - - // successfully reserved the global instance - if(CAS(&global_wdir_is_in_use, 0, 1)) - { - d = &global_wdir; - memset(d, 0, sizeof(*d)); - } - else - d = (WDIR*)calloc(1, sizeof(WDIR)); - - return d; + return (WDIR*)single_calloc(&global_wdir, &global_wdir_is_in_use, sizeof(WDIR)); } static inline void wdir_free(WDIR* d) { - if(d == &global_wdir) - global_wdir_is_in_use = 0; - else - free(d); + single_free(&global_wdir, &global_wdir_is_in_use, d); }