From b53428c7b7206a7e9abb92aafb2c4fdf1b083f01 Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:39:18 +0100 Subject: [PATCH] Use std::filesystem also update miniz miniz expects utf8 on windows now SDL2 already expected utf8 on windows, which may have been handled poorly before --- .github/workflows/test.yaml | 15 +- Makefile | 20 +- lib/miniz/miniz.c | 502 ++++++++++++++++++++---------- lib/miniz/miniz.h | 388 ++++++++++++++--------- src/ap/aptracker.h | 36 ++- src/core/assets.cpp | 14 +- src/core/assets.h | 9 +- src/core/fileutil.h | 357 ++++----------------- src/core/fs.h | 242 ++++++++++++++ src/core/log.cpp | 21 +- src/core/log.h | 7 +- src/core/pack.cpp | 176 ++++++----- src/core/pack.h | 28 +- src/core/scripthost.h | 2 +- src/core/sha256.cpp | 6 +- src/core/sha256.h | 3 +- src/core/statemanager.cpp | 40 +-- src/core/statemanager.h | 6 +- src/core/util.h | 11 + src/core/zip.cpp | 10 +- src/core/zip.h | 4 +- src/http/httpcache.cpp | 29 +- src/http/httpcache.h | 8 +- src/luaconnector/luaconnector.cpp | 2 +- src/main.cpp | 2 +- src/packmanager/packmanager.cpp | 51 +-- src/packmanager/packmanager.h | 15 +- src/poptracker.cpp | 232 +++++++------- src/poptracker.h | 18 +- src/ui/defaulttrackerwindow.cpp | 14 +- src/ui/defaulttrackerwindow.h | 2 +- src/ui/loadpackwidget.cpp | 2 +- src/ui/loadpackwidget.h | 4 +- src/ui/trackerwindow.h | 2 +- src/uilib/dlg.cpp | 56 ++-- src/uilib/dlg.h | 6 +- src/uilib/fontstore.cpp | 6 + src/uilib/image.cpp | 106 ++++--- src/uilib/image.h | 8 +- src/uilib/imagebutton.cpp | 3 +- src/uilib/imagebutton.h | 3 +- src/uilib/imghelper.h | 20 ++ src/uilib/ui.cpp | 2 +- 43 files changed, 1479 insertions(+), 1009 deletions(-) create mode 100644 src/core/fs.h create mode 100644 src/uilib/imghelper.h diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2caf117a..9caea676 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,6 +34,13 @@ jobs: include: - os: windows-latest shell: msys2 {0} + - os: macos-13 + shell: bash + extra_make_args: '' # default build + - os: macos-13 + shell: bash + # we now require 10.15 by default, but 10.13 should work with an extra dependency + extra_make_args: 'MACOS_DEPLOYMENT_TARGET=10.13' name: Test ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -52,6 +59,10 @@ jobs: if: ${{ startsWith(matrix.os, 'macos') }} run: | brew install coreutils SDL2 sdl2_ttf sdl2_image openssl@1.1 googletest + - name: Install extra dependencies (brewo) + if: ${{ startsWith(matrix.os, 'macos') && contains(matrix.extra_make_args, 'MACOS_DEPLOYMENT_TARGET') }} + run: | + brew install boost - name: Install dependencies (msys2) if: ${{ startsWith(matrix.os, 'windows') }} uses: msys2/setup-msys2@v2 @@ -74,6 +85,6 @@ jobs: with: submodules: recursive - name: Build DEBUG - run: make native CONF=DEBUG -j4 + run: make native CONF=DEBUG ${{ matrix.extra_make_args }} -j4 - name: Run tests - run: make test CONF=DEBUG + run: make test CONF=DEBUG ${{ matrix.extra_make_args }} diff --git a/Makefile b/Makefile index d8cf05e0..7beba6e3 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ # NOTE: we may use gifdec in the future to support animations # for now we use SDL2_image GIF support only CONF ?= RELEASE # make CONF=DEBUG for debug, CONF=DIST for .zip +MACOS_DEPLOYMENT_TARGET ?= 10.15 SRC_DIR = src TEST_DIR = test LIB_DIR = lib @@ -172,14 +173,31 @@ WIN64_LD_FLAGS = $(LD_FLAGS) NIX_LD_FLAGS = $(LD_FLAGS) NIX_C_FLAGS = $(C_FLAGS) -DLUA_USE_READLINE -DLUA_USE_LINUX NIX_LUA_C_FLAGS = $(LUA_C_FLAGS) -DLUA_USE_READLINE -DLUA_USE_LINUX + ifdef IS_OSX BREW_PREFIX := $(shell brew --prefix) -DEPLOYMENT_TARGET=10.12 +DEPLOYMENT_TARGET = $(MACOS_DEPLOYMENT_TARGET) +DEPLOYMENT_TARGET_MAJOR := $(shell echo $(DEPLOYMENT_TARGET) | cut -f1 -d.) +DEPLOYMENT_TARGET_MINOR := $(shell echo $(DEPLOYMENT_TARGET) | cut -f2 -d.) +# NOTE: on macos before 10.15 we have to use boost::filesystem instead of std::filesystem +# This is currently not built automatically. Please open an issue if you really need a build for macos<10.15. +HAS_STD_FILESYSTEM := $(shell [ $(DEPLOYMENT_TARGET_MAJOR) -gt 10 -o \( $(DEPLOYMENT_TARGET_MAJOR) -eq 10 -a $(DEPLOYMENT_TARGET_MINOR) -ge 15 \) ] && echo true) NIX_CPP_FLAGS += -mmacosx-version-min=$(DEPLOYMENT_TARGET) -I$(BREW_PREFIX)/opt/openssl@1.1/include -I$(BREW_PREFIX)/include NIX_LD_FLAGS += -mmacosx-version-min=$(DEPLOYMENT_TARGET) -L$(BREW_PREFIX)/opt/openssl@1.1/lib -L$(BREW_PREFIX)/lib NIX_C_FLAGS += -mmacosx-version-min=$(DEPLOYMENT_TARGET) -I$(BREW_PREFIX)/opt/openssl@1.1/include -I$(BREW_PREFIX)/include NIX_LUA_C_FLAGS += -mmacosx-version-min=$(DEPLOYMENT_TARGET) -I$(BREW_PREFIX)/opt/openssl@1.1/include -I$(BREW_PREFIX)/include +else +HAS_STD_FILESYSTEM ?= true +endif + +ifeq ($(HAS_STD_FILESYSTEM), true) +$(info using std::filesystem) +else +$(info using boost::filesystem) +NIX_LD_FLAGS += -lboost_filesystem +NIX_CPP_FLAGS += -DNO_STD_FILESYSTEM endif + ifeq ($(CONF), DEBUG) # DEBUG WINDRES_FLAGS = else diff --git a/lib/miniz/miniz.c b/lib/miniz/miniz.c index 9ded4c75..9017a0bb 100644 --- a/lib/miniz/miniz.c +++ b/lib/miniz/miniz.c @@ -1,3 +1,4 @@ +#include "miniz.h" /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software @@ -24,7 +25,7 @@ * **************************************************************************/ -#include "miniz.h" + typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; @@ -82,6 +83,12 @@ mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) } return ~crcu32; } +#elif defined(USE_EXTERNAL_MZCRC) +/* If USE_EXTERNAL_CRC is defined, an external module will export the + * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version. + * Depending on the impl, it may be necessary to ~ the input/output crc values. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len); #else /* Faster, but larger CPU cache footprint. */ @@ -157,17 +164,17 @@ void mz_free(void *p) MZ_FREE(p); } -void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } -void miniz_def_free_func(void *opaque, void *address) +MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } -void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); @@ -180,6 +187,8 @@ const char *mz_version(void) #ifndef MINIZ_NO_ZLIB_APIS +#ifndef MINIZ_NO_DEFLATE_APIS + int mz_deflateInit(mz_streamp pStream, int level) { return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); @@ -314,7 +323,7 @@ int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ - if ((source_len | *pDest_len) > 0xFFFFFFFFU) + if ((mz_uint64)(source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; @@ -347,6 +356,10 @@ mz_ulong mz_compressBound(mz_ulong source_len) return mz_deflateBound(NULL, source_len); } +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS + typedef struct { tinfl_decompressor m_decomp; @@ -546,19 +559,18 @@ int mz_inflateEnd(mz_streamp pStream) } return MZ_OK; } - -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len) { mz_stream stream; int status; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ - if ((source_len | *pDest_len) > 0xFFFFFFFFU) + if ((mz_uint64)(*pSource_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; - stream.avail_in = (mz_uint32)source_len; + stream.avail_in = (mz_uint32)*pSource_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; @@ -567,6 +579,7 @@ int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char return status; status = mz_inflate(&stream, MZ_FINISH); + *pSource_len = *pSource_len - stream.avail_in; if (status != MZ_STREAM_END) { mz_inflateEnd(&stream); @@ -577,6 +590,13 @@ int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char return mz_inflateEnd(&stream); } +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_uncompress2(pDest, pDest_len, pSource, &source_len); +} + +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + const char *mz_error(int err) { static struct @@ -654,6 +674,7 @@ const char *mz_error(int err) +#ifndef MINIZ_NO_DEFLATE_APIS #ifdef __cplusplus extern "C" { @@ -733,7 +754,7 @@ static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *p { mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; - MZ_CLEAR_OBJ(hist); + MZ_CLEAR_ARR(hist); for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; @@ -851,7 +872,7 @@ static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int { int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; - MZ_CLEAR_OBJ(num_codes); + MZ_CLEAR_ARR(num_codes); if (static_table) { for (i = 0; i < table_len; i++) @@ -877,8 +898,8 @@ static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); - MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); - MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + MZ_CLEAR_ARR(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_ARR(d->m_huff_codes[table_num]); for (i = 1, j = num_used_syms; i <= code_size_limit; i++) for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); @@ -964,7 +985,7 @@ static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int } \ } -static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; +static const mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static void tdefl_start_dynamic_block(tdefl_compressor *d) { @@ -1102,7 +1123,8 @@ static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) if (flags & 1) { mz_uint s0, s1, n0, n1, sym, num_extra_bits; - mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + mz_uint match_len = pLZ_codes[0]; + mz_uint match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); @@ -1147,7 +1169,7 @@ static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) if (pOutput_buf >= d->m_pOutput_buf_end) return MZ_FALSE; - *(mz_uint64 *)pOutput_buf = bit_buffer; + memcpy(pOutput_buf, &bit_buffer, sizeof(mz_uint64)); pOutput_buf += (bits_in >> 3); bit_buffer >>= (bits_in & ~7); bits_in &= 7; @@ -1229,6 +1251,8 @@ static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) return tdefl_compress_lz_codes(d); } +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + static int tdefl_flush_block(tdefl_compressor *d, int flush) { mz_uint saved_bit_buf, saved_bits_in; @@ -1249,8 +1273,27 @@ static int tdefl_flush_block(tdefl_compressor *d, int flush) if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { - TDEFL_PUT_BITS(0x78, 8); - TDEFL_PUT_BITS(0x01, 8); + const mz_uint8 cmf = 0x78; + mz_uint8 flg, flevel = 3; + mz_uint header, i, mz_un = sizeof(s_tdefl_num_probes) / sizeof(mz_uint); + + /* Determine compression level by reversing the process in tdefl_create_comp_flags_from_zip_params() */ + for (i = 0; i < mz_un; i++) + if (s_tdefl_num_probes[i] == (d->m_flags & 0xFFF)) break; + + if (i < 2) + flevel = 0; + else if (i < 6) + flevel = 1; + else if (i == 6) + flevel = 2; + + header = cmf << 8 | (flevel << 6); + header += 31 - (header % 31); + flg = header & 0xFF; + + TDEFL_PUT_BITS(cmf, 8); + TDEFL_PUT_BITS(flg, 8); } TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); @@ -1703,9 +1746,7 @@ static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; - - if (match_len >= TDEFL_MIN_MATCH_LEN) - d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; } static mz_bool tdefl_compress_normal(tdefl_compressor *d) @@ -1723,7 +1764,7 @@ static mz_bool tdefl_compress_normal(tdefl_compressor *d) mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); - const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + const mz_uint8 *pSrc_end = pSrc ? pSrc + num_bytes_to_process : NULL; src_buf_left -= num_bytes_to_process; d->m_lookahead_size += num_bytes_to_process; while (pSrc != pSrc_end) @@ -1933,8 +1974,8 @@ tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pI d->m_finished = (flush == TDEFL_FINISH); if (flush == TDEFL_FULL_FLUSH) { - MZ_CLEAR_OBJ(d->m_hash); - MZ_CLEAR_OBJ(d->m_next); + MZ_CLEAR_ARR(d->m_hash); + MZ_CLEAR_ARR(d->m_next); d->m_dict_size = 0; } } @@ -1957,11 +1998,12 @@ tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_fun d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) - MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_ARR(d->m_hash); d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; + *d->m_pLZ_flags = 0; d->m_num_flags_left = 8; d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; @@ -1977,7 +2019,7 @@ tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_fun d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) - MZ_CLEAR_OBJ(d->m_dict); + MZ_CLEAR_ARR(d->m_dict); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); return TDEFL_STATUS_OKAY; @@ -2068,8 +2110,6 @@ size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void return out_buf.m_size; } -static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; - /* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) { @@ -2187,7 +2227,7 @@ void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, /* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ /* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ -tdefl_compressor *tdefl_compressor_alloc() +tdefl_compressor *tdefl_compressor_alloc(void) { return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); } @@ -2205,7 +2245,9 @@ void tdefl_compressor_free(tdefl_compressor *pComp) #ifdef __cplusplus } #endif -/************************************************************************** + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC @@ -2233,6 +2275,8 @@ void tdefl_compressor_free(tdefl_compressor *pComp) +#ifndef MINIZ_NO_INFLATE_APIS + #ifdef __cplusplus extern "C" { #endif @@ -2313,10 +2357,10 @@ extern "C" { /* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ /* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ /* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ -#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ +#define TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree) \ do \ { \ - temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ if (temp >= 0) \ { \ code_len = temp >> 9; \ @@ -2328,7 +2372,7 @@ extern "C" { code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while ((temp < 0) && (num_bits >= (code_len + 1))); \ if (temp >= 0) \ break; \ @@ -2344,7 +2388,7 @@ extern "C" { /* The slow path is only executed at the very end of the input buffer. */ /* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ /* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ -#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ +#define TINFL_HUFF_DECODE(state_index, sym, pLookUp, pTree) \ do \ { \ int temp; \ @@ -2353,7 +2397,7 @@ extern "C" { { \ if ((pIn_buf_end - pIn_buf_cur) < 2) \ { \ - TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree); \ } \ else \ { \ @@ -2362,14 +2406,14 @@ extern "C" { num_bits += 16; \ } \ } \ - if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + if ((temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ code_len = temp >> 9, temp &= 511; \ else \ { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while (temp < 0); \ } \ sym = temp; \ @@ -2378,20 +2422,33 @@ extern "C" { } \ MZ_MACRO_END +static void tinfl_clear_tree(tinfl_decompressor *r) +{ + if (r->m_type == 0) + MZ_CLEAR_ARR(r->m_tree_0); + else if (r->m_type == 1) + MZ_CLEAR_ARR(r->m_tree_1); + else + MZ_CLEAR_ARR(r->m_tree_2); +} + tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) { - static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; - static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; - static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; - static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + static const mz_uint16 s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; + static const mz_uint8 s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; + static const mz_uint16 s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; + static const mz_uint8 s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - static const int s_min_table_sizes[3] = { 257, 1, 4 }; + static const mz_uint16 s_min_table_sizes[3] = { 257, 1, 4 }; + + mz_int16 *pTrees[3]; + mz_uint8 *pCode_sizes[3]; tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; - mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next ? pOut_buf_next + *pOut_buf_size : NULL; size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ @@ -2401,6 +2458,13 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex return TINFL_STATUS_BAD_PARAM; } + pTrees[0] = r->m_tree_0; + pTrees[1] = r->m_tree_1; + pTrees[2] = r->m_tree_2; + pCode_sizes[0] = r->m_code_size_0; + pCode_sizes[1] = r->m_code_size_1; + pCode_sizes[2] = r->m_code_size_2; + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; @@ -2417,7 +2481,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex TINFL_GET_BYTE(2, r->m_zhdr1); counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) - counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)((size_t)1 << (8U + (r->m_zhdr0 >> 4))))); if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); @@ -2478,11 +2542,11 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex { if (r->m_type == 1) { - mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint8 *p = r->m_code_size_0; mz_uint i; r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; - TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + TINFL_MEMSET(r->m_code_size_1, 5, 32); for (i = 0; i <= 143; ++i) *p++ = 8; for (; i <= 255; ++i) @@ -2499,26 +2563,30 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } - MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + MZ_CLEAR_ARR(r->m_code_size_2); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); - r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + r->m_code_size_2[s_length_dezigzag[counter]] = (mz_uint8)s; } r->m_table_sizes[2] = 19; } for (; (int)r->m_type >= 0; r->m_type--) { int tree_next, tree_cur; - tinfl_huff_table *pTable; + mz_int16 *pLookUp; + mz_int16 *pTree; + mz_uint8 *pCode_size; mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; - pTable = &r->m_tables[r->m_type]; - MZ_CLEAR_OBJ(total_syms); - MZ_CLEAR_OBJ(pTable->m_look_up); - MZ_CLEAR_OBJ(pTable->m_tree); + pLookUp = r->m_look_up[r->m_type]; + pTree = pTrees[r->m_type]; + pCode_size = pCode_sizes[r->m_type]; + MZ_CLEAR_ARR(total_syms); + TINFL_MEMSET(pLookUp, 0, sizeof(r->m_look_up[0])); + tinfl_clear_tree(r); for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) - total_syms[pTable->m_code_size[i]]++; + total_syms[pCode_size[i]]++; used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; for (i = 1; i <= 15; ++i) @@ -2532,7 +2600,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex } for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { - mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; + mz_uint rev_code = 0, l, cur_code, code_size = pCode_size[sym_index]; if (!code_size) continue; cur_code = next_code[code_size]++; @@ -2543,14 +2611,14 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { - pTable->m_look_up[rev_code] = k; + pLookUp[rev_code] = k; rev_code += (1 << code_size); } continue; } - if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + if (0 == (tree_cur = pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { - pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } @@ -2558,24 +2626,24 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { tree_cur -= ((rev_code >>= 1) & 1); - if (!pTable->m_tree[-tree_cur - 1]) + if (!pTree[-tree_cur - 1]) { - pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + pTree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else - tree_cur = pTable->m_tree[-tree_cur - 1]; + tree_cur = pTree[-tree_cur - 1]; } tree_cur -= ((rev_code >>= 1) & 1); - pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + pTree[-tree_cur - 1] = (mz_int16)sym_index; } if (r->m_type == 2) { for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { mz_uint s; - TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + TINFL_HUFF_DECODE(16, dist, r->m_look_up[2], r->m_tree_2); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; @@ -2595,8 +2663,8 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex { TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); } - TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); - TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + TINFL_MEMCPY(r->m_code_size_0, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_code_size_1, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); } } for (;;) @@ -2606,7 +2674,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex { if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) { - TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + TINFL_HUFF_DECODE(23, counter, r->m_look_up[0], r->m_tree_0); if (counter >= 256) break; while (pOut_buf_cur >= pOut_buf_end) @@ -2634,14 +2702,14 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex num_bits += 16; } #endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { - sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } counter = sym2; @@ -2658,14 +2726,14 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex num_bits += 16; } #endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { - sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } bit_buf >>= code_len; @@ -2694,7 +2762,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex counter += extra_bits; } - TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + TINFL_HUFF_DECODE(26, dist, r->m_look_up[1], r->m_tree_1); num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; if (num_extra) @@ -2705,7 +2773,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex } dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; - if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); } @@ -2779,7 +2847,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex --pIn_buf_cur; num_bits -= 8; } - bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + bit_buf &= ~(~(tinfl_bit_buf_t)0 << num_bits); MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) @@ -2811,7 +2879,7 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_nex } } r->m_num_bits = num_bits; - r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + r->m_bit_buf = bit_buf & ~(~(tinfl_bit_buf_t)0 << num_bits); r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; @@ -2906,6 +2974,7 @@ int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, size_t in_buf_ofs = 0, dict_ofs = 0; if (!pDict) return TINFL_STATUS_FAILED; + memset(pDict,0,TINFL_LZ_DICT_SIZE); tinfl_init(&decomp); for (;;) { @@ -2928,7 +2997,7 @@ int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, } #ifndef MINIZ_NO_MALLOC -tinfl_decompressor *tinfl_decompressor_alloc() +tinfl_decompressor *tinfl_decompressor_alloc(void) { tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); if (pDecomp) @@ -2945,7 +3014,9 @@ void tinfl_decompressor_free(tinfl_decompressor *pDecomp) #ifdef __cplusplus } #endif -/************************************************************************** + +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC @@ -2987,19 +3058,48 @@ extern "C" { #include #if defined(_MSC_VER) || defined(__MINGW64__) + +#define WIN32_LEAN_AND_MEAN +#include + +static WCHAR* mz_utf8z_to_widechar(const char* str) +{ + int reqChars = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + WCHAR* wStr = (WCHAR*)malloc(reqChars * sizeof(WCHAR)); + MultiByteToWideChar(CP_UTF8, 0, str, -1, wStr, reqChars); + return wStr; +} + static FILE *mz_fopen(const char *pFilename, const char *pMode) { - FILE *pFile = NULL; - fopen_s(&pFile, pFilename, pMode); - return pFile; + WCHAR* wFilename = mz_utf8z_to_widechar(pFilename); + WCHAR* wMode = mz_utf8z_to_widechar(pMode); + FILE* pFile = NULL; + errno_t err = _wfopen_s(&pFile, wFilename, wMode); + free(wFilename); + free(wMode); + return err ? NULL : pFile; } + static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { - FILE *pFile = NULL; - if (freopen_s(&pFile, pPath, pMode, pStream)) - return NULL; - return pFile; + WCHAR* wPath = mz_utf8z_to_widechar(pPath); + WCHAR* wMode = mz_utf8z_to_widechar(pMode); + FILE* pFile = NULL; + errno_t err = _wfreopen_s(&pFile, wPath, wMode, pStream); + free(wPath); + free(wMode); + return err ? NULL : pFile; +} + +static int mz_stat64(const char *path, struct __stat64 *buffer) +{ + WCHAR* wPath = mz_utf8z_to_widechar(path); + int res = _wstat64(wPath, buffer); + free(wPath); + return res; } + #ifndef MINIZ_NO_TIME #include #endif @@ -3010,11 +3110,12 @@ static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 #define MZ_FILE_STAT_STRUCT _stat64 -#define MZ_FILE_STAT _stat64 +#define MZ_FILE_STAT mz_stat64 #define MZ_FFLUSH fflush #define MZ_FREOPEN mz_freopen #define MZ_DELETE_FILE remove -#elif defined(__MINGW32__) + +#elif defined(__MINGW32__) || defined(__WATCOMC__) #ifndef MINIZ_NO_TIME #include #endif @@ -3022,13 +3123,14 @@ static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite -#define MZ_FTELL64 ftello64 -#define MZ_FSEEK64 fseeko64 -#define MZ_FILE_STAT_STRUCT _stat -#define MZ_FILE_STAT _stat +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove + #elif defined(__TINYC__) #ifndef MINIZ_NO_TIME #include @@ -3044,7 +3146,8 @@ static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove -#elif defined(__GNUC__) && defined(_LARGEFILE64_SOURCE) + +#elif defined(__USE_LARGEFILE64) /* gcc, clang */ #ifndef MINIZ_NO_TIME #include #endif @@ -3059,7 +3162,8 @@ static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) #define MZ_DELETE_FILE remove -#elif defined(__APPLE__) + +#elif defined(__APPLE__) || defined(__FreeBSD__) #ifndef MINIZ_NO_TIME #include #endif @@ -3205,7 +3309,7 @@ struct mz_zip_internal_state_tag mz_zip_array m_sorted_central_dir_offsets; /* The flags passed in when the archive is initially opened. */ - uint32_t m_init_flags; + mz_uint32 m_init_flags; /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ mz_bool m_zip64; @@ -3224,7 +3328,7 @@ struct mz_zip_internal_state_tag #define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size -#if defined(DEBUG) || defined(_DEBUG) || defined(NDEBUG) +#if defined(DEBUG) || defined(_DEBUG) static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) { MZ_ASSERT(index < pArray->m_size); @@ -3641,7 +3745,7 @@ static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flag if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); - if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + if (cdir_size < (mz_uint64)pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) @@ -3792,7 +3896,7 @@ static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flag void mz_zip_zero_struct(mz_zip_archive *pZip) { if (pZip) - MZ_CLEAR_OBJ(*pZip); + MZ_CLEAR_PTR(pZip); } static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) @@ -4266,7 +4370,7 @@ static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); - const uint32_t size = pZip->m_total_files; + const mz_uint32 size = pZip->m_total_files; const mz_uint filename_len = (mz_uint)strlen(pFilename); if (pIndex) @@ -4281,7 +4385,7 @@ static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char while (l <= h) { mz_int64 m = l + ((h - l) >> 1); - uint32_t file_index = pIndices[(uint32_t)m]; + mz_uint32 file_index = pIndices[(mz_uint32)m]; int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); if (!comp) @@ -4374,7 +4478,8 @@ mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, co return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); } -mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +static +mz_bool mz_zip_reader_extract_to_mem_no_alloc1(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size, const mz_zip_archive_file_stat *st) { int status = TINFL_STATUS_DONE; mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; @@ -4387,6 +4492,9 @@ mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + if (st) { + file_stat = *st; + } else if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; @@ -4517,17 +4625,22 @@ mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file return status == TINFL_STATUS_DONE; } +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); +} + mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return MZ_FALSE; - return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); + return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); } mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) { - return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); + return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, NULL, 0, NULL); } mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) @@ -4537,23 +4650,17 @@ mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFil void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) { - mz_uint64 comp_size, uncomp_size, alloc_size; - const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + mz_zip_archive_file_stat file_stat; + mz_uint64 alloc_size; void *pBuf; if (pSize) *pSize = 0; - if (!p) - { - mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return NULL; - } - - comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); - uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) { mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); @@ -4566,7 +4673,7 @@ void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, si return NULL; } - if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + if (!mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, (size_t)alloc_size, flags, NULL, 0, &file_stat)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return NULL; @@ -4592,7 +4699,9 @@ void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFile mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { int status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS mz_uint file_crc32 = MZ_CRC32_INIT; +#endif mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf = NULL; @@ -4888,7 +4997,7 @@ mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) { /* Decompression required, therefore intermediate read buffer required */ - pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); @@ -5025,7 +5134,7 @@ size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); /* Copy data to caller's buffer */ - memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); + memcpy( (mz_uint8*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS /* Perform CRC */ @@ -5246,7 +5355,10 @@ mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint f return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) - return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + goto handle_failure; + } if (local_header_filename_len) { @@ -5280,14 +5392,20 @@ mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint f mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { @@ -5385,7 +5503,7 @@ mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint f mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) { mz_zip_internal_state *pState; - uint32_t i; + mz_uint32 i; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); @@ -5403,9 +5521,6 @@ mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) } else { - if (pZip->m_total_files >= MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); - if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } @@ -5767,7 +5882,7 @@ mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 cur_ofs = 0; char buf[4096]; - MZ_CLEAR_OBJ(buf); + MZ_CLEAR_ARR(buf); do { @@ -6130,7 +6245,7 @@ mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_n pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ } - if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + if (((mz_uint64)buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ @@ -6180,8 +6295,8 @@ mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_n if (!pState->m_zip64) { /* Bail early if the archive would obviously become too large */ - if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size - + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) { @@ -6223,7 +6338,7 @@ mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_n } cur_archive_file_ofs += num_alignment_padding_bytes; - MZ_CLEAR_OBJ(local_dir_header); + MZ_CLEAR_ARR(local_dir_header); if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { @@ -6370,35 +6485,37 @@ mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_n return MZ_TRUE; } -mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, +mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { - mz_uint16 gen_flags = MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + mz_uint16 gen_flags; mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; - mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = size_to_add, comp_size = 0; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; mz_uint8 *pExtra_data = NULL; mz_uint32 extra_size = 0; mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; mz_zip_internal_state *pState; - mz_uint64 file_ofs = 0; - - if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) - gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + mz_uint64 file_ofs = 0, cur_archive_header_file_ofs; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; level = level_and_flags & 0xF; + gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + /* Sanity checks */ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; - if ((!pState->m_zip64) && (uncomp_size > MZ_UINT32_MAX)) + if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX)) { /* Source file is too large for non-zip64 */ /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ @@ -6455,7 +6572,7 @@ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pA } #endif - if (uncomp_size <= 3) + if (max_size <= 3) level = 0; if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) @@ -6471,19 +6588,25 @@ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pA MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } - if (uncomp_size && level) + if (max_size && level) { method = MZ_DEFLATED; } - MZ_CLEAR_OBJ(local_dir_header); + MZ_CLEAR_ARR(local_dir_header); if (pState->m_zip64) { - if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) { pExtra_data = extra_data; - extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, - (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + else + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL, + NULL, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date)) @@ -6534,9 +6657,8 @@ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pA cur_archive_file_ofs += user_extra_data_len; } - if (uncomp_size) + if (max_size) { - mz_uint64 uncomp_remaining = uncomp_size; void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); if (!pRead_buf) { @@ -6545,19 +6667,27 @@ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pA if (!level) { - while (uncomp_remaining) + while (1) { - mz_uint n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); - if ((read_callback(callback_opaque, file_ofs, pRead_buf, n) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); + if (n == 0) + break; + + if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } - file_ofs += n; + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + file_ofs += n; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); - uncomp_remaining -= n; cur_archive_file_ofs += n; } + uncomp_size = file_ofs; comp_size = uncomp_size; } else @@ -6584,24 +6714,26 @@ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pA for (;;) { - size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); tdefl_status status; tdefl_flush flush = TDEFL_NO_FLUSH; - if (read_callback(callback_opaque, file_ofs, pRead_buf, in_buf_size)!= in_buf_size) + size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); + if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); break; } - file_ofs += in_buf_size; - uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); - uncomp_remaining -= in_buf_size; + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) flush = TDEFL_FULL_FLUSH; - status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? flush : TDEFL_FINISH); + if (n == 0) + flush = TDEFL_FINISH; + + status = tdefl_compress_buffer(pComp, pRead_buf, n, flush); if (status == TDEFL_STATUS_DONE) { result = MZ_TRUE; @@ -6622,6 +6754,7 @@ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pA return MZ_FALSE; } + uncomp_size = file_ofs; comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; } @@ -6629,6 +6762,7 @@ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pA pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); } + if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)) { mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; @@ -6656,6 +6790,44 @@ mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pA cur_archive_file_ofs += local_dir_footer_size; } + if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) + { + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, + (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), + (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size, + (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size, + uncomp_crc32, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + cur_archive_header_file_ofs = local_dir_header_ofs; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + if (pExtra_data != NULL) + { + cur_archive_header_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_header_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_header_file_ofs += extra_size; + } + } + if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, @@ -6686,10 +6858,10 @@ static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *p return MZ_FREAD(pBuf, 1, n, pSrc_file); } -mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { - return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, size_to_add, pFile_time, pComment, comment_size, level_and_flags, + return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, max_size, pFile_time, pComment, comment_size, level_and_flags, user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); } @@ -6725,7 +6897,7 @@ mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, } #endif /* #ifndef MINIZ_NO_STDIO */ -static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) +static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, mz_uint32 ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) { /* + 64 should be enough for any new zip64 data */ if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) @@ -7041,10 +7213,10 @@ mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive * if (pZip->m_pState->m_zip64) { /* dest is zip64, so upgrade the data descriptor */ - const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0)); - const mz_uint32 src_crc32 = pSrc_descriptor[0]; - const mz_uint64 src_comp_size = pSrc_descriptor[1]; - const mz_uint64 src_uncomp_size = pSrc_descriptor[2]; + const mz_uint8 *pSrc_descriptor = (const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0); + const mz_uint32 src_crc32 = MZ_READ_LE32(pSrc_descriptor); + const mz_uint64 src_comp_size = MZ_READ_LE32(pSrc_descriptor + sizeof(mz_uint32)); + const mz_uint64 src_uncomp_size = MZ_READ_LE32(pSrc_descriptor + 2*sizeof(mz_uint32)); mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); @@ -7180,7 +7352,7 @@ mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) if (pState->m_zip64) { - if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX)) + if ((mz_uint64)pState->m_central_dir.m_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else @@ -7208,7 +7380,7 @@ mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) /* Write zip64 end of central directory header */ mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; - MZ_CLEAR_OBJ(hdr); + MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ @@ -7223,7 +7395,7 @@ mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; /* Write zip64 end of central directory locator */ - MZ_CLEAR_OBJ(hdr); + MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); @@ -7234,7 +7406,7 @@ mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) } /* Write end of central directory record */ - MZ_CLEAR_OBJ(hdr); + MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); @@ -7550,7 +7722,9 @@ const char *mz_zip_get_error_string(mz_zip_error mz_err) case MZ_ZIP_VALIDATION_FAILED: return "validation failed"; case MZ_ZIP_WRITE_CALLBACK_FAILED: - return "write calledback failed"; + return "write callback failed"; + case MZ_ZIP_TOTAL_ERRORS: + return "total errors"; default: break; } diff --git a/lib/miniz/miniz.h b/lib/miniz/miniz.h index 7db62811..9fcfffcc 100644 --- a/lib/miniz/miniz.h +++ b/lib/miniz/miniz.h @@ -1,4 +1,7 @@ -/* miniz.c 2.1.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing +#ifndef MINIZ_EXPORT +#define MINIZ_EXPORT +#endif +/* miniz.c 3.0.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt @@ -95,7 +98,7 @@ possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - ZIP archive support limitations: - No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + No spanning support. Extraction functions can only handle unencrypted, stored or deflated files. Requires streams capable of seeking. * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the @@ -114,10 +117,8 @@ - - /* Defines to completely disable specific portions of miniz.c: - If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ + If all macros here are defined the only functionality remaining will be CRC-32 and adler-32. */ /* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ /*#define MINIZ_NO_STDIO */ @@ -127,6 +128,12 @@ /* The current downside is the times written to your archives will be from 1979. */ /*#define MINIZ_NO_TIME */ +/* Define MINIZ_NO_DEFLATE_APIS to disable all compression API's. */ +/*#define MINIZ_NO_DEFLATE_APIS */ + +/* Define MINIZ_NO_INFLATE_APIS to disable all decompression API's. */ +/*#define MINIZ_NO_INFLATE_APIS */ + /* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ /*#define MINIZ_NO_ARCHIVE_APIS */ @@ -145,6 +152,14 @@ functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ /*#define MINIZ_NO_MALLOC */ +#ifdef MINIZ_NO_INFLATE_APIS +#define MINIZ_NO_ARCHIVE_APIS +#endif + +#ifdef MINIZ_NO_DEFLATE_APIS +#define MINIZ_NO_ARCHIVE_WRITING_APIS +#endif + #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) /* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ #define MINIZ_NO_TIME @@ -163,18 +178,40 @@ #define MINIZ_X86_OR_X64_CPU 0 #endif -#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +/* Set MINIZ_LITTLE_ENDIAN only if not set */ +#if !defined(MINIZ_LITTLE_ENDIAN) +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ #define MINIZ_LITTLE_ENDIAN 1 #else #define MINIZ_LITTLE_ENDIAN 0 #endif +#else + +#if MINIZ_X86_OR_X64_CPU +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +#endif +#endif + +/* Using unaligned loads and stores causes errors when using UBSan */ +#if defined(__has_feature) +#if __has_feature(undefined_behavior_sanitizer) +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) #if MINIZ_X86_OR_X64_CPU /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ -#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 #define MINIZ_UNALIGNED_USE_MEMCPY #else #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 @@ -198,15 +235,15 @@ extern "C" { typedef unsigned long mz_ulong; /* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ -void mz_free(void *p); +MINIZ_EXPORT void mz_free(void *p); #define MZ_ADLER32_INIT (1) /* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ -mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); +MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); #define MZ_CRC32_INIT (0) /* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ -mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); +MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); /* Compression strategies. */ enum @@ -222,7 +259,7 @@ enum #define MZ_DEFLATED 8 /* Heap allocation callbacks. -Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */ +Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */ typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); typedef void (*mz_free_func)(void *opaque, void *address); typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); @@ -238,10 +275,10 @@ enum MZ_DEFAULT_COMPRESSION = -1 }; -#define MZ_VERSION "10.1.0" -#define MZ_VERNUM 0xA100 -#define MZ_VER_MAJOR 10 -#define MZ_VER_MINOR 1 +#define MZ_VERSION "11.0.2" +#define MZ_VERNUM 0xB002 +#define MZ_VER_MAJOR 11 +#define MZ_VER_MINOR 2 #define MZ_VER_REVISION 0 #define MZ_VER_SUBREVISION 0 @@ -304,7 +341,9 @@ typedef struct mz_stream_s typedef mz_stream *mz_streamp; /* Returns the version string of miniz.c. */ -const char *mz_version(void); +MINIZ_EXPORT const char *mz_version(void); + +#ifndef MINIZ_NO_DEFLATE_APIS /* mz_deflateInit() initializes a compressor with default options: */ /* Parameters: */ @@ -317,17 +356,17 @@ const char *mz_version(void); /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if the input parameters are bogus. */ /* MZ_MEM_ERROR on out of memory. */ -int mz_deflateInit(mz_streamp pStream, int level); +MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level); /* mz_deflateInit2() is like mz_deflate(), except with more control: */ /* Additional parameters: */ /* method must be MZ_DEFLATED */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ /* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ -int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); +MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ -int mz_deflateReset(mz_streamp pStream); +MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream); /* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ /* Parameters: */ @@ -339,34 +378,38 @@ int mz_deflateReset(mz_streamp pStream); /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ -int mz_deflate(mz_streamp pStream, int flush); +MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush); /* mz_deflateEnd() deinitializes a compressor: */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ -int mz_deflateEnd(mz_streamp pStream); +MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream); /* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ -mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); +MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); /* Single-call compression functions mz_compress() and mz_compress2(): */ /* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ -int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); -int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); +MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); /* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ -mz_ulong mz_compressBound(mz_ulong source_len); +MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len); + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS /* Initializes a decompressor. */ -int mz_inflateInit(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream); /* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ -int mz_inflateInit2(mz_streamp pStream, int window_bits); +MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ -int mz_inflateReset(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream); /* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ /* Parameters: */ @@ -382,17 +425,19 @@ int mz_inflateReset(mz_streamp pStream); /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ /* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ -int mz_inflate(mz_streamp pStream, int flush); +MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush); /* Deinitializes a decompressor. */ -int mz_inflateEnd(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream); /* Single-call decompression. */ /* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len); +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ /* Returns a string description of the specified error code, or NULL if the error code is invalid. */ -const char *mz_error(int err); +MINIZ_EXPORT const char *mz_error(int err); /* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ @@ -440,6 +485,8 @@ typedef void *const voidpc; #define free_func mz_free_func #define internal_state mz_internal_state #define z_stream mz_stream + +#ifndef MINIZ_NO_DEFLATE_APIS #define deflateInit mz_deflateInit #define deflateInit2 mz_deflateInit2 #define deflateReset mz_deflateReset @@ -449,12 +496,18 @@ typedef void *const voidpc; #define compress mz_compress #define compress2 mz_compress2 #define compressBound mz_compressBound +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS #define inflateInit mz_inflateInit #define inflateInit2 mz_inflateInit2 #define inflateReset mz_inflateReset #define inflate mz_inflate #define inflateEnd mz_inflateEnd #define uncompress mz_uncompress +#define uncompress2 mz_uncompress2 +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + #define crc32 mz_crc32 #define adler32 mz_adler32 #define MAX_WBITS 15 @@ -475,12 +528,19 @@ typedef void *const voidpc; #ifdef __cplusplus } #endif + + + + + #pragma once #include #include #include #include + + /* ------------------- Types and macros */ typedef unsigned char mz_uint8; typedef signed short mz_int16; @@ -511,7 +571,8 @@ typedef int mz_bool; #ifdef MINIZ_NO_TIME typedef struct mz_dummy_time_t_tag { - int m_dummy; + mz_uint32 m_dummy1; + mz_uint32 m_dummy2; } mz_dummy_time_t; #define MZ_TIME_T mz_dummy_time_t #else @@ -533,6 +594,8 @@ typedef struct mz_dummy_time_t_tag #define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) +#define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj)) +#define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj)) #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) @@ -556,9 +619,9 @@ typedef struct mz_dummy_time_t_tag extern "C" { #endif -extern void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); -extern void miniz_def_free_func(void *opaque, void *address); -extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); +extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address); +extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); #define MZ_UINT16_MAX (0xFFFFU) #define MZ_UINT32_MAX (0xFFFFFFFFU) @@ -566,9 +629,11 @@ extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, s #ifdef __cplusplus } #endif -#pragma once + #pragma once +#ifndef MINIZ_NO_DEFLATE_APIS + #ifdef __cplusplus extern "C" { #endif @@ -616,11 +681,11 @@ enum /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must free() the returned block when it's no longer needed. */ -void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); +MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ /* Returns 0 on failure. */ -size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); +MINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* Compresses an image to a compressed PNG file in memory. */ /* On entry: */ @@ -632,14 +697,14 @@ size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pLen_out will be set to the size of the PNG image file. */ /* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ -void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); -void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); /* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); /* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ -mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +MINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); enum { @@ -727,39 +792,43 @@ typedef struct /* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ /* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ /* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ -tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); /* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ -tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); +MINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); /* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ /* tdefl_compress_buffer() always consumes the entire input buffer. */ -tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); +MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); -tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); -mz_uint32 tdefl_get_adler32(tdefl_compressor *d); +MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d); /* Create tdefl_compress() flags given zlib-style compression parameters. */ /* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ /* window_bits may be -15 (raw deflate) or 15 (zlib) */ /* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ -mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor structure in C so that */ /* non-C language bindings to tdefl_ API don't need to worry about */ /* structure size and allocation mechanism. */ -tdefl_compressor *tdefl_compressor_alloc(void); -void tdefl_compressor_free(tdefl_compressor *pComp); +MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void); +MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp); #endif #ifdef __cplusplus } #endif -#pragma once + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + #pragma once /* ------------------- Low-level Decompression API Definitions */ +#ifndef MINIZ_NO_INFLATE_APIS + #ifdef __cplusplus extern "C" { #endif @@ -784,17 +853,17 @@ enum /* Function returns a pointer to the decompressed data, or NULL on failure. */ /* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must call mz_free() on the returned block when it's no longer needed. */ -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); +MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ /* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) -size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); +MINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ /* Returns 1 on success or 0 on failure. */ typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); -int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +MINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; @@ -803,8 +872,8 @@ typedef struct tinfl_decompressor_tag tinfl_decompressor; /* Allocate the tinfl_decompressor structure in C so that */ /* non-C language bindings to tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ -tinfl_decompressor *tinfl_decompressor_alloc(void); -void tinfl_decompressor_free(tinfl_decompressor *pDecomp); +MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void); +MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp); #endif /* Max size of LZ dictionary. */ @@ -855,7 +924,7 @@ typedef enum { /* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ /* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ -tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); +MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); /* Internal/private bits follow. */ enum @@ -868,12 +937,6 @@ enum TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS }; -typedef struct -{ - mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; - mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; -} tinfl_huff_table; - #if MINIZ_HAS_64BIT_REGISTERS #define TINFL_USE_64BIT_BITBUF 1 #else @@ -893,7 +956,13 @@ struct tinfl_decompressor_tag mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; tinfl_bit_buf_t m_bit_buf; size_t m_dist_from_out_buf_start; - tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE]; + mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; + mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2]; + mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2]; + mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1]; + mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2]; mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; @@ -901,6 +970,8 @@ struct tinfl_decompressor_tag } #endif +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + #pragma once @@ -934,10 +1005,6 @@ typedef struct mz_uint16 m_bit_flag; mz_uint16 m_method; -#ifndef MINIZ_NO_TIME - MZ_TIME_T m_time; -#endif - /* CRC-32 of uncompressed data. */ mz_uint32 m_crc32; @@ -974,6 +1041,11 @@ typedef struct /* Guaranteed to be zero terminated, may be truncated to fit. */ char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +#ifdef MINIZ_NO_TIME + MZ_TIME_T m_padding; +#else + MZ_TIME_T m_time; +#endif } mz_zip_archive_file_stat; typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); @@ -999,7 +1071,10 @@ typedef enum { MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, - MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000 + MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000, + /*After adding a compressed file, seek back + to local file header and set the correct sizes*/ + MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000 } mz_zip_flags; typedef enum { @@ -1082,9 +1157,7 @@ typedef struct mz_uint flags; int status; -#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS - mz_uint file_crc32; -#endif + mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf; @@ -1094,149 +1167,157 @@ typedef struct tinfl_decompressor inflator; +#ifdef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint padding; +#else + mz_uint file_crc32; +#endif + } mz_zip_reader_extract_iter_state; /* -------- ZIP reading */ /* Inits a ZIP archive reader. */ /* These functions read and validate the archive's central directory. */ -mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); -mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); #ifndef MINIZ_NO_STDIO /* Read a archive from a disk file. */ /* file_start_ofs is the file offset where the archive actually begins, or 0. */ /* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ -mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); -mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); +MINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); /* Read an archive from an already opened FILE, beginning at the current file position. */ -/* The archive is assumed to be archive_size bytes long. If archive_size is < 0, then the entire rest of the file is assumed to contain the archive. */ +/* The archive is assumed to be archive_size bytes long. If archive_size is 0, then the entire rest of the file is assumed to contain the archive. */ /* The FILE will NOT be closed when mz_zip_reader_end() is called. */ -mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); #endif /* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ -mz_bool mz_zip_reader_end(mz_zip_archive *pZip); +MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip); /* -------- ZIP reading or writing */ /* Clears a mz_zip_archive struct to all zeros. */ /* Important: This must be done before passing the struct to any mz_zip functions. */ -void mz_zip_zero_struct(mz_zip_archive *pZip); +MINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip); -mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); -mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); /* Returns the total number of files in the archive. */ -mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); +MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); -mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); -mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); -MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); +MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); /* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ -size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); +MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); /* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ /* Note that the m_last_error functionality is not thread safe. */ -mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); -mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); -mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); -mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); -const char *mz_zip_get_error_string(mz_zip_error mz_err); +MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); +MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err); /* MZ_TRUE if the archive file entry is a directory entry. */ -mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); /* MZ_TRUE if the file is encrypted/strong encrypted. */ -mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); /* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ -mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); /* Retrieves the filename of an archive file entry. */ /* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ -mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); +MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); /* Attempts to locates a file in the archive's central directory. */ /* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ /* Returns -1 if the file cannot be found. */ -int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); -int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); +MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); /* Returns detailed information about an archive file entry. */ -mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); +MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); /* MZ_TRUE if the file is in zip64 format. */ /* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ -mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); +MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); /* Returns the total central directory size in bytes. */ /* The current max supported size is <= MZ_UINT32_MAX. */ -size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); +MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); /* Extracts a archive file to a memory buffer using no memory allocation. */ /* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ -mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); -mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); /* Extracts a archive file to a memory buffer. */ -mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); /* Extracts a archive file to a dynamically allocated heap buffer. */ /* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ /* Returns NULL and sets the last error on failure. */ -void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); -void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); /* Extracts a archive file using a callback function to output the file's data. */ -mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); /* Extract a file iteratively */ -mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); -mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); -size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); -mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); +MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); +MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); +MINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); #ifndef MINIZ_NO_STDIO /* Extracts a archive file to a disk file and sets its last accessed and modified times. */ /* This function only extracts files, not archive directory records. */ -mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); /* Extracts a archive file starting at the current position in the destination FILE stream. */ -mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); -mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); #endif #if 0 /* TODO */ typedef void *mz_zip_streaming_extract_state_ptr; mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); - uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); - uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); - mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs); + mz_uint64 mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_uint64 mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, mz_uint64 new_ofs); size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); #endif /* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ /* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ -mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); /* Validates an entire archive by calling mz_zip_validate_file() on each file. */ -mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); /* Misc utils/helpers, valid for ZIP reading or writing */ -mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); -mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); +MINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); +#ifndef MINIZ_NO_STDIO +MINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); +#endif /* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ -mz_bool mz_zip_end(mz_zip_archive *pZip); +MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip); /* -------- ZIP writing */ @@ -1245,16 +1326,16 @@ mz_bool mz_zip_end(mz_zip_archive *pZip); /* Inits a ZIP archive writer. */ /*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ /*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ -mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); -mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +MINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); -mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); -mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); +MINIZ_EXPORT mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); #ifndef MINIZ_NO_STDIO -mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); -mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); -mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +MINIZ_EXPORT mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); #endif /* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ @@ -1263,56 +1344,57 @@ mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint f /* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ /* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ /* the archive is finalized the file's central directory will be hosed. */ -mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); -mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); +MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); /* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ /* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ -mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); /* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ /* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ -mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, - mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); -mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, - mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, - const char *user_extra_data_central, mz_uint user_extra_data_central_len); +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); /* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ /* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ -mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 size_to_add, +MINIZ_EXPORT mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); + #ifndef MINIZ_NO_STDIO /* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ -mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +MINIZ_EXPORT mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); /* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ -mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, +MINIZ_EXPORT mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); #endif /* Adds a file to an archive by fully cloning the data from another archive. */ /* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ -mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); +MINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); /* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ /* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ /* An archive must be manually finalized by calling this function for it to be valid. */ -mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +MINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); -/* Finalizes a heap archive, returning a poiner to the heap block and its size. */ +/* Finalizes a heap archive, returning a pointer to the heap block and its size. */ /* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ -mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); +MINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); /* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ /* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ -mz_bool mz_zip_writer_end(mz_zip_archive *pZip); +MINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip); /* -------- Misc. high-level helper functions: */ @@ -1320,14 +1402,16 @@ mz_bool mz_zip_writer_end(mz_zip_archive *pZip); /* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ /* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ -mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); -mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); +MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); +#ifndef MINIZ_NO_STDIO /* Reads a single file from an archive into a heap block. */ /* If pComment is not NULL, only the file with the specified comment will be extracted. */ /* Returns NULL on failure. */ -void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); -void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); +MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); +#endif #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ diff --git a/src/ap/aptracker.h b/src/ap/aptracker.h index 8750d258..5f4b81b4 100644 --- a/src/ap/aptracker.h +++ b/src/ap/aptracker.h @@ -25,18 +25,30 @@ class APTracker final { APTracker(const std::string& appname, const std::string& game="", bool allowSend=false) : _appname(appname), _game(game), _allowSend(allowSend) { - // AP uses an UUID to detect reconnects (replace old connection). If - // stored UUID is older than 60min, the connection should already be + // AP uses a UUID to detect reconnects (replace old connection). + // If stored UUID is older than 60min, the connection should already be // dropped, so we can generate a new one. bool newUuid = false; auto uuidFilename = getConfigPath(_appname, UUID_FILENAME); +#ifdef _WIN32 + struct _stat64 st; + auto now = _time64(nullptr); + if (_wstat64(uuidFilename.c_str(), &st) == 0) +#else struct stat st; - if (stat(uuidFilename.c_str(), &st) == 0) { - time_t elapsed = time(nullptr) - st.st_mtime; - newUuid = elapsed > 3600; + auto now = time(nullptr); + if (stat(uuidFilename.c_str(), &st) == 0) +#endif + { + static_assert(sizeof(st.st_mtime) == 8 && sizeof(now) == 8); + newUuid = (now - st.st_mtime) > 3600; } - if (!newUuid) newUuid = !readFile(uuidFilename, _uuid); - if (!newUuid) newUuid = _uuid.empty(); + else + newUuid = true; + if (!newUuid) + newUuid = !readFile(uuidFilename, _uuid); + if (!newUuid) + newUuid = _uuid.empty(); if (newUuid) { // generate a new uuid thread-safe _uuid.clear(); @@ -50,7 +62,11 @@ class APTracker final { writeFile(uuidFilename, _uuid); } else { // update mtime of uuid file +#ifdef _WIN32 + _wutime(uuidFilename.c_str(), nullptr); +#else utime(uuidFilename.c_str(), nullptr); +#endif } std::string s; if (readFile(getConfigPath(_appname, DATAPACKAGE_FILENAME), s)) { @@ -80,7 +96,7 @@ class APTracker final { return false; } - _ap = new APClient(_uuid, _game, uri, asset("cacert.pem")); + _ap = new APClient(_uuid, _game, uri, asset("cacert.pem").u8string()); _ap->set_data_package(_datapackage); _ap->set_socket_connected_handler([this, slot, pw]() { auto lock = EventLock(_event); @@ -204,7 +220,11 @@ class APTracker final { void touchUUID() { // update mtime of the uuid file so we reuse the uuid on next connect +#ifdef _WIN32 + _wutime(getConfigPath(_appname, UUID_FILENAME).c_str(), nullptr); +#else utime(getConfigPath(_appname, UUID_FILENAME).c_str(), nullptr); +#endif } int getPlayerNumber() const diff --git a/src/core/assets.cpp b/src/core/assets.cpp index 47f416d3..f21feffd 100644 --- a/src/core/assets.cpp +++ b/src/core/assets.cpp @@ -2,19 +2,21 @@ #include "fileutil.h" #include -std::vector Assets::_searchPaths; +std::vector Assets::_searchPaths; -void Assets::addSearchPath(const std::string& path) +void Assets::addSearchPath(const fs::path& path) { if (std::find(_searchPaths.begin(), _searchPaths.end(), path) != _searchPaths.end()) return; _searchPaths.push_back(path); } -std::string Assets::Find(const std::string& name) +fs::path Assets::Find(const std::string& name) { for (auto& searchPath : _searchPaths) { - auto filename = os_pathcat(searchPath, name); - if (fileExists(filename)) return filename; + auto filename = searchPath / name; + if (fs::is_regular_file(filename)) + return filename; } - return os_pathcat("assets", name); // fall-back to CWD + auto fallback = fs::u8path("assets") / name; // fall-back to CWD + return fallback; } \ No newline at end of file diff --git a/src/core/assets.h b/src/core/assets.h index 0d346996..24213f71 100644 --- a/src/core/assets.h +++ b/src/core/assets.h @@ -3,17 +3,18 @@ #include #include +#include "fs.h" class Assets final { public: - static std::string Find(const std::string& name); - static void addSearchPath(const std::string& path); + static fs::path Find(const std::string& name); + static void addSearchPath(const fs::path& path); private: static void initialize(); - static std::vector _searchPaths; + static std::vector _searchPaths; }; -static std::string asset(std::string name) { return Assets::Find(name); } +static fs::path asset(const std::string& name) { return Assets::Find(name); } #endif /* _CORE_ASSETS_H */ diff --git a/src/core/fileutil.h b/src/core/fileutil.h index d80a61e6..cf775e37 100644 --- a/src/core/fileutil.h +++ b/src/core/fileutil.h @@ -19,6 +19,9 @@ #include #include #include +#include "fs.h" +#include "util.h" +#include #if defined(__APPLE__) && !defined(MACOS) #define MACOS @@ -34,11 +37,14 @@ #include #endif - -static bool readFile(const std::string& file, std::string& out) +static bool readFile(const fs::path& file, std::string& out) { out.clear(); +#ifdef _WIN32 + FILE* f = _wfopen(file.c_str(), L"rb"); +#else FILE* f = fopen(file.c_str(), "rb"); +#endif if (!f) { return false; } @@ -56,11 +62,15 @@ static bool readFile(const std::string& file, std::string& out) return false; } -static bool writeFile(const std::string& file, const std::string& data) +static bool writeFile(const fs::path& file, const std::string& data) { +#ifdef _WIN32 + FILE* f = _wfopen(file.c_str(), L"wb"); +#else FILE* f = fopen(file.c_str(), "wb"); +#endif if (!f) { - fprintf(stderr, "Cloud not open file \"%s\" for writing: %s\n", file.c_str(), strerror(errno)); + fprintf(stderr, "Cloud not open file \"%s\" for writing: %s\n", sanitize_print(file).c_str(), strerror(errno)); return false; } size_t res = fwrite(data.c_str(), 1, data.length(), f); @@ -74,80 +84,22 @@ static const char OS_DIR_SEP = '\\'; #else static const char OS_DIR_SEP = '/'; #endif - -static std::string os_pathcat(const std::string& a, const std::string& b) -{ - if (a.empty()) return b; - if (a[a.length()-1] == OS_DIR_SEP) return a+b; - return a + OS_DIR_SEP + b; -} -template -static std::string os_pathcat(const std::string a, const std::string b, Args... args) { - return os_pathcat(os_pathcat(a,b), args...); -} - -static std::string os_dirname(const std::string& filename) -{ - auto p = filename.rfind("/"); - if (OS_DIR_SEP != '/') { - auto p2 = filename.rfind(OS_DIR_SEP); - if (p == filename.npos || (p2 != filename.npos && p2>p)) - p = p2; - } - return filename.substr(0, p); -} - -static std::string os_basename(const std::string& filename) -{ - auto p = filename.rfind("/"); - if (OS_DIR_SEP != '/') { - auto p2 = filename.rfind(OS_DIR_SEP); - if (p == filename.npos || (p2 != filename.npos && p2>p)) - p = p2; - } - return filename.substr(p+1); -} -#include -static inline bool fileExists(const char* path) -{ - struct stat st; - if (stat(path, &st) != 0) return false; - return S_ISREG(st.st_mode); -} - -static inline bool fileExists(const std::string& path) -{ - return fileExists(path.c_str()); -} - -static inline bool dirExists(const char* path) -{ - struct stat st; - if (stat(path, &st) != 0) return false; - return S_ISDIR(st.st_mode); -} - -static inline bool dirExists(const std::string& path) -{ - return dirExists(path.c_str()); -} - -static inline bool pathExists(const char* path) +static inline bool isWritable(const char* path) { - struct stat st; - if (stat(path, &st) != 0) return false; - return true; + return (access(path, W_OK) == 0); } -static inline bool pathExists(const std::string& path) +#ifdef _WIN32 +static inline bool isWritable(const wchar_t* path) { - return pathExists(path.c_str()); + return (_waccess(path, W_OK) == 0); } +#endif -static inline bool isWritable(const char* path) +static inline bool isWritable(const fs::path& path) { - return (access(path, W_OK) == 0); + return isWritable(path.c_str()); } static inline bool isWritable(const std::string& path) @@ -170,52 +122,16 @@ static std::string getCwd() #include // dirname #include -static std::string getAppPath() -{ - char result[_MAX_PATH+1]; - if (GetModuleFileNameA(NULL,result,_MAX_PATH) < 1) { - fprintf(stderr, "Warning: could not get app path!\n"); - return ""; - } - result[_MAX_PATH] = 0; - char* slash = strrchr(result, '\\'); - if (!slash) return ""; - *slash = 0; - return result; -} - -static std::string getHomePath() -{ - char result[_MAX_PATH+1]; - if (SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, SHGFP_TYPE_CURRENT, result) != 0) { - fprintf(stderr, "Warning: could not get Home path!\n"); - return ""; - } - result[_MAX_PATH] = 0; - return result; -} - -static std::string getDocumentsPath() +static fs::path getConfigPath(const std::string& appname="", const std::string& filename="", bool isPortable=false) { - char result[_MAX_PATH+1]; - if (SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, result) != 0) { - fprintf(stderr, "Warning: could not get My Documents path!\n"); - return ""; - } - result[_MAX_PATH] = 0; - return result; -} - -static std::string getConfigPath(const std::string& appname="", const std::string& filename="", bool isPortable=false) -{ - std::string res; + fs::path res; if (isPortable) { - res = os_pathcat(getAppPath(), "portable-config"); + res = fs::app_path() / "portable-config"; } else { - char result[_MAX_PATH+1]; - if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, result) != 0) { + wchar_t result[_MAX_PATH+1]; + if (SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, result) != 0) { fprintf(stderr, "Warning: could not get AppData path!\n"); - res = getHomePath(); + res = fs::home_path(); } else { result[_MAX_PATH] = 0; res = result; @@ -223,189 +139,58 @@ static std::string getConfigPath(const std::string& appname="", const std::strin } if (!appname.empty() && !filename.empty()) - return os_pathcat(res, appname, filename); - else if (!appname.empty()) - return os_pathcat(res, appname); - else if (!filename.empty()) - return os_pathcat(res, filename); + return res / appname / filename; + if (!appname.empty()) + return res / appname; + if (!filename.empty()) + return res / filename; return res; } -#else -#include // dirname -#include // struct passwd -#include // getpwuid -#ifdef MACOS -#include -static std::string getAppPath() -{ - uint32_t bufsize = PATH_MAX; - char result[PATH_MAX+1]; memset(result, 0, sizeof(result)); // to make valgrind happy - if(_NSGetExecutablePath(result, &bufsize) == -1) { - fprintf(stderr, "Warning: could not get app path!\n"); - return ""; - } - result[PATH_MAX]=0; - - char* slash = strrchr(result, '/'); - if (!slash) return ""; - *slash = 0; - return result; -} #else -static std::string getAppPath() -{ - char result[PATH_MAX+1]; memset(result, 0, sizeof(result)); // to make valgrind happy - if (readlink("/proc/self/exe", result, PATH_MAX)<0) { - fprintf(stderr, "Warning: could not get app path!\n"); - return ""; - } - result[PATH_MAX]=0; - char* slash = strrchr(result, '/'); - if (!slash) return ""; - *slash = 0; - return result; -} -#endif - -static std::string getHomePath() -{ - const char* res = getenv("HOME"); - if (res && *res) return res; - else { - struct passwd pwd; - struct passwd *ppwd; - char buf[4096]; - if (getpwuid_r(getuid(), &pwd, buf, sizeof(buf), &ppwd) != 0 || !ppwd) return ""; - return ppwd->pw_dir; - } -} - -static std::string getDocumentsPath() -{ - // this returns XDG_DOCUMENTS_DIR if defined, HOME otherwise - const char* res = getenv("XDG_DOCUMENTS_DIR"); - if (res && *res) return res; - return getHomePath(); -} -static std::string getConfigPath(const std::string& appname="", const std::string& filename="", bool isPortable=false) +static fs::path getConfigPath(const std::string& appname="", const std::string& filename="", bool isPortable=false) { // this returns XDG_CONFIG_DIR[/appname] if defined, HOME/.config[/appname] otherwise - std::string res; + fs::path res; if (isPortable) { - res = os_pathcat(getAppPath(), ".portable-config"); + res = fs::app_path() / ".portable-config"; } else { const char* tmp = getenv("XDG_CONFIG_DIR"); if (tmp && *tmp) { res = tmp; } else { - res = os_pathcat(getHomePath(), ".config"); + res = fs::home_path() / ".config"; } } if (!appname.empty() && !filename.empty()) - return os_pathcat(res, appname, filename); + return res / appname / filename; else if (!appname.empty()) - return os_pathcat(res, appname); + return res / appname; else if (!filename.empty()) - return os_pathcat(res, filename); + return res / filename; return res; } #endif -#ifdef WIN32 -#define MKDIR(a,b) mkdir(a) -#else -#define MKDIR mkdir -#endif -static int mkdir_recursive(const char *dir, mode_t mode=0750) { - if (!dir) { - errno = EINVAL; - return -1; - } - size_t len = strlen(dir); - char *tmp = (char*)malloc(len+1); - if (!tmp) { - errno = ENOMEM; - return -1; - } - memcpy(tmp, dir, len+1); - if(tmp[len-1]=='/' || tmp[len-1]==OS_DIR_SEP) tmp[len-1] = 0; - - for(char *p = tmp + 1; *p; p++) { - if(*p=='/' || *p==OS_DIR_SEP) { - char c = *p; - *p = 0; - MKDIR(tmp, mode); - *p = c; - } - } - int res = MKDIR(tmp, mode); - free(tmp); - return res; -} - -static inline int mkdir_recursive(const std::string& dir, mode_t mode=0750) { - return mkdir_recursive(dir.c_str(), mode); -} - -static int os_copyfile(const char* src, const char* dst) +static bool copy_recursive(const fs::path& src, const fs::path& dst, fs::error_code& ec) { -#ifdef WIN32 - if (!CopyFileA(src, dst, 0)) return -1; - return 0; -#else - int input, output; - if ((input = open(src, O_RDONLY)) == -1) - { - return -1; - } - if ((output = creat(dst, 0660)) == -1) - { - close(input); - return -1; - } - -#if defined(__APPLE__) || defined(__FreeBSD__) - int result = fcopyfile(input, output, 0, COPYFILE_ALL); -#else - off_t bytesCopied = 0; - struct stat fileinfo = {0}; - fstat(input, &fileinfo); - int result = sendfile(output, input, &bytesCopied, fileinfo.st_size); -#endif - - close(input); - close(output); - - return result; -#endif + fs::copy(src, dst, fs::copy_options::overwrite_existing | fs::copy_options::recursive, ec); + return !ec; } -static bool copy_recursive(const char* src, const char* dst); -static bool copy_recursive(const char* src, const char* dst) +#ifdef _WIN32 +static bool getFileMTime(const wchar_t* path, std::chrono::system_clock::time_point& tp) { - if (fileExists(src)) { - return (os_copyfile(src, dst) >= 0); - } else if (dirExists(src)) { - mkdir_recursive(dst); - DIR *d = opendir(src); - if (!d) return false; - struct dirent *dir; - while ((dir = readdir(d)) != NULL) - { - if (strcmp(dir->d_name,".")==0 || strcmp(dir->d_name,"..")==0) continue; - auto fsrc = os_pathcat(src, dir->d_name); - auto fdst = os_pathcat(dst, dir->d_name); - if (!copy_recursive(fsrc.c_str(), fdst.c_str())) return false; - } - closedir(d); - return true; - } - return false; + struct _stat64 st; + if (_wstat64(path, &st)) + return false; + auto duration = std::chrono::seconds(st.st_mtime); + tp = std::chrono::system_clock::time_point(duration); + return true; } - +#else static bool getFileMTime(const char* path, std::chrono::system_clock::time_point& tp) { struct stat st; @@ -414,15 +199,18 @@ static bool getFileMTime(const char* path, std::chrono::system_clock::time_point tp = std::chrono::system_clock::time_point(duration); return true; } +#endif -static bool getFileMTime(const std::string& path, std::chrono::system_clock::time_point& tp) +static bool getFileMTime(const fs::path& path, std::chrono::system_clock::time_point& tp) { return getFileMTime(path.c_str(), tp); } +#define pathFromUTF8(s) fs::u8path(s) + #ifdef WIN32 -// for now we use ANSI paths on windows, so we have to convert ANSI -> UTF8 -static std::string pathToUTF8(const std::string& s) +// we use ANSI paths on windows in some places, so we have to convert ANSI -> UTF8 +static std::string localToUTF8(const std::string& s) { std::wstring wbuf; std::string res; @@ -447,36 +235,9 @@ static std::string pathToUTF8(const std::string& s) res.resize(len - 1); // cut terminating NUL return res; } - -static std::string pathFromUTF8(const std::string& s) -{ - std::wstring wbuf; - std::string res; - // utf8 -> wstring - int wlen = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); - if (wlen < 1) // error - return res; - wbuf.resize(wlen); // resize to incl NUL, since the implicit NUL can not be written - wlen = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, wbuf.data(), wbuf.size()); - if (wlen < 1) // error - return res; - wbuf.resize(wlen - 1); // cut terminating NUL - // wstring -> ansi - int len = WideCharToMultiByte(CP_ACP, 0, wbuf.c_str(), -1, NULL, 0, NULL, NULL); - if (len < 1) // error - return res; - res.resize(len); - len = WideCharToMultiByte(CP_ACP, 0, wbuf.c_str(), -1, res.data(), res.size(), NULL, NULL); - if (len < 1) // error - res.resize(0); - else - res.resize(len - 1); // cut terminating NUL - return res; -} #else // non-WIN32 // on non-windows paths are expected to be UTF8 -#define pathToUTF8(s) (s) -#define pathFromUTF8(s) (s) +#define localToUTF8(s) (s) #endif #endif // _CORE_FILEUTIL_H diff --git a/src/core/fs.h b/src/core/fs.h new file mode 100644 index 00000000..e24d4c9b --- /dev/null +++ b/src/core/fs.h @@ -0,0 +1,242 @@ +#pragma once + +//#define NO_STD_FILESYSTEM // define to force using boost::filesystem + +#if !defined(NO_STD_FILESYSTEM) && !defined(FS_USE_STD_FILESYSTEM) +#ifdef __has_include +# if __has_include() +# define FS_USE_STD_FILESYSTEM +# endif +#endif +#endif + + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#if defined __APPLE__ +#include +#elif defined _WIN32 +#include +#include +#endif + + +#ifdef FS_USE_STD_FILESYSTEM + +#include +#include + +namespace fs { + using std::filesystem::path; + using std::filesystem::current_path; + using std::filesystem::is_regular_file; + using std::filesystem::equivalent; + using std::filesystem::u8path; + using std::filesystem::create_directories; + using std::filesystem::exists; + using std::filesystem::remove; + using std::filesystem::rename; + using std::filesystem::perms; + using std::filesystem::status; + using std::filesystem::is_directory; + using std::filesystem::last_write_time; + using std::filesystem::directory_iterator; + using std::filesystem::recursive_directory_iterator; + using std::filesystem::copy; + using std::filesystem::copy_options; + using std::error_code; +} + +#else + +#include +#include + +namespace fs { + using boost::filesystem::current_path; + using boost::filesystem::is_regular_file; + using boost::filesystem::equivalent; + using boost::filesystem::create_directories; + using boost::filesystem::exists; + using boost::filesystem::remove; + using boost::filesystem::rename; + using boost::filesystem::perms; + using boost::filesystem::status; + using boost::filesystem::is_directory; + using boost::filesystem::last_write_time; + using boost::filesystem::directory_iterator; + using boost::filesystem::recursive_directory_iterator; + using boost::filesystem::copy; + using boost::filesystem::copy_options; + using boost::system::error_code; + + ///* + class path : public boost::filesystem::path { + public: + path() {} + path(const std::string& s) : boost::filesystem::path(s) {} + path(const char* s) : boost::filesystem::path(s) {} + path(const boost::filesystem::path& path) : boost::filesystem::path(path) {} + + std::string u8string() const + { +#ifdef _WIN32 +#error "Not implemented" +#else + // we assume utf8 on non-windows + return string(); +#endif + } + + std::string string() const + { +#ifdef _WIN32 +#error "Not implemented" +#else + // we assume utf8 on non-windows + return boost::filesystem::path::string(); +#endif + } + + path parent_path() const + { + return boost::filesystem::path::parent_path(); + } + }; + //*/ + + inline path operator/(path lhs, boost::filesystem::path const& rhs) + { + lhs.append(rhs); + return lhs; + } + + inline path operator/(path lhs, const std::string& rhs) + { + lhs.append(rhs); + return lhs; + } + + inline path operator/(path lhs, const char* rhs) + { + lhs.append(rhs); + return lhs; + } + + template + inline path u8path(const SOURCE& s) + { +#ifdef _WIN32 + auto wideStr = std::wstring_convert, char16_t>{}.from_bytes(s); + return path(wideStr); +#else + // we assume utf8 on non-windows + return s; +#endif + } +} + +#endif + + +// extensions to std::filesystem +namespace fs { + inline path app_path() + { +#if defined _WIN32 + wchar_t result[_MAX_PATH+1]; + if (GetModuleFileNameW(nullptr, result, _MAX_PATH) < 1) { + fprintf(stderr, "Warning: could not get app path!\n"); + return ""; + } + result[_MAX_PATH] = 0; + wchar_t* slash = wcsrchr(result, L'\\'); +#elif defined __APPLE__ + uint32_t bufsize = PATH_MAX; + char result[PATH_MAX+1]; + memset(result, 0, sizeof(result)); // to make valgrind happy + if(_NSGetExecutablePath(result, &bufsize) == -1) { + fprintf(stderr, "Warning: could not get app path!\n"); + return ""; + } + result[PATH_MAX] = 0; + + char* slash = strrchr(result, '/'); +#else + char result[PATH_MAX+1]; + memset(result, 0, sizeof(result)); // to make valgrind happy + if (readlink("/proc/self/exe", result, PATH_MAX)<0) { + fprintf(stderr, "Warning: could not get app path!\n"); + return ""; + } + result[PATH_MAX] = 0; + char* slash = strrchr(result, '/'); +#endif + if (!slash) + return ""; + *slash = 0; + return result; + } + + inline path home_path() + { +#ifdef _WIN32 + wchar_t result[_MAX_PATH+1]; + if (SHGetFolderPathW(nullptr, CSIDL_PROFILE, nullptr, SHGFP_TYPE_CURRENT, result) != 0) { + fprintf(stderr, "Warning: could not get Home path!\n"); + return ""; + } + result[_MAX_PATH] = 0; + return result; +#else + const char* res = getenv("HOME"); + if (res && *res) { + return res; + } else { + struct passwd pwd; + struct passwd *ppwd; + char buf[4096]; + if (getpwuid_r(getuid(), &pwd, buf, sizeof(buf), &ppwd) != 0 || !ppwd) + return ""; + return ppwd->pw_dir; + } +#endif + } + + inline path documents_path() + { +#ifdef _WIN32 + wchar_t result[_MAX_PATH+1]; + if (SHGetFolderPathW(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, result) != 0) { + fprintf(stderr, "Warning: could not get My Documents path!\n"); + return ""; + } + result[_MAX_PATH] = 0; + return result; +#else + // this returns XDG_DOCUMENTS_DIR if defined, HOME otherwise + const char* res = getenv("XDG_DOCUMENTS_DIR"); + if (res && *res) + return res; + return home_path(); +#endif + } + + inline bool is_sub_path(path p, const path& root) + { + static const path emptyPath; + while (p != emptyPath) { + if (equivalent(p, root)) { + return true; + } + p = p.parent_path(); + } + return false; + } +} diff --git a/src/core/log.cpp b/src/core/log.cpp index 486735bf..caa05481 100644 --- a/src/core/log.cpp +++ b/src/core/log.cpp @@ -1,7 +1,9 @@ #include "log.h" +#include "fs.h" +#include "util.h" -std::string Log::_logFile = ""; +fs::path Log::_logFile = ""; int Log::_oldOut = -1; int Log::_oldErr = -1; int Log::_newOut = -1; @@ -17,16 +19,19 @@ int Log::_newErr = -1; #include #include -bool Log::RedirectStdOut(const std::string& file, bool truncate) { +bool Log::RedirectStdOut(const fs::path& file, bool truncate) { fflush(stdout); fflush(stderr); _logFile = file; UnredirectStdOut(); +#ifdef _WIN32 + _newOut = _wopen(_logFile.c_str(), O_RDWR|O_CREAT|O_APPEND|(truncate ? O_TRUNC : 0), 0655); +#else _newOut = open(_logFile.c_str(), O_RDWR|O_CREAT|O_APPEND|(truncate ? O_TRUNC : 0), 0655); +#endif _newErr = dup(_newOut); if (_newOut == -1) { - fprintf(stderr, "Could not open %s for logging!\n", - _logFile.c_str()); + fprintf(stderr, "Could not open %s for logging!\n", sanitize_print(_logFile).c_str()); return false; } _oldErr = dup(fileno(stderr)); @@ -34,7 +39,11 @@ bool Log::RedirectStdOut(const std::string& file, bool truncate) { // if stdout has no underlying handle, use freopen (fileno(stdout) is -2 on windows) if (_oldOut < 0) { +#ifdef _WIN32 + _wfreopen(_logFile.c_str(), L"w", stdout); +#else freopen(_logFile.c_str(), "w", stdout); +#endif setvbuf(stdout, NULL, _IONBF, 0); // _IOLBF close(_newOut); _newOut = fileno(stdout); @@ -59,7 +68,11 @@ bool Log::RedirectStdOut(const std::string& file, bool truncate) { // if stderr has no underlying handle, use freopen if (_oldErr < 0 || _newErr < 0) { +#ifdef _WIN32 + _wfreopen(_logFile.c_str(), L"w", stderr); +#else freopen(_logFile.c_str(), "w", stderr); +#endif setvbuf(stderr, NULL, _IONBF, 0); close(_newErr); _newErr = fileno(stderr); diff --git a/src/core/log.h b/src/core/log.h index 47736387..3b7c159b 100644 --- a/src/core/log.h +++ b/src/core/log.h @@ -2,18 +2,19 @@ #define LOG_H #include +#include "fs.h" class Log final { private: - static std::string _logFile; + static fs::path _logFile; static int _oldOut; static int _oldErr; static int _newOut; static int _newErr; public: - static std::string getFile() { return _logFile; } - static bool RedirectStdOut(const std::string& file, bool truncate=true); + static const fs::path& getFile() { return _logFile; } + static bool RedirectStdOut(const fs::path& file, bool truncate=true); static void UnredirectStdOut(); diff --git a/src/core/pack.cpp b/src/core/pack.cpp index 9001fcb9..3e6d3935 100644 --- a/src/core/pack.cpp +++ b/src/core/pack.cpp @@ -6,6 +6,10 @@ #include "sha256.h" #include "util.h" +#ifdef _WIN32 +#include +#endif + using nlohmann::json; @@ -41,7 +45,8 @@ static bool sanitizePath(const std::string& userfile, std::string& file) return true; } -static std::string replaceAll(std::string str, const std::string& from, const std::string& to) { +template +static T replaceAll(T str, const T& from, const T& to) { size_t start_pos = 0; while((start_pos = str.find(from, start_pos)) != std::string::npos) { str.replace(start_pos, from.length(), to); @@ -50,22 +55,24 @@ static std::string replaceAll(std::string str, const std::string& from, const st return str; } -static std::string cleanUpPath(const std::string& path) +static fs::path cleanUpPath(const fs::path& path) { + using namespace std::string_literals; + #if defined WIN32 || defined _WIN32 - char buf[_MAX_PATH]; - char* p = _fullpath(buf, path.c_str(), sizeof(buf)); - std::string s = p ? (std::string)p : path; - s = replaceAll(replaceAll(s, "//", "/"), "\\\\", "\\"); - if (s[s.length()-1] == '/' || s[s.length()-1] == '\\') + wchar_t buf[_MAX_PATH]; + auto p = _wfullpath(buf, path.c_str(), sizeof(buf)); + std::wstring s = p ? p : path.c_str(); + s = replaceAll(replaceAll(s, L"//"s, L"/"s), L"\\\\"s, L"\\"s); + if (s[s.length()-1] == L'/' || s[s.length()-1] == L'\\') s.pop_back(); #else char* p = realpath(path.c_str(), nullptr); - std::string s = p ? (std::string)p : path; + std::string s = p ? p : path.c_str(); if (p) free(p); p = nullptr; - s = replaceAll(s, "//", "/"); + s = replaceAll(s, "//"s, "/"s); if (s[s.length()-1] == '/') s.pop_back(); #endif @@ -84,35 +91,32 @@ static bool fileNewerThan(const std::string& path, const std::chrono::system_clo return (!getFileMTime(path, tp) || tp > than); // assume changed on error } -static bool dirNewerThan(const char* path, const std::chrono::system_clock::time_point& than) +static bool fileNewerThan(const fs::path& path, const std::chrono::system_clock::time_point& than) { - DIR *d = opendir(path); - if (!d) return true; // error -> assume changed - struct dirent *dir; - while ((dir = readdir(d)) != NULL) - { - if (strcmp(dir->d_name,".")==0 || strcmp(dir->d_name,"..")==0) continue; - auto f = os_pathcat(path, dir->d_name); - struct stat st; - if (stat(f.c_str(), &st) != 0) return true; - if (S_ISDIR(st.st_mode) && dirNewerThan(f.c_str(), than)) return true; - else if (!S_ISDIR(st.st_mode) && fileNewerThan(&st, than)) return true; + std::chrono::system_clock::time_point tp; + return (!getFileMTime(path, tp) || tp > than); // assume changed on error +} + +static bool dirNewerThan(const fs::path& path, const std::chrono::system_clock::time_point& than) +{ + for (const auto& dirEntry: fs::recursive_directory_iterator{path}) { + if ((dirEntry.is_regular_file() || dirEntry.is_symlink()) && fileNewerThan(dirEntry.path(), than)) + return true; } - closedir(d); return false; } -std::vector Pack::_searchPaths; -std::vector Pack::_overrideSearchPaths; +std::vector Pack::_searchPaths; +std::vector Pack::_overrideSearchPaths; -Pack::Pack(const std::string& path) : _zip(nullptr), _path(path), _override(nullptr) +Pack::Pack(const fs::path& path) : _zip(nullptr), _path(path), _override(nullptr) { _loaded = std::chrono::system_clock::now(); std::string s; - if (fileExists(path)) { - _zip = new Zip(path); + if (fs::is_regular_file(path)) { + _zip = new Zip(path.u8string().c_str()); auto root = _zip->list(); if (root.size() == 1 && root[0].first == Zip::EntryType::DIR) { _zip->setDir(root[0].second); @@ -121,7 +125,7 @@ Pack::Pack(const std::string& path) : _zip(nullptr), _path(path), _override(null if (_zip->readFile("manifest.json", s, err)) { _manifest = parse_jsonc(s); } else { - fprintf(stderr, "%s: could not read manifest.json: %s\n", path.c_str(), err.c_str()); + fprintf(stderr, "%s: could not read manifest.json: %s\n", sanitize_print(path).c_str(), err.c_str()); } } else { if (ReadFile("manifest.json", s)) { @@ -139,8 +143,8 @@ Pack::Pack(const std::string& path) : _zip(nullptr), _path(path), _override(null if (!_uid.empty()) { for (const auto& path: _overrideSearchPaths) { - std::string overridePath = os_pathcat(path, _uid); - if (dirExists(overridePath)) { + auto overridePath = path / _uid; + if (fs::is_directory(overridePath)) { _override = new Override(overridePath); break; } @@ -200,14 +204,14 @@ bool Pack::hasFile(const std::string& userfile) const return false; if (_zip) { - if (!_variant.empty() && _zip->hasFile(_variant+"/"+file)) + if (!_variant.empty() && _zip->hasFile(_variant + "/" + file)) return true; if (_zip->hasFile(file)) return true; } else { - if (!_variant.empty() && fileExists(os_pathcat(_path,_variant,file))) + if (!_variant.empty() && fs::is_regular_file(_path / fs::u8path(_variant) / fs::u8path(file))) return true; - if (fileExists(os_pathcat(_path,file))) + if (fs::is_regular_file(_path / fs::u8path(file))) return true; } return false; @@ -223,18 +227,18 @@ bool Pack::ReadFile(const std::string& userfile, std::string& out) const bool packHasVariantFile = false; if (!_variant.empty()) { if (_zip) - packHasVariantFile = _zip->hasFile(_variant+"/"+file); + packHasVariantFile = _zip->hasFile(_variant + "/" + file); else - packHasVariantFile = fileExists(os_pathcat(_path,_variant,file)); + packHasVariantFile = fs::is_regular_file(_path / fs::u8path(_variant) / fs::u8path(file)); } - if (packHasVariantFile && _override->ReadFile(_variant+"/"+file, out)) + if (packHasVariantFile && _override->ReadFile(_variant + "/" + file, out)) return true; else if ((!packHasVariantFile || _variant.empty()) && _override->ReadFile(file, out)) return true; } if (_zip) { - if (!_variant.empty() && _zip->readFile(_variant+"/"+file, out)) + if (!_variant.empty() && _zip->readFile(_variant + "/" + file, out)) return true; if (_zip->readFile(file, out)) return true; @@ -242,12 +246,21 @@ bool Pack::ReadFile(const std::string& userfile, std::string& out) const } else { out.clear(); FILE* f = nullptr; +#ifdef _WIN32 if (!_variant.empty()) { - f = fopen(os_pathcat(_path,_variant,file).c_str(), "rb"); + f = _wfopen((_path / fs::u8path(_variant) / fs::u8path(file)).c_str(), L"rb"); } if (!f) { - f = fopen(os_pathcat(_path,file).c_str(), "rb"); + f = _wfopen((_path / fs::u8path(file)).c_str(), L"rb"); } +#else + if (!_variant.empty()) { + f = fopen((_path / fs::u8path(_variant) / fs::u8path(file)).c_str(), "rb"); + } + if (!f) { + f = fopen((_path / fs::u8path(file)).c_str(), "rb"); + } +#endif if (!f) { return false; } @@ -367,9 +380,9 @@ bool Pack::hasFilesChanged() const if (_override && _override->hasFilesChanged(_loaded)) return true; if (_zip) - return fileNewerThan(_path, _loaded); + return fileNewerThan(_path, _loaded); // TODO: fs::last_write_time + time_point_cast once we use C+++20 else - return dirNewerThan(_path.c_str(), _loaded); + return dirNewerThan(_path, _loaded); } std::string Pack::getSHA256() const @@ -384,20 +397,14 @@ std::vector Pack::ListAvailable() { std::vector res; for (auto& searchPath: _searchPaths) { - DIR *d = opendir(searchPath.c_str()); - if (!d && errno != ENOENT) - fprintf(stderr, "Packs: could not open %s: %s\n", searchPath.c_str(), strerror(errno)); - if (!d) + if (!fs::is_directory(searchPath)) continue; - struct dirent *dir; - while ((dir = readdir(d)) != NULL) - { - Pack pack(os_pathcat(searchPath, dir->d_name)); + for (auto const& dirEntry : fs::directory_iterator{searchPath}) { + Pack pack(dirEntry.path()); if (!pack.isValid()) continue; res.push_back(pack.getInfo()); } - closedir(d); } std::sort(res.begin(), res.end(), [](const Pack::Info& lhs, const Pack::Info& rhs) { @@ -419,26 +426,24 @@ Pack::Info Pack::Find(const std::string& uid, const std::string& version, const { std::vector packs; for (auto& searchPath: _searchPaths) { - DIR *d = opendir(searchPath.c_str()); - if (!d) continue; - struct dirent *dir; - while ((dir = readdir(d)) != NULL) { - Pack pack(os_pathcat(searchPath, dir->d_name)); - if (pack.isValid() && pack.getUID() == uid) { - if (!sha256.empty()) { - // require exact match if hash is given - if ((version.empty() || pack.getVersion() == version) && - strcasecmp(pack.getSHA256().c_str(), sha256.c_str()) == 0) { - return pack.getInfo(); // return exact match - } - } else if (!version.empty() && pack.getVersion() == version) { - return pack.getInfo(); // return exact version match - } else { - packs.push_back(pack.getInfo()); + if (!fs::is_directory(searchPath)) + continue; + for (auto const& dirEntry : fs::directory_iterator{searchPath}) { + Pack pack(dirEntry.path()); + if (!pack.isValid() || pack.getUID() != uid) + continue; + if (!sha256.empty()) { + // require exact match if hash is given + if ((version.empty() || pack.getVersion() == version) && + strcasecmp(pack.getSHA256().c_str(), sha256.c_str()) == 0) { + return pack.getInfo(); // return exact match } + } else if (!version.empty() && pack.getVersion() == version) { + return pack.getInfo(); // return exact version match + } else { + packs.push_back(pack.getInfo()); } } - closedir(d); } if (!packs.empty()) { @@ -453,9 +458,10 @@ Pack::Info Pack::Find(const std::string& uid, const std::string& version, const return {}; } -static void addPath(const std::string& path, std::vector& paths) +static void addPath(const fs::path& path, std::vector& paths) { #if !defined WIN32 && !defined _WIN32 + // TODO: canonical? char* tmp = realpath(path.c_str(), NULL); if (tmp) { std::string real = tmp; @@ -465,9 +471,10 @@ static void addPath(const std::string& path, std::vector& paths) paths.push_back(real); } else #else - char* tmp = _fullpath(NULL, path.c_str(), 1024); + // TODO: canonical? + auto tmp = _wfullpath(nullptr, path.c_str(), 1024); if (tmp) { - auto cmp = [tmp](const std::string& s) { return strcasecmp(tmp, s.c_str()) == 0; }; + auto cmp = [tmp](const fs::path& p) { return wcsicmp(tmp, p.c_str()) == 0; }; if (std::find_if(paths.begin(), paths.end(), cmp) != paths.end()) return; paths.push_back(tmp); @@ -481,33 +488,34 @@ static void addPath(const std::string& path, std::vector& paths) } } -void Pack::addSearchPath(const std::string& path) +void Pack::addSearchPath(const fs::path& path) { addPath(path, _searchPaths); } -bool Pack::isInSearchPath(const std::string& uncleanPath) +bool Pack::isInSearchPath(const fs::path& uncleanPath) { - std::string path = cleanUpPath(uncleanPath); + auto path = cleanUpPath(uncleanPath); for (const auto& uncleanSearchPath: _searchPaths) { - std::string searchPath = cleanUpPath(uncleanSearchPath) + OS_DIR_SEP; - if (strncmp(path.c_str(), searchPath.c_str(), searchPath.length()) == 0) + auto searchPath = cleanUpPath(uncleanSearchPath); + // TODO: .make_preferred().canonical() instead + if (fs::is_sub_path(path, searchPath)) return true; } return false; } -const std::vector& Pack::getSearchPaths() +const std::vector& Pack::getSearchPaths() { return _searchPaths; } -void Pack::addOverrideSearchPath(const std::string& path) +void Pack::addOverrideSearchPath(const fs::path& path) { addPath(path, _overrideSearchPaths); } -Pack::Override::Override(const std::string& path) +Pack::Override::Override(const fs::path& path) : _path(path) { } @@ -523,10 +531,12 @@ bool Pack::Override::ReadFile(const std::string& userfile, std::string& out) con return false; out.clear(); - FILE* f = nullptr; - if (!f) { - f = fopen(os_pathcat(_path,file).c_str(), "rb"); - } + FILE* f = +#ifdef _WIN32 + _wfopen((_path / fs::u8path(file)).c_str(), L"rb"); +#else + fopen((_path / fs::u8path(file)).c_str(), "rb"); +#endif if (!f) { return false; } @@ -547,5 +557,5 @@ bool Pack::Override::ReadFile(const std::string& userfile, std::string& out) con bool Pack::Override::hasFilesChanged(std::chrono::system_clock::time_point since) const { - return dirNewerThan(_path.c_str(), since); + return dirNewerThan(_path, since); } diff --git a/src/core/pack.h b/src/core/pack.h index 939e6dc0..9ee3e0c2 100644 --- a/src/core/pack.h +++ b/src/core/pack.h @@ -6,6 +6,8 @@ #include #include #include + +#include "fs.h" #include "zip.h" #include "version.h" @@ -17,7 +19,7 @@ class Pack final { std::string name; }; struct Info { - std::string path; + fs::path path; std::string uid; std::string version; std::string platform; @@ -27,7 +29,7 @@ class Pack final { std::vector variants; }; - Pack(const std::string& path); + Pack(const fs::path& path); virtual ~Pack(); bool isValid() const @@ -37,7 +39,7 @@ class Pack final { } void setVariant(const std::string& variant); - const std::string& getPath() const { return _path; } + const fs::path& getPath() const { return _path; } const std::string& getUID() const { return _uid; } const std::string& getVariant() const { return _variant; } const std::string& getName() const { return _name; } @@ -63,28 +65,28 @@ class Pack final { static std::vector ListAvailable(); static Info Find(const std::string& uid, const std::string& version="", const std::string& sha256=""); - static void addSearchPath(const std::string& path); - static bool isInSearchPath(const std::string& path); - static const std::vector& getSearchPaths(); + static void addSearchPath(const fs::path& path); + static bool isInSearchPath(const fs::path& path); + static const std::vector& getSearchPaths(); - static void addOverrideSearchPath(const std::string& path); + static void addOverrideSearchPath(const fs::path& path); private: class Override { public: - Override(const std::string& path); + Override(const fs::path& path); virtual ~Override(); bool ReadFile(const std::string& file, std::string& out) const; bool hasFilesChanged(std::chrono::system_clock::time_point since) const; - const std::string& getPath() const { return _path; } + const fs::path& getPath() const { return _path; } private: - std::string _path; + fs::path _path; }; Zip* _zip; - std::string _path; + fs::path _path; std::string _variant; std::string _uid; std::string _name; @@ -99,8 +101,8 @@ class Pack final { std::chrono::system_clock::time_point _loaded; - static std::vector _searchPaths; - static std::vector _overrideSearchPaths; + static std::vector _searchPaths; + static std::vector _overrideSearchPaths; }; #endif // _CORE_PACK_H diff --git a/src/core/scripthost.h b/src/core/scripthost.h index b12849e5..027af74f 100644 --- a/src/core/scripthost.h +++ b/src/core/scripthost.h @@ -4,8 +4,8 @@ #include #include #include -#include "pack.h" #include "autotracker.h" +#include "pack.h" #include "tracker.h" #include "luaitem.h" #include diff --git a/src/core/sha256.cpp b/src/core/sha256.cpp index 1f8b1e31..98e5e638 100644 --- a/src/core/sha256.cpp +++ b/src/core/sha256.cpp @@ -4,7 +4,7 @@ #include -std::string SHA256_File(const std::string& file) +std::string SHA256_File(const fs::path& file) { std::string res = ""; EVP_MD_CTX* context = nullptr; @@ -13,7 +13,11 @@ std::string SHA256_File(const std::string& file) uint8_t hash[EVP_MAX_MD_SIZE]; unsigned int hashLen = 0; +#ifdef _WIN32 + FILE* f = _wfopen(file.c_str(), L"rb"); +#else FILE* f = fopen(file.c_str(), "rb"); +#endif if (!f) goto err_fopen; context = EVP_MD_CTX_new(); if(context == NULL) goto err_alloc; diff --git a/src/core/sha256.h b/src/core/sha256.h index f6014083..611bedc9 100644 --- a/src/core/sha256.h +++ b/src/core/sha256.h @@ -2,7 +2,8 @@ #define _CORE_SHA256_H #include +#include "fs.h" -std::string SHA256_File(const std::string& file); +std::string SHA256_File(const fs::path& file); #endif // _CORE_SHA256_H diff --git a/src/core/statemanager.cpp b/src/core/statemanager.cpp index 9afcb113..c60d24ff 100644 --- a/src/core/statemanager.cpp +++ b/src/core/statemanager.cpp @@ -6,9 +6,9 @@ using nlohmann::json; std::map StateManager::_states; -std::string StateManager::_dir; +fs::path StateManager::_dir; -void StateManager::setDir(const std::string& dir) +void StateManager::setDir(const fs::path& dir) { _dir = dir; } @@ -42,7 +42,7 @@ bool StateManager::saveState(Tracker* tracker, ScriptHost*, } state["ui_hints"] = jUiHints; state["pack"] = { - { "path", pathToUTF8(pack->getPath()) }, + { "path", pack->getPath().u8string() }, { "uid", pack->getUID() }, { "variant", pack->getVariant() }, { "version", pack->getVersion() }, @@ -55,19 +55,19 @@ bool StateManager::saveState(Tracker* tracker, ScriptHost*, pack->getVariant(), name }] = state; return true; } else { - std::string filename; + fs::path filename; if (external) { filename = name; } else { - auto dirname = os_pathcat(_dir, - sanitize_dir(pack->getUID()), - sanitize_dir(pack->getVersion()), - sanitize_dir(pack->getVariant())); - mkdir_recursive(dirname.c_str()); - filename = os_pathcat(dirname, name+".json"); + auto dirname = _dir / + sanitize_dir(pack->getUID()) / + sanitize_dir(pack->getVersion()) / + sanitize_dir(pack->getVariant()); + fs::create_directories(dirname); + filename = dirname / (name + ".json"); } printf("Saving state \"%s\" to file %s...\n", - external ? "export" : name.c_str(), filename.c_str()); + external ? "export" : name.c_str(), sanitize_print(filename).c_str()); try { std::string new_state = state.dump(); std::string old_state; @@ -102,12 +102,12 @@ bool StateManager::loadState(Tracker* tracker, ScriptHost* scripthost, json& ext extra_out = it->second["extra"]; } else { std::string s; - std::string filename = external ? name : os_pathcat(_dir, - sanitize_dir(pack->getUID()), - sanitize_dir(pack->getVersion()), - sanitize_dir(pack->getVariant()), name+".json"); + fs::path filename = external ? fs::u8path(name) : (_dir / + sanitize_dir(pack->getUID()) / + sanitize_dir(pack->getVersion()) / + sanitize_dir(pack->getVariant()) / (name + ".json")); printf("Loading state \"%s\" from file %s...", - external ? "import" : name.c_str(), filename.c_str()); + external ? "import" : name.c_str(), sanitize_print(filename).c_str()); if (!readFile(filename, s)) { printf(" missing\n"); return false; @@ -143,10 +143,10 @@ json StateManager::getStateExtra(Tracker* tracker, } } else { std::string s; - std::string filename = external ? name : os_pathcat(_dir, - sanitize_dir(pack->getUID()), - sanitize_dir(pack->getVersion()), - sanitize_dir(pack->getVariant()), name+".json"); + fs::path filename = external ? fs::path(name) : _dir / + sanitize_dir(pack->getUID()) / + sanitize_dir(pack->getVersion()) / + sanitize_dir(pack->getVariant()) / (name+".json"); if (readFile(filename, s)) { auto j = parse_jsonc(s); if (j.is_object()) diff --git a/src/core/statemanager.h b/src/core/statemanager.h index bd5c508e..8657e9b4 100644 --- a/src/core/statemanager.h +++ b/src/core/statemanager.h @@ -3,8 +3,8 @@ #include #include -#include "tracker.h" #include "scripthost.h" +#include "tracker.h" #include class StateManager { @@ -20,7 +20,7 @@ class StateManager { static json getStateExtra(Tracker* tracker, bool file=false, const std::string& name="autosave", bool external=false); - static void setDir(const std::string& dir); + static void setDir(const fs::path& dir); private: struct StateID { @@ -43,7 +43,7 @@ class StateManager { }; static std::map _states; - static std::string _dir; + static fs::path _dir; }; #endif /* _CORE_STATEMANAGER_H */ diff --git a/src/core/util.h b/src/core/util.h index c371078e..a84897e8 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -4,6 +4,7 @@ #include #include #include +#include "fs.h" template< class Type, ptrdiff_t n > @@ -15,6 +16,16 @@ static std::string sanitize_print(const std::string& s) { return res; } +static std::string sanitize_print(const char* s) +{ + return sanitize_print(std::string(s)); +} + +static std::string sanitize_print(const fs::path& path) +{ + return sanitize_print(path.u8string()); +} + /// Replaces reserved/non-portable symbols by '_'. static std::string sanitize_filename(std::string s) { auto exclude = "<>:\"/\\|?*$'`"; diff --git a/src/core/zip.cpp b/src/core/zip.cpp index 58335b23..1dcbb8b8 100644 --- a/src/core/zip.cpp +++ b/src/core/zip.cpp @@ -1,19 +1,21 @@ #include "zip.h" // as long as we only use miniz here, we can just #include it -extern "C" { +//extern "C" { #include -} +//} #include + +#include "fs.h" #include "string.h" -Zip::Zip(const std::string& filename) +Zip::Zip(const fs::path& filename) { _slashes = Slashes::UNKNOWN; mz_zip_zero_struct(&_zip); - if (!mz_zip_reader_init_file(&_zip, filename.c_str(), 0)) { + if (!mz_zip_reader_init_file(&_zip, filename.u8string().c_str(), 0)) { _valid = false; return; } diff --git a/src/core/zip.h b/src/core/zip.h index 6e017bdf..c837ac6e 100644 --- a/src/core/zip.h +++ b/src/core/zip.h @@ -6,6 +6,8 @@ #include #include +#include "fs.h" + class Zip final { public: @@ -14,7 +16,7 @@ class Zip final DIR }; - Zip(const std::string& filename); + Zip(const fs::path& filename); virtual ~Zip(); void setDir(const std::string&); diff --git a/src/http/httpcache.cpp b/src/http/httpcache.cpp index f8e4189f..8aa98791 100644 --- a/src/http/httpcache.cpp +++ b/src/http/httpcache.cpp @@ -25,10 +25,10 @@ std::string HTTPCache::GetRandomName(const std::string& suffix, int len) } -HTTPCache::HTTPCache(asio::io_service *asio, const std::string& cachefile, const std::string& cachedir, const std::list& httpDefaultHeaders) +HTTPCache::HTTPCache(asio::io_service *asio, const fs::path& cachefile, const fs::path& cachedir, const std::list& httpDefaultHeaders) : _asio(asio), _cachefile(cachefile), _cachedir(cachedir), _httpDefaultHeaders(httpDefaultHeaders) { - mkdir_recursive(_cachedir.c_str()); + fs::create_directories(_cachedir); std::string s; try { if (readFile(_cachefile, s)) { @@ -57,11 +57,11 @@ void HTTPCache::GetCached(const std::string& url, std::function() > time(NULL)-_minAge && // less than 60 seconds old cacheIt.value()["timestamp"].get() < time(NULL)+30 && // less than 30 seconds in the future - readFile(os_pathcat(_cachedir, cacheIt.value()["file"].get()), s)) { + readFile(_cachedir / fs::u8path(cacheIt.value()["file"].get()), s)) { cb(true, s); return; } else if (cacheIt.value()["etag"].is_string() && cacheIt.value()["file"].is_string() && - fileExists(os_pathcat(_cachedir, cacheIt.value()["file"].get()))) { + fs::is_regular_file(_cachedir / fs::u8path(cacheIt.value()["file"].get()))) { headers.push_back("If-None-Match: " + cacheIt.value()["etag"].get()); } else { _cache.erase(cacheIt); @@ -78,8 +78,9 @@ void HTTPCache::GetCached(const std::string& url, std::function()); - unlink(oldpath.c_str()); + auto oldPath = _cachedir / fs::u8path(oldCacheIt.value()["file"].get()); + fs::error_code ec; + fs::remove(oldPath, ec); } _cache.erase(oldCacheIt); flushCache = true; @@ -91,8 +92,12 @@ void HTTPCache::GetCached(const std::string& url, std::function=0) break; } if (fd >= 0 && write(fd, r.c_str(), r.length()) == (ssize_t)r.length()) { @@ -107,10 +112,12 @@ void HTTPCache::GetCached(const std::string& url, std::function= 0) { // write failed - std::string path = os_pathcat(_cachedir, name); - printf("Error %d writing cache file \"%s\": %s\n", errno, path.c_str(), strerror(errno)); + auto path = _cachedir / name; + printf("Error %d writing cache file \"%s\": %s\n", + errno, sanitize_print(path).c_str(), strerror(errno)); close(fd); - unlink(path.c_str()); + fs::error_code ec; + fs::remove(path, ec); } else { printf("Could not create cache file!\n"); } @@ -125,7 +132,7 @@ void HTTPCache::GetCached(const std::string& url, std::function()), cached)) { + if (cacheIt.value()["file"].is_string() && readFile(_cachedir / fs::u8path(cacheIt.value()["file"].get()), cached)) { time_t t = time(NULL); _cache[url]["timestamp"] = t; cb(true, cached); diff --git a/src/http/httpcache.h b/src/http/httpcache.h index 8fb3c12a..c6b4536d 100644 --- a/src/http/httpcache.h +++ b/src/http/httpcache.h @@ -7,11 +7,13 @@ #include #include +#include "../core/fs.h" + class HTTPCache { typedef nlohmann::json json; public: - HTTPCache(asio::io_service *asio, const std::string& cachefile, const std::string& cachedir, const std::list& httpDefaultHeaders={}); + HTTPCache(asio::io_service *asio, const fs::path& cachefile, const fs::path& cachedir, const std::list& httpDefaultHeaders={}); HTTPCache(const HTTPCache& orig) = delete; virtual ~HTTPCache(); @@ -20,8 +22,8 @@ class HTTPCache { static std::string GetRandomName(const std::string& suffix="", int len=12); asio::io_service *_asio = nullptr; - std::string _cachefile; - std::string _cachedir; + fs::path _cachefile; + fs::path _cachedir; std::list _httpDefaultHeaders; json _cache = json::object(); int _minAge = 60; // don't fetch if less than X seconds old diff --git a/src/luaconnector/luaconnector.cpp b/src/luaconnector/luaconnector.cpp index f16f3c32..655c6d24 100644 --- a/src/luaconnector/luaconnector.cpp +++ b/src/luaconnector/luaconnector.cpp @@ -1,4 +1,5 @@ #include "luaconnector.h" +#include "server.h" #include "../core/autotrackprovider.h" #include #include @@ -8,7 +9,6 @@ #include #include #include "../core/util.h" -#include "server.h" namespace LuaConnector { diff --git a/src/main.cpp b/src/main.cpp index 822ae84f..ec0722c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -180,7 +180,7 @@ int main(int argc, char** argv) // argc/argv should be empty here. we pass everything through json args json args = json::object(); if (packPath) { - args["pack"] = { {"path", pathToUTF8(packPath)} }; + args["pack"] = { {"path", localToUTF8(packPath)} }; } else if (loadPack) { args["pack"] = { {"uid", loadPack} }; } diff --git a/src/packmanager/packmanager.cpp b/src/packmanager/packmanager.cpp index bcf7504e..974fa5c8 100644 --- a/src/packmanager/packmanager.cpp +++ b/src/packmanager/packmanager.cpp @@ -10,13 +10,13 @@ #endif -PackManager::PackManager(asio::io_service *asio, const std::string& workdir, const std::list& httpDefaultHeaders) - : HTTPCache(asio, os_pathcat(workdir, "pack-cache.json"), os_pathcat(workdir, "pack-cache"), httpDefaultHeaders) +PackManager::PackManager(asio::io_service *asio, const fs::path& workdir, const std::list& httpDefaultHeaders) + : HTTPCache(asio, workdir / "pack-cache.json", workdir / "pack-cache", httpDefaultHeaders) { valijson::SchemaParser parser; parser.populateSchema(JsonSchemaAdapter(_packsSchemaJson), _packsSchema); parser.populateSchema(JsonSchemaAdapter(_versionSchemaJson), _versionSchema); - _confFile = os_pathcat(workdir, "pack-conf.json"); + _confFile = workdir / "pack-conf.json"; loadConf(); } @@ -222,11 +222,15 @@ void PackManager::tempIgnoreSourceVersion(const std::string& uid, const std::str _tempIgnoredSourceVersion[uid].insert(version); } -void PackManager::GetFile(const std::string& url, const std::string& dest, +void PackManager::GetFile(const std::string& url, const fs::path& dest, std::function cb, std::function progress, int followRedirect) { +#ifdef _WIN32 + FILE* f = _wfopen(dest.c_str(), L"wb"); +#else FILE* f = fopen(dest.c_str(), "wb"); +#endif if (!f) cb(false, strerror(errno)); HTTP::GetAsync(*_asio, url, _httpDefaultHeaders, [=](int code, const std::string& response, const HTTP::Headers&){ fclose(f); @@ -243,16 +247,21 @@ void PackManager::GetFile(const std::string& url, const std::string& dest, }, f, progress); } -void PackManager::downloadUpdate(const std::string& url, const std::string& install_dir, +void PackManager::downloadUpdate(const std::string& url, const fs::path& install_dir, const std::string& validate_uid, const std::string& validate_version, const std::string& validate_sha256) { - std::string path; + fs::path path; int fd = -1; for (int i=0; i<5; i++) { std::string name = GetRandomName(".zip"); - path = os_pathcat(_cachedir, name); + path = _cachedir / name; +#ifdef _WIN32 + fd = _wopen(path.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0600); +#else fd = open(path.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0600); - if (fd>=0) break; +#endif + if (fd>=0) + break; } if (fd < 0) { onUpdateFailed.emit(this, url, "Could not create temp file!"); @@ -264,12 +273,14 @@ void PackManager::downloadUpdate(const std::string& url, const std::string& inst if (!validate_sha256.empty()) { auto hash = SHA256_File(path); if (hash.empty()) { - unlink(path.c_str()); + fs::error_code ec; + fs::remove(path, ec); onUpdateFailed.emit(this, url, "Could not calculate hash"); return; } if (strcasecmp(hash.c_str(), validate_sha256.c_str()) != 0) { - unlink(path.c_str()); + fs::error_code ec; + fs::remove(path, ec); onUpdateFailed.emit(this, url, "SHA256 mismatch"); return; } @@ -278,27 +289,30 @@ void PackManager::downloadUpdate(const std::string& url, const std::string& inst Pack *pack = new Pack(path); if (!pack->isValid()) { delete pack; - unlink(path.c_str()); + fs::error_code ec; + fs::remove(path, ec); onUpdateFailed.emit(this, url, "Not a valid pack"); return; } std::string newVersion = pack->getVersion(); std::string newUid = pack->getUID(); - std::string newPath = os_pathcat(install_dir, - sanitize_filename(newUid+"_"+newVersion+".zip")); + auto newPath = install_dir / sanitize_filename(newUid + "_" + newVersion + ".zip"); delete pack; pack = nullptr; if (!validate_version.empty() && newVersion != validate_version) { std::string err = "Version mismatch: " + newVersion + " != " + validate_version; - unlink(path.c_str()); + fs::error_code ec; + fs::remove(path, ec); onUpdateFailed.emit(this, url, err); return; } auto install = [this, url, path, newPath, newUid]() { - if (rename(path.c_str(), newPath.c_str()) != 0) { - unlink(path.c_str()); - onUpdateFailed.emit(this, url, std::string("Error moving: ") + strerror(errno)); + fs::error_code ec; + fs::rename(path, newPath, ec); + if (ec) { + fs::remove(path, ec); + onUpdateFailed.emit(this, url, "Error moving: " + ec.message()); return; } onUpdateComplete.emit(this, url, newPath, newUid); @@ -308,7 +322,8 @@ void PackManager::downloadUpdate(const std::string& url, const std::string& inst if (res) install(); else { std::string err = "UID mismatch: " + newUid + " != " + validate_uid; - unlink(path.c_str()); + fs::error_code ec; + fs::remove(path, ec); onUpdateFailed.emit(this, url, err); } }); diff --git a/src/packmanager/packmanager.h b/src/packmanager/packmanager.h index 35a11b51..a2a0e2c1 100644 --- a/src/packmanager/packmanager.h +++ b/src/packmanager/packmanager.h @@ -10,10 +10,11 @@ #include #include #include -#include "../core/pack.h" -#include "../core/signal.h" #include "../http/http.h" #include "../http/httpcache.h" +#include "../core/fs.h" +#include "../core/signal.h" +#include "../core/pack.h" class PackManager final : HTTPCache { @@ -24,7 +25,7 @@ class PackManager final : HTTPCache { typedef std::function no_update_available_callback; typedef std::function)> confirmation_callback; - PackManager(asio::io_service *asio, const std::string& workdir, const std::list& httpDefaultHeaders={}); + PackManager(asio::io_service *asio, const fs::path& workdir, const std::list& httpDefaultHeaders={}); PackManager(const PackManager&) = delete; virtual ~PackManager(); @@ -53,12 +54,12 @@ class PackManager final : HTTPCache { void getAvailablePacks(std::function cb); void ignoreUpdateSHA256(const std::string& uid, const std::string& sha256); void tempIgnoreSourceVersion(const std::string& uid, const std::string& version); - void downloadUpdate(const std::string& url, const std::string& install_dir, + void downloadUpdate(const std::string& url, const fs::path& install_dir, const std::string& validate_uid, const std::string& validate_version, const std::string& validate_sha256); Signal onUpdateAvailable; Signal onUpdateProgress; - Signal onUpdateComplete; + Signal onUpdateComplete; Signal onUpdateFailed; private: @@ -68,11 +69,11 @@ class PackManager final : HTTPCache { void askConfirmation(const std::string& message, std::function cb); void loadConf(); void saveConf(); - void GetFile(const std::string& url, const std::string& dest, + void GetFile(const std::string& url, const fs::path& dest, std::function cb, std::function progress, int followRedirect=3); - std::string _confFile; + fs::path _confFile; std::map _repositories; json _packs = json::object(); json _conf = json::object(); diff --git a/src/poptracker.cpp b/src/poptracker.cpp index 93cf6b6b..c8f82977 100644 --- a/src/poptracker.cpp +++ b/src/poptracker.cpp @@ -16,14 +16,15 @@ #include "ap/archipelago.h" #include #include "luasandbox/require.h" +#include "uilib/imghelper.h" #include "ui/maptooltip.h" +#include "core/fs.h" #ifdef _WIN32 #include #endif using nlohmann::json; using Ui::Dlg; - const Version PopTracker::VERSION = Version(APP_VERSION_TUPLE); @@ -138,22 +139,22 @@ PopTracker::PopTracker(int argc, char** argv, bool cli, const json& args) { _args = args; - std::string appPath = getAppPath(); - if (!appPath.empty() && fileExists(os_pathcat(appPath, "portable.txt"))) { + auto appPath = fs::app_path(); + if (!appPath.empty() && fs::is_regular_file((appPath / "portable.txt"))) { _isPortable = true; } else { _isPortable = false; // make sure } std::string config; - std::string configFilename = getConfigPath(APPNAME, std::string(APPNAME)+".json", _isPortable); + auto configFilename = getConfigPath(APPNAME, std::string(APPNAME)+".json", _isPortable); if (readFile(configFilename, config)) { _config = parse_jsonc(config); _oldConfig = _config; } std::string colorsData; - std::string colorsFilename = getConfigPath(APPNAME, "colors.json", _isPortable); + auto colorsFilename = getConfigPath(APPNAME, "colors.json", _isPortable); if (readFile(colorsFilename, colorsData)) { _colors = parse_jsonc(colorsData); if (_colors.is_object()) { @@ -191,8 +192,8 @@ PopTracker::PopTracker(int argc, char** argv, bool cli, const json& args) if (!cli) { // enable logging if (_config["log"]) { - std::string logFilename = getConfigPath(APPNAME, "log.txt", _isPortable); - printf("Logging to %s\n", logFilename.c_str()); + auto logFilename = getConfigPath(APPNAME, "log.txt", _isPortable); + printf("Logging to %s\n", logFilename.u8string().c_str()); if (Log::RedirectStdOut(logFilename)) { printf("%s %s\n", APPNAME, VERSION_STRING); } @@ -291,47 +292,47 @@ PopTracker::PopTracker(int argc, char** argv, bool cli, const json& args) Pack::addSearchPath("packs"); // current directory Pack::addOverrideSearchPath("user-override"); - std::string cwdPath = getCwd(); - std::string documentsPath = getDocumentsPath(); - std::string homePath = getHomePath(); + auto cwdPath = fs::current_path(); + auto documentsPath = fs::documents_path(); + auto homePath = fs::home_path(); - std::string homePopTrackerPath = os_pathcat(homePath, "PopTracker"); - _homePackDir = os_pathcat(homePopTrackerPath, "packs"); - _appPackDir = os_pathcat(appPath, "packs"); + auto homePopTrackerPath = homePath / "PopTracker"; + _homePackDir = homePopTrackerPath / "packs"; + _appPackDir = appPath / "packs"; if (!homePath.empty()) { Pack::addSearchPath(_homePackDir); // default user packs if (!_isPortable) { // default user overrides - std::string homeUserOverrides = os_pathcat(homePopTrackerPath, "user-override"); - if (dirExists(homePopTrackerPath)) - mkdir_recursive(homeUserOverrides); + auto homeUserOverrides = homePopTrackerPath / "user-override"; + if (fs::is_directory(homePopTrackerPath)) + fs::create_directories(homeUserOverrides); Pack::addOverrideSearchPath(homeUserOverrides); - Assets::addSearchPath(os_pathcat(homePopTrackerPath, "assets")); + Assets::addSearchPath(homePopTrackerPath / "assets"); } } if (!documentsPath.empty() && documentsPath != ".") { - std::string documentsPopTrackerPath = os_pathcat(documentsPath, "PopTracker"); - Pack::addSearchPath(os_pathcat(documentsPopTrackerPath, "packs")); // alternative user packs + auto documentsPopTrackerPath = documentsPath / "PopTracker"; + Pack::addSearchPath(documentsPopTrackerPath / "packs"); // alternative user packs if (!_isPortable) { - std::string documentsUserOverrides = os_pathcat(documentsPopTrackerPath, "user-override"); + auto documentsUserOverrides = documentsPopTrackerPath / "user-override"; Pack::addOverrideSearchPath(documentsUserOverrides); - if (dirExists(documentsPopTrackerPath)) - mkdir_recursive(documentsUserOverrides); + if (fs::is_directory(documentsPopTrackerPath)) + fs::create_directories(documentsUserOverrides); } if (_config.value("add_emo_packs", false)) { - Pack::addSearchPath(os_pathcat(documentsPath, "EmoTracker", "packs")); // "old" packs + Pack::addSearchPath(documentsPath / "EmoTracker" / "packs"); // "old" packs } } if (!appPath.empty() && appPath != "." && appPath != cwdPath) { Pack::addSearchPath(_appPackDir); // system packs - Pack::addOverrideSearchPath(os_pathcat(appPath, "user-override")); // portable/system overrides - Assets::addSearchPath(os_pathcat(appPath, "assets")); // system assets + Pack::addOverrideSearchPath(appPath / "user-override"); // portable/system overrides + Assets::addSearchPath(appPath / "assets"); // system assets } _asio = new asio::io_service(); - HTTP::certfile = asset("cacert.pem"); // https://curl.se/docs/caextract.html + HTTP::certfile = asset("cacert.pem").u8string(); // https://curl.se/docs/caextract.html _packManager = new PackManager(_asio, getConfigPath(APPNAME, "", _isPortable), _httpDefaultHeaders); // TODO: move repositories to config? @@ -492,27 +493,27 @@ bool PopTracker::start() } auto& jPack = _args.contains("pack") ? _args["pack"] : _config["pack"]; if (jPack.type() == json::value_t::object) { - std::string path = pathFromUTF8(to_string(jPack["path"],"")); + auto path = pathFromUTF8(to_string(jPack["path"],"")); std::string variant = to_string(jPack["variant"],""); if (!scheduleLoadTracker(path,variant)) { - printf("Could not load pack \"%s\"... fuzzy matching\n", path.c_str()); + printf("Could not load pack \"%s\"... fuzzy matching\n", sanitize_print(path).c_str()); // try to fuzzy match by uid and version if path is missing std::string uid = to_string(jPack["uid"],""); std::string version = to_string(jPack["version"],""); Pack::Info info = Pack::Find(uid, version); if (info.path.empty()) info = Pack::Find(uid); // try without version if (!scheduleLoadTracker(info.path, variant)) { - printf("Could not load pack uid \"%s\" (\"%s\")\n", uid.c_str(), info.path.c_str()); + printf("Could not load pack uid \"%s\" (\"%s\")\n", uid.c_str(), sanitize_print(info.path).c_str()); } else { - printf("found pack \"%s\"\n", info.path.c_str()); + printf("found pack \"%s\"\n", sanitize_print(info.path).c_str()); } } } } // create main window - auto icon = IMG_Load(asset("icon.png").c_str()); + auto icon = Ui::LoadImage(asset("icon.png")); _win = _ui->createWindow("PopTracker", icon, pos, size); SDL_FreeSurface(icon); @@ -539,7 +540,8 @@ bool PopTracker::start() if (_settings) { _settings->Raise(); } else { - auto icon = IMG_Load(asset("icon.png").c_str()); + // NOTE: IMG_Load uses SDL_iostream, which uses WIN_UTF8ToStringW + auto icon = Ui::LoadImage(asset("icon.png")); Ui::Position pos = _win->getPosition() + Ui::Size{0,32}; _settings = _ui->createWindow("Settings", icon, pos); _settings->setBackground({255,0,255}); @@ -631,22 +633,22 @@ bool PopTracker::start() } if (item == Ui::TrackerWindow::MENU_SAVE_STATE) { - if (!_tracker) return; - if (!_pack) return; - std::string lastName; + if (!_tracker || !_pack) + return; + fs::path last; if (_exportFile.empty() || _exportUID != _pack->getUID()) { - lastName = sanitize_filename(_pack->getGameName()) + ".json"; + last = sanitize_filename(_pack->getGameName()) + ".json"; if (!_exportDir.empty()) - lastName = os_pathcat(_exportDir, lastName); + last = _exportDir / last; } else { - lastName = _exportFile; + last = _exportFile; } - std::string filename; - if (!Dlg::SaveFile("Save State", lastName.c_str(), {{"JSON Files",{"*.json"}}}, filename)) + fs::path filename; + if (!Dlg::SaveFile("Save State", last, {{"JSON Files",{"*.json"}}}, filename)) return; #ifdef _WIN32 // windows does not add *.* filter, so we can be sure json was selected - if (filename.length() < 5 || strcasecmp(filename.c_str() + filename.length() - 5, ".JSON") != 0) + if (strcasecmp(filename.extension().u8string().c_str(), ".JSON") != 0) filename += ".json"; #endif auto jWindow = windowToJson(_win); @@ -656,11 +658,11 @@ bool PopTracker::start() }; if (!jWindow.is_null()) extra["window"] = jWindow; - if (StateManager::saveState(_tracker, _scriptHost, _win->getHints(), extra, true, filename, true)) + if (StateManager::saveState(_tracker, _scriptHost, _win->getHints(), extra, true, filename.u8string(), true)) { _exportFile = filename; // this is local encoding for fopen _exportUID = _pack->getUID(); - _exportDir = os_dirname(_exportFile); + _exportDir = _exportFile.parent_path(); } else { @@ -670,20 +672,20 @@ bool PopTracker::start() } if (item == Ui::TrackerWindow::MENU_LOAD_STATE) { - std::string lastName = _exportFile.empty() ? (_exportDir + OS_DIR_SEP) : _exportFile; - std::string filename; - if (!Dlg::OpenFile("Load State", lastName.c_str(), {{"JSON Files",{"*.json"}}}, filename)) return; + auto last = _exportFile.empty() ? (_exportDir / "") : _exportFile; + fs::path filename; + if (!Dlg::OpenFile("Load State", last, {{"JSON Files",{"*.json"}}}, filename)) return; if (filename != _exportFile) { _exportFile = filename; // this is local encoding for fopen - _exportDir = os_dirname(_exportFile); + _exportDir = _exportFile.parent_path(); _exportUID.clear(); } loadState(filename); } }}; - _win->onPackSelected += {this, [this](void *s, const std::string& pack, const std::string& variant) { - printf("Pack selected: %s:%s\n", pack.c_str(), variant.c_str()); + _win->onPackSelected += {this, [this](void *s, const fs::path& pack, const std::string& variant) { + printf("Pack selected: %s:%s\n", sanitize_print(pack).c_str(), sanitize_print(variant).c_str()); if (!scheduleLoadTracker(pack, variant)) { fprintf(stderr, "Error scheduling load of tracker/pack!"); // TODO: show error @@ -703,32 +705,41 @@ bool PopTracker::start() filename.pop_back(); const char* tmp = strrchr(filename.c_str(), OS_DIR_SEP); std::string packname = tmp ? (std::string)(tmp+1) : filename; - std::string path = filename; - if (!Pack::isInSearchPath(filename)) { - if (type == Ui::DropType::DIR && !fileExists(os_pathcat(filename, "manifest.json"))) return; +#ifdef _WIN32 + // on windows this is UTF8 + fs::path path = fs::u8path(filename); +#else + // on other platforms, it's probably local encoding + fs::path path = filename; +#endif + auto newPath = path; + if (!Pack::isInSearchPath(path)) { + if (type == Ui::DropType::DIR && !fs::is_regular_file(path / "manifest.json")) + return; std::string msg = "Install pack " + packname + " ?"; if (Dlg::MsgBox("PopTracker", msg, Dlg::Buttons::YesNo, Dlg::Icon::Question) != Dlg::Result::Yes) return; // default to %HOME%/PopTracker/packs if that exist, otherwise // default to %APPDIR%/packs and fall back to %HOME% // TODO: go through PackManager? - path = os_pathcat(getPackInstallDir(), packname); - if (!copy_recursive(filename.c_str(), path.c_str())) { + newPath = getPackInstallDir() / packname; + fs::error_code ec; + if (!copy_recursive(path, newPath, ec)) { std::string msg = "Error copying files:\n"; - msg += strerror(errno); - path = os_pathcat(_homePackDir, packname); - mkdir_recursive(_homePackDir.c_str()); + msg += ec.message(); + newPath = _homePackDir / packname; + fs::create_directories(_homePackDir); errno = 0; - if (!copy_recursive(filename.c_str(), path.c_str())) { - msg += "\n"; msg += strerror(errno); + if (!copy_recursive(path, newPath, ec)) { + msg += "\n"; msg += ec.message(); Dlg::MsgBox("PopTracker", msg, Dlg::Buttons::OK, Dlg::Icon::Error); return; } } - printf("Installed to %s\n", path.c_str()); + printf("Installed to %s\n", sanitize_print(newPath).c_str()); } // attempt to load pack - if (!loadTracker(path, "standard", false)) { + if (!loadTracker(newPath, "standard", false)) { _win->showOpen(); } } else if (type == Ui::DropType::TEXT && strncasecmp(data.c_str(), "https://", 8)==0 && strcasecmp(data.c_str()+data.length()-4, ".zip") == 0) { @@ -754,10 +765,10 @@ bool PopTracker::start() // default to %HOME%/PopTracker/packs if that exist, otherwise // default to %APPDIR%/packs and fall back to %HOME% // TODO: use PackManager::downloadUpdate - std::string path = os_pathcat(getPackInstallDir(), zipname); + auto path = getPackInstallDir() / fs::u8path(zipname); if (!writeFile(path, s)) { - path = os_pathcat(_homePackDir, zipname); - mkdir_recursive(_homePackDir.c_str()); + path = _homePackDir / fs::u8path(zipname); + fs::create_directories(_homePackDir); errno = 0; if (!writeFile(path, s)) { std:: string msg = "Error saving file:\n"; @@ -766,7 +777,7 @@ bool PopTracker::start() return; } } - printf("Download saved to %s\n", path.c_str()); + printf("Download saved to %s\n", sanitize_print(path).c_str()); // attempt to load pack if (!loadTracker(path, "standard", false)) { _win->showOpen(); @@ -792,7 +803,7 @@ bool PopTracker::start() if (Dlg::MsgBox("PopTracker", msg, Dlg::Buttons::YesNo, Dlg::Icon::Question) == Dlg::Result::Yes) { const auto& installDir = getPackInstallDir(); - mkdir_recursive(installDir); + fs::create_directories(installDir); _packManager->downloadUpdate(url, installDir, uid, version, sha256); } else if (Dlg::MsgBox("PopTracker", "Skip version " + version + "?", Dlg::Buttons::YesNo, Dlg::Icon::Question) == Dlg::Result::Yes) { @@ -812,27 +823,36 @@ bool PopTracker::start() _win->hideProgress(); Dlg::MsgBox("PopTracker", "Update failed: " + reason); }}; - _packManager->onUpdateComplete += {this, [this](void*, const std::string& url, const std::string& file, const std::string& uid) { + _packManager->onUpdateComplete += {this, [this](void*, const std::string& url, const fs::path& file, const std::string& uid) { (void)url; _win->hideProgress(); std::string variant = _pack ? _pack->getVariant() : ""; if (_pack && _pack->getUID() == uid) { // FIXME: probably should capture which pack started the update - std::string oldPath = _pack->getPath(); + auto oldPath = _pack->getPath(); unloadTracker(); - std::string oldDir = os_dirname(oldPath); - std::string oldName = os_basename(oldPath); - std::string backupDir = os_pathcat(oldDir, "old"); - mkdir_recursive(backupDir.c_str()); - std::string backupPath = os_pathcat(backupDir, oldName); - if (pathExists(backupPath) && unlink(backupPath.c_str()) != 0) { + auto oldDir = oldPath.parent_path(); + auto oldName = oldPath.filename(); + auto backupDir = oldDir / "old"; + fs::create_directories(backupDir); + auto backupPath = backupDir / oldName; + if (fs::exists(backupPath) && !fs::remove(backupPath)) { fprintf(stderr, "Could not delete old backup %s: %s\n", - backupPath.c_str(), strerror(errno)); - } else if (rename(oldPath.c_str(), backupPath.c_str()) == 0) { - printf("Moved %s to %s\n", oldPath.c_str(), backupPath.c_str()); + sanitize_print(backupPath).c_str(), + strerror(errno)); } else { - fprintf(stderr, "Could not move %s to %s: %s\n", - oldPath.c_str(), backupPath.c_str(), strerror(errno)); + fs::error_code ec; + fs::rename(oldPath, backupPath, ec); + if (ec) { + fprintf(stderr, "Could not move %s to %s: %s\n", + sanitize_print(oldPath).c_str(), + sanitize_print(backupPath).c_str(), + strerror(errno)); + } else { + printf("Moved %s to %s\n", + sanitize_print(oldPath).c_str(), + sanitize_print(backupPath).c_str()); + } } } loadTracker(file, variant); @@ -883,7 +903,7 @@ bool PopTracker::frame() _config["format_version"] = 1; if (_pack) _config["pack"] = { - {"path",pathToUTF8(_pack->getPath())}, + {"path",_pack->getPath().u8string()}, {"variant",_pack->getVariant()}, {"uid",_pack->getUID()}, {"version",_pack->getVersion()} @@ -892,9 +912,9 @@ bool PopTracker::frame() _config["window"] = jWindow; else _config.erase("window"); - _config["export_file"] = pathToUTF8(_exportFile); + _config["export_file"] = _exportFile.u8string(); _config["export_uid"] = _exportUID; - _config["export_dir"] = pathToUTF8(_exportDir); + _config["export_dir"] = _exportDir.u8string(); saveConfig(); } @@ -955,7 +975,7 @@ bool PopTracker::ListPacks(PackManager::confirmation_callback confirm, bool inst printf("Search paths:\n"); for (const auto& path: Pack::getSearchPaths()) { - printf("%s\n", path.c_str()); + printf("%s\n", sanitize_print(path).c_str()); } printf("\n"); @@ -993,7 +1013,7 @@ bool PopTracker::InstallPack(const std::string& uid, PackManager::confirmation_c int percent = total>0 ? received*100/total : 0; printf("%3d%% [%s/%s] \r", percent, format_bytes(received).c_str(), format_bytes(total).c_str()); }}; - _packManager->onUpdateComplete += {this, [&done, &res](void*, const std::string& url, const std::string& local, const std::string&) { + _packManager->onUpdateComplete += {this, [&done, &res](void*, const std::string& url, const fs::path& local, const std::string&) { printf("\nDownload complete.\n"); printf("Pack installed as %s\n", sanitize_print(local).c_str()); res = false; @@ -1006,7 +1026,7 @@ bool PopTracker::InstallPack(const std::string& uid, PackManager::confirmation_c }}; _packManager->checkForUpdate(uid, "", "", [this](const std::string& uid, const std::string& version, const std::string& url, const std::string& sha256){ const auto& installDir = getPackInstallDir(); - mkdir_recursive(installDir); + fs::create_directories(installDir); _packManager->downloadUpdate(url, installDir, uid, version, sha256); }, [&done, &res](const std::string& uid) { printf("%s has no installation candidate\n", uid.c_str()); @@ -1019,9 +1039,11 @@ bool PopTracker::InstallPack(const std::string& uid, PackManager::confirmation_c return res; } -const std::string& PopTracker::getPackInstallDir() const +const fs::path& PopTracker::getPackInstallDir() const { - return (!_isPortable && dirExists(_homePackDir)) || !isWritable(_appPackDir) ? _homePackDir : _appPackDir; + if ((!_isPortable && fs::is_directory(_homePackDir)) || !isWritable(_appPackDir)) + return _homePackDir; + return _appPackDir; } void PopTracker::unloadTracker() @@ -1067,7 +1089,7 @@ void PopTracker::unloadTracker() _debugFlags = _defaultDebugFlags; } -bool PopTracker::loadTracker(const std::string& pack, const std::string& variant, bool loadAutosave) +bool PopTracker::loadTracker(const fs::path& pack, const std::string& variant, bool loadAutosave) { printf("Cleaning up...\n"); unloadTracker(); @@ -1302,10 +1324,11 @@ bool PopTracker::loadTracker(const std::string& pack, const std::string& variant return res; } -bool PopTracker::scheduleLoadTracker(const std::string& pack, const std::string& variant, bool loadAutosave) +bool PopTracker::scheduleLoadTracker(const fs::path& pack, const std::string& variant, bool loadAutosave) { // TODO: (load and) verify pack already? - if (! pathExists(pack)) return false; + if (!fs::exists(pack)) + return false; _newPack = pack; _newVariant = variant; _newTrackerLoadAutosave = loadAutosave; @@ -1324,11 +1347,11 @@ void PopTracker::reloadTracker(bool force) } } -void PopTracker::loadState(const std::string& filename) +void PopTracker::loadState(const fs::path& filename) { std::string s; if (!readFile(filename, s)) { - fprintf(stderr, "Error reading state file: %s\n", filename.c_str()); + fprintf(stderr, "Error reading state file: %s\n", sanitize_print(filename).c_str()); Dlg::MsgBox("PopTracker", "Could not read state file!", Dlg::Buttons::OK, Dlg::Icon::Error); return; @@ -1337,7 +1360,7 @@ void PopTracker::loadState(const std::string& filename) try { j = parse_jsonc(s); } catch (...) { - fprintf(stderr, "Error parsing state file: %s\n", filename.c_str()); + fprintf(stderr, "Error parsing state file: %s\n", sanitize_print(filename).c_str()); Dlg::MsgBox("PopTracker", "Selected file is not valid json!", Dlg::Buttons::OK, Dlg::Icon::Error); return; @@ -1345,7 +1368,7 @@ void PopTracker::loadState(const std::string& filename) auto jNull = json(nullptr); auto& jPack = j.is_object() ? j["pack"] : jNull; if (!jPack.is_object() || !jPack["uid"].is_string() || !jPack["variant"].is_string()) { - fprintf(stderr, "Json is not a state file: %s\n", filename.c_str()); + fprintf(stderr, "Json is not a state file: %s\n", sanitize_print(filename).c_str()); Dlg::MsgBox("PopTracker", "Selected file is not a state file!", Dlg::Buttons::OK, Dlg::Icon::Error); return; @@ -1353,7 +1376,7 @@ void PopTracker::loadState(const std::string& filename) if (_pack->getUID() != jPack["uid"] || _pack->getVariant() != jPack["variant"]) { auto packInfo = Pack::Find(jPack["uid"]); if (packInfo.uid != jPack["uid"]) { - std::string msg = "Could not find pack: " + sanitize_print(jPack["uid"]) + "!"; + std::string msg = "Could not find pack: " + sanitize_print(jPack["uid"].get()) + "!"; fprintf(stderr, "%s\n", msg.c_str()); Dlg::MsgBox("PopTracker", msg, Dlg::Buttons::OK, Dlg::Icon::Error); @@ -1367,9 +1390,9 @@ void PopTracker::loadState(const std::string& filename) } } if (!variantMatch) { - std::string msg = "Pack " + sanitize_print(jPack["uid"]) + + std::string msg = "Pack " + sanitize_print(jPack["uid"].get()) + " does not have requested variant " - + sanitize_print(jPack["variant"]); + + sanitize_print(jPack["variant"].get()); fprintf(stderr, "%s\n", msg.c_str()); Dlg::MsgBox("PopTracker", msg, Dlg::Buttons::OK, Dlg::Icon::Error); @@ -1385,7 +1408,7 @@ void PopTracker::loadState(const std::string& filename) } } json extra; - if (!StateManager::loadState(_tracker, _scriptHost, extra, true, filename, true)) { + if (!StateManager::loadState(_tracker, _scriptHost, extra, true, filename.u8string(), true)) { Dlg::MsgBox("PopTracker", "Error loading state!", Dlg::Buttons::OK, Dlg::Icon::Error); } @@ -1408,7 +1431,7 @@ void PopTracker::showBroadcast() _broadcast->Raise(); } else if (_tracker->hasLayout("tracker_broadcast")) { // create window - auto icon = IMG_Load(asset("icon.png").c_str()); + auto icon = Ui::LoadImage(asset("icon.png")); Ui::Position pos = _win->getPosition() + Ui::Size{0,32}; _broadcast = _ui->createWindow("Broadcast", icon, pos); SDL_FreeSurface(icon); @@ -1430,7 +1453,7 @@ void PopTracker::showBroadcast() void PopTracker::updateAvailable(const std::string& version, const std::string& url, const std::list assets) { std::string ignoreData; - std::string ignoreFilename = getConfigPath(APPNAME, "ignored-versions.json", _isPortable); + auto ignoreFilename = getConfigPath(APPNAME, "ignored-versions.json", _isPortable); json ignore; if (readFile(ignoreFilename, ignoreData)) { ignore = parse_jsonc(ignoreData); @@ -1492,10 +1515,11 @@ bool PopTracker::saveConfig() { bool res = true; if (_oldConfig != _config) { - std::string configDir = getConfigPath(APPNAME, "", _isPortable); - mkdir_recursive(configDir.c_str()); - res = writeFile(os_pathcat(configDir, std::string(APPNAME)+".json"), _config.dump(4)+"\n"); - if (res) _oldConfig = _config; + auto configDir = getConfigPath(APPNAME, "", _isPortable); + fs::create_directories(configDir); + res = writeFile(configDir / (std::string(APPNAME)+".json"), _config.dump(4)+"\n"); + if (res) + _oldConfig = _config; } return res; } diff --git a/src/poptracker.h b/src/poptracker.h index cb136870..1ed58071 100644 --- a/src/poptracker.h +++ b/src/poptracker.h @@ -43,11 +43,11 @@ class PopTracker final : public App { asio::io_service *_asio = nullptr; std::list _httpDefaultHeaders; PackManager *_packManager = nullptr; - std::string _exportFile; + fs::path _exportFile; std::string _exportUID; - std::string _exportDir; - std::string _homePackDir; - std::string _appPackDir; + fs::path _exportDir; + fs::path _homePackDir; + fs::path _appPackDir; std::set _debugFlags; std::set _defaultDebugFlags; @@ -56,24 +56,24 @@ class PopTracker final : public App { std::chrono::steady_clock::time_point _fpsTimer; std::chrono::steady_clock::time_point _frameTimer; - std::string _newPack; + fs::path _newPack; std::string _newVariant; bool _newTrackerLoadAutosave = false; std::chrono::steady_clock::time_point _autosaveTimer; std::string _atUri, _atSlot, _atPassword; - bool loadTracker(const std::string& pack, const std::string& variant, bool loadAutosave=true); - bool scheduleLoadTracker(const std::string& pack, const std::string& variant, bool loadAutosave=true); + bool loadTracker(const fs::path& pack, const std::string& variant, bool loadAutosave=true); + bool scheduleLoadTracker(const fs::path& pack, const std::string& variant, bool loadAutosave=true); void unloadTracker(); void reloadTracker(bool force=false); - void loadState(const std::string& filename); + void loadState(const fs::path& filename); void showBroadcast(); void updateAvailable(const std::string& version, const std::string& url, const std::list assets); static bool isNewer(const Version& v); - const std::string& getPackInstallDir() const; + const fs::path& getPackInstallDir() const; bool saveConfig(); diff --git a/src/ui/defaulttrackerwindow.cpp b/src/ui/defaulttrackerwindow.cpp index 0aef9298..5d522d04 100644 --- a/src/ui/defaulttrackerwindow.cpp +++ b/src/ui/defaulttrackerwindow.cpp @@ -16,28 +16,28 @@ DefaultTrackerWindow::DefaultTrackerWindow(const char* title, SDL_Surface* icon, _menu = hbox; addChild(_menu); - _btnLoad = new ImageButton(0,0,32-4,32-4, asset("load.png").c_str()); + _btnLoad = new ImageButton(0,0,32-4,32-4, asset("load.png")); _btnLoad->setDarkenGreyscale(false); hbox->addChild(_btnLoad); _btnLoad->onClick += { this, [this](void*, int x, int y, int button) { onMenuPressed.emit(this, MENU_LOAD, 0); }}; - _btnReload = new ImageButton(0,0,32-4,32-4, asset("reload.png").c_str()); + _btnReload = new ImageButton(0,0,32-4,32-4, asset("reload.png")); _btnReload->setVisible(false); hbox->addChild(_btnReload); _btnReload->onClick += { this, [this](void*, int x, int y, int button) { onMenuPressed.emit(this, MENU_RELOAD, 0); }}; - _btnImport = new ImageButton(0,0,32-4,32-4, asset("import.png").c_str()); + _btnImport = new ImageButton(0,0,32-4,32-4, asset("import.png")); _btnImport->setVisible(false); hbox->addChild(_btnImport); _btnImport->onClick += { this, [this](void*, int x, int y, int button) { onMenuPressed.emit(this, MENU_LOAD_STATE, 0); }}; - _btnExport = new ImageButton(0,0,32-4,32-4, asset("export.png").c_str()); + _btnExport = new ImageButton(0,0,32-4,32-4, asset("export.png")); _btnExport->setVisible(false); hbox->addChild(_btnExport); _btnExport->onClick += { this, [this](void*, int x, int y, int button) { @@ -45,7 +45,7 @@ DefaultTrackerWindow::DefaultTrackerWindow(const char* title, SDL_Surface* icon, }}; #ifndef __EMSCRIPTEN__ // no multi-window support (yet) - _btnBroadcast = new ImageButton(0,0,32-4,32-4, asset("broadcast.png").c_str()); + _btnBroadcast = new ImageButton(0,0,32-4,32-4, asset("broadcast.png")); _btnBroadcast->setVisible(false); hbox->addChild(_btnBroadcast); _btnBroadcast->onClick += { this, [this](void*, int x, int y, int button) { @@ -53,7 +53,7 @@ DefaultTrackerWindow::DefaultTrackerWindow(const char* title, SDL_Surface* icon, }}; #endif - _btnPackSettings = new ImageButton(32-4,0,32-4,32-4, asset("settings.png").c_str()); + _btnPackSettings = new ImageButton(32-4,0,32-4,32-4, asset("settings.png")); _btnPackSettings->setVisible(false); hbox->addChild(_btnPackSettings); _btnPackSettings->onClick += { this, [this](void*, int x, int y, int button) { @@ -89,7 +89,7 @@ DefaultTrackerWindow::DefaultTrackerWindow(const char* title, SDL_Surface* icon, _loadPackWidget->setVisible(false); addChild(_loadPackWidget); - _loadPackWidget->onPackSelected += {this,[this](void *s, const std::string& pack, const std::string& variant) { + _loadPackWidget->onPackSelected += {this,[this](void *s, const fs::path& pack, const std::string& variant) { onPackSelected.emit(this,pack,variant); }}; diff --git a/src/ui/defaulttrackerwindow.h b/src/ui/defaulttrackerwindow.h index 34a35c8a..42288bbe 100644 --- a/src/ui/defaulttrackerwindow.h +++ b/src/ui/defaulttrackerwindow.h @@ -24,7 +24,7 @@ class DefaultTrackerWindow : public TrackerWindow { virtual void showProgress(const std::string& title, int progress, int max); virtual void hideProgress(); - Signal onPackSelected; + Signal onPackSelected; protected: ImageButton *_btnLoad = nullptr; diff --git a/src/ui/loadpackwidget.cpp b/src/ui/loadpackwidget.cpp index f07e2777..d31aa6a9 100644 --- a/src/ui/loadpackwidget.cpp +++ b/src/ui/loadpackwidget.cpp @@ -96,7 +96,7 @@ void LoadPackWidget::update() _curVariantLabel->setBackground(VARIANT_BG_DEFAULT); _curVariantLabel = nullptr; }}; - std::string path = pack.path; + auto path = pack.path; lbl->onMouseEnter += {this,[this](void *s, int x, int y, unsigned buttons) { if (!s || s==_curVariantLabel) return; if (_curVariantLabel) _curVariantLabel->onMouseLeave.emit(_curVariantLabel); diff --git a/src/ui/loadpackwidget.h b/src/ui/loadpackwidget.h index 8d2b958c..5f94723f 100644 --- a/src/ui/loadpackwidget.h +++ b/src/ui/loadpackwidget.h @@ -11,6 +11,8 @@ #include "../core/signal.h" #include +#include "../core/fs.h" + namespace Ui { class LoadPackWidget : public SimpleContainer { @@ -20,7 +22,7 @@ class LoadPackWidget : public SimpleContainer { void update(); - Signal onPackSelected; + Signal onPackSelected; virtual void setSize(Size size) override; // TODO: have more intelligent hbox instead diff --git a/src/ui/trackerwindow.h b/src/ui/trackerwindow.h index 48c98120..d5185248 100644 --- a/src/ui/trackerwindow.h +++ b/src/ui/trackerwindow.h @@ -2,13 +2,13 @@ #define _UI_TRACKERWINDOW_H #include +#include "../core/autotracker.h" #include "../uilib/window.h" #include "../uilib/label.h" #include "../uilib/image.h" #include "../uilib/imagebutton.h" #include "trackerview.h" #include "../core/tracker.h" -#include "../core/autotracker.h" namespace Ui { diff --git a/src/uilib/dlg.cpp b/src/uilib/dlg.cpp index 7e0ba64b..3a06e7a9 100644 --- a/src/uilib/dlg.cpp +++ b/src/uilib/dlg.cpp @@ -337,19 +337,23 @@ Dlg::Result Dlg::MsgBox(const std::string& title, const std::string& message, Dl #endif } -bool Dlg::OpenFile(const std::string& title, const std::string& dflt, const std::list& types, std::string& out, bool multi) +bool Dlg::OpenFile(const std::string& title, const fs::path& dflt, const std::list& types, fs::path& out, bool multi) { #ifdef __WIN32__ using namespace std::string_literals; // TODO: implement multi-select assert(!multi); - // NOTE: unicode filename currently not supported since we use fopen (not CreateFileW) bool res = false; - wchar_t buf[MAX_PATH]; *buf = 0; + wchar_t buf[MAX_PATH]; + *buf = 0; + + if (dflt.native().length() >= MAX_PATH) + return false; std::string filter; // "Name\0*.ext\0All Files\0*\0"s - if (types.empty()) filter = "All Files\0*\0"s; + if (types.empty()) + filter = "All Files\0*\0"s; for (const auto& type: types) { std::string patterns; for(const auto& pat: type.patterns) { @@ -361,12 +365,12 @@ bool Dlg::OpenFile(const std::string& title, const std::string& dflt, const std: LPWSTR lpwTitle = (LPWSTR)malloc(title.length()*2+2); MultiByteToWideChar(CP_UTF8, 0, title.c_str(), -1, lpwTitle, title.length()+1); - LPWSTR lpwDflt = (LPWSTR)malloc(dflt.length()*2+2); - MultiByteToWideChar(CP_ACP, 0, dflt.c_str(), -1, lpwDflt, dflt.length()+1); - if (dflt.length()*2+2 <= sizeof(buf)) memcpy(buf, lpwDflt, dflt.length()*2+2); LPWSTR lpwFilter = (LPWSTR)malloc(filter.length()*2+2); MultiByteToWideChar(CP_UTF8, 0, filter.c_str(), filter.length()+1, lpwFilter, filter.length()+1); + static_assert(sizeof(*buf) == sizeof(*dflt.c_str())); + memcpy(buf, dflt.c_str(), sizeof(*buf) * dflt.native().size()); + OPENFILENAMEW ofn; memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); @@ -376,18 +380,10 @@ bool Dlg::OpenFile(const std::string& title, const std::string& dflt, const std: ofn.lpstrTitle = lpwTitle; ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; if (GetOpenFileNameW(&ofn)) { - // filenames are in local (ansi) encoding at the moment - int tmplen = WideCharToMultiByte(CP_ACP, 0, ofn.lpstrFile, -1, nullptr, 0, nullptr, nullptr); - if (tmplen > 0) { - char* tmp = (char*)malloc(tmplen); - WideCharToMultiByte(CP_ACP, 0, ofn.lpstrFile, -1, tmp, tmplen, nullptr, nullptr); - out = tmp; - free(tmp); - res = true; - } + out = ofn.lpstrFile; + res = true; } free(lpwTitle); - free(lpwDflt); free(lpwFilter); return res; #else @@ -411,14 +407,18 @@ bool Dlg::OpenFile(const std::string& title, const std::string& dflt, const std: #endif } -bool Dlg::SaveFile(const std::string& title, const std::string& dflt, const std::list& types, std::string& out) +bool Dlg::SaveFile(const std::string& title, const fs::path& dflt, const std::list& types, fs::path& out) { #ifdef __WIN32__ using namespace std::string_literals; // NOTE: unicode filename currently not supported since we use fopen (not CreateFileW) bool res = false; - wchar_t buf[MAX_PATH]; *buf = 0; + wchar_t buf[MAX_PATH]; + *buf = 0; + + if (dflt.native().length() >= MAX_PATH) + return false; std::string filter; // "Name\0*.ext\0All Files\0*\0"s if (types.empty()) filter = "All Files\0*\0"s; @@ -431,11 +431,11 @@ bool Dlg::SaveFile(const std::string& title, const std::string& dflt, const std: filter += type.name + "\0"s + patterns + "\0"s; } + static_assert(sizeof(*buf) == sizeof(*dflt.c_str())); + memcpy(buf, dflt.c_str(), sizeof(*buf) * dflt.native().size()); + LPWSTR lpwTitle = (LPWSTR)malloc(title.length()*2+2); MultiByteToWideChar(CP_UTF8, 0, title.c_str(), -1, lpwTitle, title.length()+1); - LPWSTR lpwDflt = (LPWSTR)malloc(dflt.length()*2+2); - MultiByteToWideChar(CP_ACP, 0, dflt.c_str(), -1, lpwDflt, dflt.length()+1); - if (dflt.length()*2+2 <= sizeof(buf)) memcpy(buf, lpwDflt, dflt.length()*2+2); LPWSTR lpwFilter = (LPWSTR)malloc(filter.length()*2+2); MultiByteToWideChar(CP_UTF8, 0, filter.c_str(), filter.length()+1, lpwFilter, filter.length()+1); @@ -448,18 +448,10 @@ bool Dlg::SaveFile(const std::string& title, const std::string& dflt, const std: ofn.lpstrTitle = lpwTitle; ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR; if (GetSaveFileNameW(&ofn)) { - // filenames are in local (ansi) encoding at the moment - int tmplen = WideCharToMultiByte(CP_ACP, 0, ofn.lpstrFile, -1, nullptr, 0, nullptr, nullptr); - if (tmplen > 0) { - char* tmp = (char*)malloc(tmplen); - WideCharToMultiByte(CP_ACP, 0, ofn.lpstrFile, -1, tmp, tmplen, nullptr, nullptr); - out = tmp; - free(tmp); - res = true; - } + out = ofn.lpstrFile; + res = true; } free(lpwTitle); - free(lpwDflt); free(lpwFilter); return res; #else diff --git a/src/uilib/dlg.h b/src/uilib/dlg.h index 8bc302b3..66a7f7c7 100644 --- a/src/uilib/dlg.h +++ b/src/uilib/dlg.h @@ -6,6 +6,8 @@ #include #include +#include "../core/fs.h" + namespace Ui { @@ -39,8 +41,8 @@ class Dlg { static bool InputBox(const std::string& title, const std::string& message, const std::string& dflt, std::string& result, bool password=false); static Result MsgBox(const std::string& title, const std::string& message, Buttons btns=Buttons::OK, Icon icon=Icon::Info, Result dflt=Result::OK); - static bool OpenFile(const std::string& title, const std::string& dflt, const std::list& types, std::string& out, bool multi=false); - static bool SaveFile(const std::string& title, const std::string& dflt, const std::list& types, std::string& out); + static bool OpenFile(const std::string& title, const fs::path& dflt, const std::list& types, fs::path& out, bool multi=false); + static bool SaveFile(const std::string& title, const fs::path& dflt, const std::list& types, fs::path& out); static bool hasGUI(); diff --git a/src/uilib/fontstore.cpp b/src/uilib/fontstore.cpp index 2aa7c1d9..9e233d2a 100644 --- a/src/uilib/fontstore.cpp +++ b/src/uilib/fontstore.cpp @@ -28,7 +28,13 @@ FontStore::FONT FontStore::getFont(const char* name, int size) return sizeIt->second; } create_font: +#ifdef _WIN32 + // on Windows, SDL_ttf uses SDL's IO functions, which expect UTF8 + FONT font = TTF_OpenFont(asset(name).u8string().c_str(), size); +#else + // otherwise it's probably fopen, which is "native" encoding FONT font = TTF_OpenFont(asset(name).c_str(), size); +#endif if (!font) fprintf(stderr, "TTF_OpenFont: %s\n", TTF_GetError()); _store[name][size] = font; return font; diff --git a/src/uilib/image.cpp b/src/uilib/image.cpp index e877112e..8d24bba6 100644 --- a/src/uilib/image.cpp +++ b/src/uilib/image.cpp @@ -2,68 +2,102 @@ #include #include #include "colorhelper.h" +#include "imghelper.h" +#include "../core/fs.h" namespace Ui { -Image::Image(int x, int y, int w, int h, const char* path) - : Widget(x,y,w,h), _path(path) +Image::Image(int x, int y, int w, int h, const fs::path& path) + : Widget(x,y,w,h) { - if (!path || !*path) { - _surf = nullptr; - return; - } - // NOTE: if the app hangs or crashes in IMG_Load, you are probably mixing incompatible DLLs - // FIXME: loading images takes a majority of the time to build the UI. Cache it! - _surf = IMG_Load(_path.c_str()); - if (_surf) { - _autoSize = {_surf->w, _surf->h}; - if (w<1 && h<1) { - _size.width = _autoSize.width; - _size.height = _autoSize.height; - } else if (w<1) { - _size.width = (_autoSize.width * h + _autoSize.height/2) / _autoSize.height; - } else if (h<1) { - _size.height = (_autoSize.height * w + _autoSize.width/2) / _autoSize.width; - } - } - else { - fprintf(stderr, "IMG_Load: %s\n", IMG_GetError()); + setImage(path); + if (w<1 && h<1) { + _size.width = _autoSize.width; + _size.height = _autoSize.height; + } else if (w<1) { + _size.width = (_autoSize.width * h + _autoSize.height/2) / _autoSize.height; + } else if (h<1) { + _size.height = (_autoSize.height * w + _autoSize.width/2) / _autoSize.width; } } + Image::Image(int x, int y, int w, int h, const void* data, size_t len) : Widget(x,y,w,h) { - if (!data || !len) { - _surf = nullptr; - return; + setImage(data, len); + if (w<1 && h<1) { + _size.width = _autoSize.width; + _size.height = _autoSize.height; + } else if (w<1) { + _size.width = (_autoSize.width * h + _autoSize.height/2) / _autoSize.height; + } else if (h<1) { + _size.height = (_autoSize.height * w + _autoSize.width/2) / _autoSize.width; } - // NOTE: if the app hangs or crashes in IMG_Load_RW, you are probably mixing incompatible DLLs +} + +Image::~Image() +{ + if (_tex) SDL_DestroyTexture(_tex); + if (_texBw) SDL_DestroyTexture(_texBw); + if (_surf) SDL_FreeSurface(_surf); + _tex = nullptr; + _texBw = nullptr; + _surf = nullptr; +} + +void Image::setImage(const fs::path& path) +{ + // NOTE: if the app hangs or crashes in IMG_Load, you are probably mixing incompatible DLLs // FIXME: loading images takes a majority of the time to build the UI. Cache it! - _surf = IMG_Load_RW(SDL_RWFromMem((void*)data, (int)len), 1); + if (path == _path && !path.empty()) + return; // unchanged + + if (_tex) SDL_DestroyTexture(_tex); + if (_texBw) SDL_DestroyTexture(_texBw); + if (_surf) SDL_FreeSurface(_surf); + _tex = nullptr; + _texBw = nullptr; + _surf = nullptr; + + if (path.empty()) { + _path.clear(); + return; // no data + } else { + _path = path; + } + + _surf = Ui::LoadImage(_path); if (_surf) { _autoSize = {_surf->w, _surf->h}; - if (w<1 && h<1) { - _size.width = _autoSize.width; - _size.height = _autoSize.height; - } else if (w<1) { - _size.width = (_autoSize.width * h + _autoSize.height/2) / _autoSize.height; - } else if (h<1) { - _size.height = (_autoSize.height * w + _autoSize.width/2) / _autoSize.width; - } } else { fprintf(stderr, "IMG_Load: %s\n", IMG_GetError()); } } -Image::~Image() + +void Image::setImage(const void* data, size_t len) { + // NOTE: if the app hangs or crashes in IMG_Load_RW, you are probably mixing incompatible DLLs + // FIXME: loading images takes a majority of the time to build the UI. Cache it! if (_tex) SDL_DestroyTexture(_tex); if (_texBw) SDL_DestroyTexture(_texBw); if (_surf) SDL_FreeSurface(_surf); _tex = nullptr; _texBw = nullptr; _surf = nullptr; + _path.clear(); + + if (!data || !len) + return; // no data + + _surf = IMG_Load_RW(SDL_RWFromMem((void*)data, (int)len), 1); + if (_surf) { + _autoSize = {_surf->w, _surf->h}; + } + else { + fprintf(stderr, "IMG_Load: %s\n", IMG_GetError()); + } } void Image::render(Renderer renderer, int offX, int offY) diff --git a/src/uilib/image.h b/src/uilib/image.h index 57be3ef7..70d606f9 100644 --- a/src/uilib/image.h +++ b/src/uilib/image.h @@ -2,6 +2,7 @@ #define _UILIB_IMAGE_H #include "widget.h" +#include "../core/fs.h" #include namespace Ui { @@ -9,7 +10,7 @@ namespace Ui { class Image : public Widget { public: - Image(int x, int y, int w, int h, const char* path); + Image(int x, int y, int w, int h, const fs::path& path); Image(int x, int y, int w, int h, const void* data, size_t len); ~Image(); virtual void render(Renderer renderer, int offX, int offY) override; @@ -18,11 +19,14 @@ class Image : public Widget // NOTE: this has to be set before the image is rendered for the first time virtual void setQuality(int q) { _quality = q; } virtual void setDarkenGreyscale(bool value); + void setImage(const fs::path& path); + void setImage(const void* data, size_t len); + protected: SDL_Surface *_surf = nullptr; SDL_Texture *_tex = nullptr; SDL_Texture *_texBw = nullptr; - std::string _path; + fs::path _path; bool _fixedAspect=true; int _quality=-1; bool _darkenGreyscale = true; // makes greyscale version look "disabled" diff --git a/src/uilib/imagebutton.cpp b/src/uilib/imagebutton.cpp index 6acc495e..b09bdc39 100644 --- a/src/uilib/imagebutton.cpp +++ b/src/uilib/imagebutton.cpp @@ -1,8 +1,9 @@ #include "imagebutton.h" +#include "../core/fs.h" namespace Ui { -ImageButton::ImageButton(int x, int y, int w, int h, const char* path) +ImageButton::ImageButton(int x, int y, int w, int h, const fs::path& path) : Image(x,y,w,h,path) { setCursor(Cursor::HAND); diff --git a/src/uilib/imagebutton.h b/src/uilib/imagebutton.h index 51a64304..4d627bc1 100644 --- a/src/uilib/imagebutton.h +++ b/src/uilib/imagebutton.h @@ -2,12 +2,13 @@ #define _UILIB_IMAGEBUTTON_H #include "image.h" +#include "../core/fs.h" namespace Ui { class ImageButton : public Image { public: - ImageButton(int x, int y, int w, int h, const char* path); + ImageButton(int x, int y, int w, int h, const fs::path& path); ImageButton(int x, int y, int w, int h, const void* data, size_t len); }; diff --git a/src/uilib/imghelper.h b/src/uilib/imghelper.h new file mode 100644 index 00000000..62e9bb0d --- /dev/null +++ b/src/uilib/imghelper.h @@ -0,0 +1,20 @@ +#pragma once + +#include "../core/fs.h" +#include +#include + +namespace Ui { + +inline SDL_Surface* LoadImage(const fs::path& path) +{ +#ifdef _WIN32 + // on Windows, SDL_Image uses SDL's IO functions, which expect UTF8 + return IMG_Load(path.u8string().c_str()); +#else + // otherwise it's probably fopen, which is "native" encoding + return IMG_Load(path.c_str()); +#endif +} + +} // namespace Ui diff --git a/src/uilib/ui.cpp b/src/uilib/ui.cpp index 39f76ef2..295854e3 100644 --- a/src/uilib/ui.cpp +++ b/src/uilib/ui.cpp @@ -357,7 +357,7 @@ bool Ui::render() EVENT_LOCK(this); auto winit = _windows.find(ev.drop.windowID); if (winit != _windows.end()) { - bool isDir = dirExists(ev.drop.file); + bool isDir = fs::is_directory(fs::u8path(ev.drop.file)); winit->second->onDrop.emit(winit->second, 0, 0, isDir ? DropType::DIR : DropType::FILE, ev.drop.file);