add virtual filesystem thing w/ ZIP support

similar to Quake 3: all the archives and folders get mounted to the same mountpoint in the VFS, read access to files in the VFS is transparent
This commit is contained in:
fgsfds 2020-06-07 21:05:26 +03:00
parent be4062e9d5
commit 4feacc0065
20 changed files with 2294 additions and 317 deletions

View file

@ -56,6 +56,11 @@ NO_LDIV ?= 0
LEGACY_GL ?= 0 LEGACY_GL ?= 0
# Misc settings for EXTERNAL_DATA
BASEDIR ?= res
BASEPACK ?= base.zip
# Automatic settings for PC port(s) # Automatic settings for PC port(s)
NON_MATCHING := 1 NON_MATCHING := 1
@ -266,7 +271,7 @@ LEVEL_DIRS := $(patsubst levels/%,%,$(dir $(wildcard levels/*/header.h)))
# Directories containing source files # Directories containing source files
# Hi, I'm a PC # Hi, I'm a PC
SRC_DIRS := src src/engine src/game src/audio src/menu src/buffers actors levels bin data assets src/pc src/pc/gfx src/pc/audio src/pc/controller SRC_DIRS := src src/engine src/game src/audio src/menu src/buffers actors levels bin data assets src/pc src/pc/gfx src/pc/audio src/pc/controller src/pc/fs src/pc/fs/packtypes
ASM_DIRS := ASM_DIRS :=
BIN_DIRS := bin bin/$(VERSION) BIN_DIRS := bin bin/$(VERSION)
@ -556,8 +561,8 @@ endif
# Load external textures # Load external textures
ifeq ($(EXTERNAL_DATA),1) ifeq ($(EXTERNAL_DATA),1)
CC_CHECK += -DEXTERNAL_DATA CC_CHECK += -DEXTERNAL_DATA -DFS_BASEDIR="\"$(BASEDIR)\""
CFLAGS += -DEXTERNAL_DATA CFLAGS += -DEXTERNAL_DATA -DFS_BASEDIR="\"$(BASEDIR)\""
# tell skyconv to write names instead of actual texture data and save the split tiles so we can use them later # tell skyconv to write names instead of actual texture data and save the split tiles so we can use them later
SKYCONV_ARGS := --store-names --write-tiles "$(BUILD_DIR)/textures/skybox_tiles" SKYCONV_ARGS := --store-names --write-tiles "$(BUILD_DIR)/textures/skybox_tiles"
endif endif
@ -619,27 +624,30 @@ all: $(EXE)
ifeq ($(EXTERNAL_DATA),1) ifeq ($(EXTERNAL_DATA),1)
# thank you apple very cool BASEPACK_PATH := $(BUILD_DIR)/$(BASEDIR)/$(BASEPACK)
ifeq ($(HOST_OS),Darwin) BASEPACK_LST := $(BUILD_DIR)/basepack.lst
CP := gcp
else
CP := cp
endif
# depend on resources as well # depend on resources as well
all: res all: $(BASEPACK_PATH)
# prepares the resource folder for external data # phony target for building resources
res: $(EXE) res: $(BASEPACK_PATH)
@mkdir -p $(BUILD_DIR)/res/sound
@$(CP) -r -f textures/ $(BUILD_DIR)/res/ # prepares the basepack.lst
@$(CP) -r -f $(BUILD_DIR)/textures/skybox_tiles/ $(BUILD_DIR)/res/textures/ $(BASEPACK_LST): $(EXE)
@$(CP) -f $(SOUND_BIN_DIR)/sound_data.ctl $(BUILD_DIR)/res/sound/ @mkdir -p $(BUILD_DIR)/$(BASEDIR)
@$(CP) -f $(SOUND_BIN_DIR)/sound_data.tbl $(BUILD_DIR)/res/sound/ @echo -n > $(BASEPACK_LST)
@$(CP) -f $(SOUND_BIN_DIR)/sequences.bin $(BUILD_DIR)/res/sound/ @echo "$(BUILD_DIR)/sound/bank_sets sound/bank_sets" >> $(BASEPACK_LST)
@$(CP) -f $(SOUND_BIN_DIR)/bank_sets $(BUILD_DIR)/res/sound/ @echo "$(BUILD_DIR)/sound/sequences.bin sound/sequences.bin" >> $(BASEPACK_LST)
@find actors -name \*.png -exec $(CP) --parents {} $(BUILD_DIR)/res/ \; @echo "$(BUILD_DIR)/sound/sound_data.ctl sound/sound_data.ctl" >> $(BASEPACK_LST)
@find levels -name \*.png -exec $(CP) --parents {} $(BUILD_DIR)/res/ \; @echo "$(BUILD_DIR)/sound/sound_data.tbl sound/sound_data.tbl" >> $(BASEPACK_LST)
@find actors -name \*.png -exec echo "{} gfx/{}" >> $(BASEPACK_LST) \;
@find levels -name \*.png -exec echo "{} gfx/{}" >> $(BASEPACK_LST) \;
@find textures -name \*.png -exec echo "{} gfx/{}" >> $(BASEPACK_LST) \;
# prepares the resource ZIP with base data
$(BASEPACK_PATH): $(BASEPACK_LST)
@$(TOOLS_DIR)/mkzip.py $(BASEPACK_LST) $(BASEPACK_PATH)
endif endif

View file

@ -59,11 +59,15 @@
// Convenience macros for endian conversions // Convenience macros for endian conversions
#if IS_BIG_ENDIAN #if IS_BIG_ENDIAN
#define BE_TO_HOST16(x) (x) # define BE_TO_HOST16(x) (x)
#define BE_TO_HOST32(x) (x) # define BE_TO_HOST32(x) (x)
# define LE_TO_HOST16(x) BSWAP16(x)
# define LE_TO_HOST32(x) BSWAP32(x)
#else #else
#define BE_TO_HOST16(x) BSWAP16(x) # define BE_TO_HOST16(x) BSWAP16(x)
#define BE_TO_HOST32(x) BSWAP32(x) # define BE_TO_HOST32(x) BSWAP32(x)
# define LE_TO_HOST16(x) (x)
# define LE_TO_HOST32(x) (x)
#endif #endif
#endif #endif

724
include/tinfl.h Normal file
View file

@ -0,0 +1,724 @@
/* tinfl.c v1.11 - public domain inflate with zlib header parsing/adler32 checking (inflate-only subset of miniz.c)
See "unlicense" statement at the end of this file.
Rich Geldreich <richgel99@gmail.com>, last updated May 20, 2011
Implements RFC 1950: https://www.ietf.org/rfc/rfc1950.txt and RFC 1951: https://www.ietf.org/rfc/rfc1951.txt
The entire decompressor coroutine is implemented in tinfl_decompress(). The other functions are optional high-level helpers.
*/
#ifndef TINFL_HEADER_INCLUDED
#define TINFL_HEADER_INCLUDED
#include <stdint.h>
typedef uint8_t mz_uint8;
typedef int16_t mz_int16;
typedef uint16_t mz_uint16;
typedef uint32_t mz_uint32;
typedef unsigned int mz_uint;
typedef uint64_t mz_uint64;
/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. */
typedef unsigned long mz_ulong;
/* Heap allocation callbacks. */
typedef void *(*mz_alloc_func)(void *opaque, unsigned int items, unsigned int size);
typedef void (*mz_free_func)(void *opaque, void *address);
#if defined(_M_IX86) || defined(_M_X64)
/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 if integer loads and stores to unaligned addresses are acceptable on the target platform (slightly faster). */
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */
#define MINIZ_LITTLE_ENDIAN 1
#endif
#if defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__)
/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if the processor has 64-bit general purpose registers (enables 64-bit bitbuffer in inflator) */
#define MINIZ_HAS_64BIT_REGISTERS 1
#endif
/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */
#ifdef _MSC_VER
#define MZ_MACRO_END while (0, 0)
#else
#define MZ_MACRO_END while (0)
#endif
/* Decompression flags. */
enum
{
TINFL_FLAG_PARSE_ZLIB_HEADER = 1,
TINFL_FLAG_HAS_MORE_INPUT = 2,
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4,
TINFL_FLAG_COMPUTE_ADLER32 = 8
};
struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor;
/* Max size of LZ dictionary. */
#define TINFL_LZ_DICT_SIZE 32768
/* Return status. */
typedef enum
{
TINFL_STATUS_BAD_PARAM = -3,
TINFL_STATUS_ADLER32_MISMATCH = -2,
TINFL_STATUS_FAILED = -1,
TINFL_STATUS_DONE = 0,
TINFL_STATUS_NEEDS_MORE_INPUT = 1,
TINFL_STATUS_HAS_MORE_OUTPUT = 2
} tinfl_status;
/* Initializes the decompressor to its initial state. */
#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END
#define tinfl_get_adler32(r) (r)->m_check_adler32
/* 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. */
static 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
{
TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19,
TINFL_FAST_LOOKUP_BITS = 10, 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
#endif
#if TINFL_USE_64BIT_BITBUF
typedef mz_uint64 tinfl_bit_buf_t;
#define TINFL_BITBUF_SIZE (64)
#else
typedef mz_uint32 tinfl_bit_buf_t;
#define TINFL_BITBUF_SIZE (32)
#endif
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_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137];
};
#endif /* #ifdef TINFL_HEADER_INCLUDED */
/* ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) */
#ifndef TINFL_HEADER_FILE_ONLY
#ifdef MINIZ_NO_MALLOC
#define MZ_MALLOC(x) NULL
#define MZ_FREE(x) x, ((void)0)
#define MZ_REALLOC(p, x) NULL
#else
#define MZ_MALLOC(x) malloc(x)
#define MZ_FREE(x) free(x)
#define MZ_REALLOC(p, x) realloc(p, x)
#endif
#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))
#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
#define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
#define MZ_READ_LE32(p) *((const mz_uint32 *)(p))
#else
#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U))
#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U))
#endif
#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l)
#define TINFL_MEMSET(p, c, l) memset(p, c, l)
#define TINFL_CR_BEGIN switch(r->m_state) { case 0:
#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END
#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END
#define TINFL_CR_FINISH }
/* TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never */
/* reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. */
#define TINFL_GET_BYTE(state_index, c) do { \
if (pIn_buf_cur >= pIn_buf_end) { \
for ( ; ; ) { \
if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \
TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \
if (pIn_buf_cur < pIn_buf_end) { \
c = *pIn_buf_cur++; \
break; \
} \
} else { \
c = 0; \
break; \
} \
} \
} else c = *pIn_buf_cur++; } MZ_MACRO_END
#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n))
#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END
#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END
/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */
/* 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) \
do { \
temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \
if (temp >= 0) { \
code_len = temp >> 9; \
if ((code_len) && (num_bits >= code_len)) \
break; \
} else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \
code_len = TINFL_FAST_LOOKUP_BITS; \
do { \
temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
} while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \
} TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \
} while (num_bits < 15);
/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */
/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */
/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */
/* The slow path is only executed at the very end of the input buffer. */
#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \
int temp; mz_uint code_len, c; \
if (num_bits < 15) { \
if ((pIn_buf_end - pIn_buf_cur) < 2) { \
TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \
} else { \
bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \
} \
} \
if ((temp = (pHuff)->m_look_up[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)]; } while (temp < 0); \
} sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END
static void tinfl_def_free_func(void *opaque, void *address) {
(void)opaque, (void)address;
MZ_FREE(address);
}
static void *tinfl_def_alloc_func(void *opaque, unsigned int items, unsigned int size) {
(void)opaque, (void)items, (void)size;
return MZ_MALLOC(items * size);
}
static 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_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 };
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;
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). */
if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; }
num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start;
TINFL_CR_BEGIN
bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1;
if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
{
TINFL_GET_BYTE(1, r->m_zhdr0); 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)))));
if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); }
}
do
{
TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1;
if (r->m_type == 0)
{
TINFL_SKIP_BITS(5, num_bits & 7);
for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); }
if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); }
while ((counter) && (num_bits))
{
TINFL_GET_BITS(51, dist, 8);
while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); }
*pOut_buf_cur++ = (mz_uint8)dist;
counter--;
}
while (counter)
{
size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); }
while (pIn_buf_cur >= pIn_buf_end)
{
if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT)
{
TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT);
}
else
{
TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED);
}
}
n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter);
TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n;
}
}
else if (r->m_type == 3)
{
TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED);
}
else
{
if (r->m_type == 1)
{
mz_uint8 *p = r->m_tables[0].m_code_size; 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);
for ( i = 0; i <= 143; ++i) *p++ = 8;
for ( ; i <= 255; ++i) *p++ = 9;
for ( ; i <= 279; ++i) *p++ = 7;
for ( ; i <= 287; ++i) *p++ = 8;
}
else
{
for (counter = 0; counter < 3; counter++) { 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); 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_table_sizes[2] = 19;
}
for ( ; (int)r->m_type >= 0; r->m_type--)
{
int tree_next, tree_cur; tinfl_huff_table *pTable;
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);
for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++;
used_syms = 0, total = 0; next_code[0] = next_code[1] = 0;
for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); }
if ((65536 != total) && (used_syms > 1))
{
TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED);
}
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]; if (!code_size) continue;
cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1);
if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; }
if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; }
rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1);
for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--)
{
tree_cur -= ((rev_code >>= 1) & 1);
if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-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 -= ((rev_code >>= 1) & 1); pTable->m_tree[-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]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; }
if ((dist == 16) && (!counter))
{
TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED);
}
num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16];
TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s;
}
if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter)
{
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]);
}
}
for ( ; ; )
{
mz_uint8 *pSrc;
for ( ; ; )
{
if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2))
{
TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]);
if (counter >= 256)
break;
while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); }
*pOut_buf_cur++ = (mz_uint8)counter;
}
else
{
int sym2; mz_uint code_len;
#if TINFL_USE_64BIT_BITBUF
if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; }
#else
if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; }
#endif
if ((sym2 = r->m_tables[0].m_look_up[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)]; } while (sym2 < 0);
}
counter = sym2; bit_buf >>= code_len; num_bits -= code_len;
if (counter & 256)
break;
#if !TINFL_USE_64BIT_BITBUF
if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; }
#endif
if ((sym2 = r->m_tables[0].m_look_up[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)]; } while (sym2 < 0);
}
bit_buf >>= code_len; num_bits -= code_len;
pOut_buf_cur[0] = (mz_uint8)counter;
if (sym2 & 256)
{
pOut_buf_cur++;
counter = sym2;
break;
}
pOut_buf_cur[1] = (mz_uint8)sym2;
pOut_buf_cur += 2;
}
}
if ((counter &= 511) == 256) break;
num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257];
if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; }
TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]);
num_extra = s_dist_extra[dist]; dist = s_dist_base[dist];
if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; }
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))
{
TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED);
}
pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask);
if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end)
{
while (counter--)
{
while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); }
*pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask];
}
continue;
}
#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
else if ((counter >= 9) && (counter <= dist))
{
const mz_uint8 *pSrc_end = pSrc + (counter & ~7);
do
{
((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0];
((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1];
pOut_buf_cur += 8;
} while ((pSrc += 8) < pSrc_end);
if ((counter &= 7) < 3)
{
if (counter)
{
pOut_buf_cur[0] = pSrc[0];
if (counter > 1)
pOut_buf_cur[1] = pSrc[1];
pOut_buf_cur += counter;
}
continue;
}
}
#endif
do
{
pOut_buf_cur[0] = pSrc[0];
pOut_buf_cur[1] = pSrc[1];
pOut_buf_cur[2] = pSrc[2];
pOut_buf_cur += 3; pSrc += 3;
} while ((int)(counter -= 3) > 2);
if ((int)counter > 0)
{
pOut_buf_cur[0] = pSrc[0];
if ((int)counter > 1)
pOut_buf_cur[1] = pSrc[1];
pOut_buf_cur += counter;
}
}
}
} while (!(r->m_final & 1));
if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
{
TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; }
}
TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE);
TINFL_CR_FINISH
common_exit:
r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start;
*pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next;
if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0))
{
const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size;
mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552;
while (buf_len)
{
for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
{
s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1;
s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1;
}
for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1;
s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552;
}
r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH;
}
return status;
}
/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other stuff is for advanced use. */
enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 };
/* Return status codes. MZ_PARAM_ERROR is non-standard. */
enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 };
/* Compression levels. */
enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_DEFAULT_COMPRESSION = -1 };
/* Window bits */
#define MZ_DEFAULT_WINDOW_BITS 15
struct mz_internal_state;
/* Compression/decompression stream struct. */
typedef struct mz_stream_s
{
const unsigned char *next_in; /* pointer to next byte to read */
unsigned int avail_in; /* number of bytes available at next_in */
mz_ulong total_in; /* total number of bytes consumed so far */
unsigned char *next_out; /* pointer to next byte to write */
unsigned int avail_out; /* number of bytes that can be written to next_out */
mz_ulong total_out; /* total number of bytes produced so far */
char *msg; /* error msg (unused) */
struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */
mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */
mz_free_func zfree; /* optional heap free function (defaults to free) */
void *opaque; /* heap alloc function user pointer */
int data_type; /* data_type (unused) */
mz_ulong adler; /* adler32 of the source or uncompressed data */
mz_ulong reserved; /* not used */
} mz_stream;
typedef mz_stream *mz_streamp;
typedef struct
{
tinfl_decompressor m_decomp;
mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits;
mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];
tinfl_status m_last_status;
} inflate_state;
static int mz_inflateInit2(mz_streamp pStream, int window_bits)
{
inflate_state *pDecomp;
if (!pStream) return MZ_STREAM_ERROR;
if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR;
pStream->data_type = 0;
pStream->adler = 0;
pStream->msg = NULL;
pStream->total_in = 0;
pStream->total_out = 0;
pStream->reserved = 0;
if (!pStream->zalloc) pStream->zalloc = tinfl_def_alloc_func;
if (!pStream->zfree) pStream->zfree = tinfl_def_free_func;
pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state));
if (!pDecomp) return MZ_MEM_ERROR;
pStream->state = (struct mz_internal_state *)pDecomp;
tinfl_init(&pDecomp->m_decomp);
pDecomp->m_dict_ofs = 0;
pDecomp->m_dict_avail = 0;
pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
pDecomp->m_first_call = 1;
pDecomp->m_has_flushed = 0;
pDecomp->m_window_bits = window_bits;
return MZ_OK;
}
static int mz_inflate(mz_streamp pStream, int flush)
{
inflate_state* pState;
mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;
size_t in_bytes, out_bytes, orig_avail_in;
tinfl_status status;
if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR;
if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH;
if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR;
pState = (inflate_state*)pStream->state;
if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;
orig_avail_in = pStream->avail_in;
first_call = pState->m_first_call; pState->m_first_call = 0;
if (pState->m_last_status < 0) return MZ_DATA_ERROR;
if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR;
pState->m_has_flushed |= (flush == MZ_FINISH);
if ((flush == MZ_FINISH) && (first_call))
{
/* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */
decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
in_bytes = pStream->avail_in; out_bytes = pStream->avail_out;
status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags);
pState->m_last_status = status;
pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes;
pStream->adler = tinfl_get_adler32(&pState->m_decomp);
pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes;
if (status < 0)
return MZ_DATA_ERROR;
else if (status != TINFL_STATUS_DONE)
{
pState->m_last_status = TINFL_STATUS_FAILED;
return MZ_BUF_ERROR;
}
return MZ_STREAM_END;
}
/* flush != MZ_FINISH then we must assume there's more input. */
if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;
if (pState->m_dict_avail)
{
n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n;
pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
}
for ( ; ; )
{
in_bytes = pStream->avail_in;
out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;
status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);
pState->m_last_status = status;
pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes;
pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp);
pState->m_dict_avail = (mz_uint)out_bytes;
n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n;
pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
if (status < 0)
return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */
else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))
return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */
else if (flush == MZ_FINISH)
{
/* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */
if (status == TINFL_STATUS_DONE)
return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;
/* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */
else if (!pStream->avail_out)
return MZ_BUF_ERROR;
}
else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail))
break;
}
return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
}
static int mz_inflateEnd(mz_streamp pStream)
{
if (!pStream)
return MZ_STREAM_ERROR;
if (pStream->state)
{
pStream->zfree(pStream->opaque, pStream->state);
pStream->state = NULL;
}
return MZ_OK;
}
/* make this a drop-in replacement for zlib... */
#define voidpf void*
#define uInt unsigned int
#define z_stream mz_stream
#define inflateInit2 mz_inflateInit2
#define inflate mz_inflate
#define inflateEnd mz_inflateEnd
#define Z_SYNC_FLUSH MZ_SYNC_FLUSH
#define Z_FINISH MZ_FINISH
#define Z_OK MZ_OK
#define Z_STREAM_END MZ_STREAM_END
#define Z_NEED_DICT MZ_NEED_DICT
#define Z_ERRNO MZ_ERRNO
#define Z_STREAM_ERROR MZ_STREAM_ERROR
#define Z_DATA_ERROR MZ_DATA_ERROR
#define Z_MEM_ERROR MZ_MEM_ERROR
#define Z_BUF_ERROR MZ_BUF_ERROR
#define Z_VERSION_ERROR MZ_VERSION_ERROR
#define MAX_WBITS 15
#endif /* #ifndef TINFL_HEADER_FILE_ONLY */
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
*/

View file

@ -7,6 +7,7 @@
#include "seqplayer.h" #include "seqplayer.h"
#include "../pc/platform.h" #include "../pc/platform.h"
#include "../pc/fs/fs.h"
#define ALIGN16(val) (((val) + 0xF) & ~0xF) #define ALIGN16(val) (((val) + 0xF) & ~0xF)
@ -875,11 +876,8 @@ void load_sequence_internal(u32 player, u32 seqId, s32 loadAsync) {
# include <stdio.h> # include <stdio.h>
# include <stdlib.h> # include <stdlib.h>
static inline void *load_sound_res(const char *path) { static inline void *load_sound_res(const char *path) {
void *data = sys_load_res(path); void *data = fs_load_file(path, NULL);
if (!data) { if (!data) sys_fatal("could not load sound data from '%s'", path);
fprintf(stderr, "could not load sound data from '%s'\n", path);
abort();
}
// FIXME: figure out where it is safe to free this shit // FIXME: figure out where it is safe to free this shit
// can't free it immediately after in audio_init() // can't free it immediately after in audio_init()
return data; return data;

View file

@ -5,13 +5,20 @@
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <SDL2/SDL.h>
#if USE_SDL == 2
# include <SDL2/SDL.h>
# define WINDOWPOS_CENTERED SDL_WINDOWPOS_CENTERED
#else
# define WINDOWPOS_CENTERED 0
#endif
#include "platform.h" #include "platform.h"
#include "configfile.h" #include "configfile.h"
#include "cliopts.h" #include "cliopts.h"
#include "gfx/gfx_screen_config.h" #include "gfx/gfx_screen_config.h"
#include "controller/controller_api.h" #include "controller/controller_api.h"
#include "fs/fs.h"
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0])) #define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0]))
@ -38,8 +45,8 @@ struct ConfigOption {
// Video/audio stuff // Video/audio stuff
ConfigWindow configWindow = { ConfigWindow configWindow = {
.x = SDL_WINDOWPOS_CENTERED, .x = WINDOWPOS_CENTERED,
.y = SDL_WINDOWPOS_CENTERED, .y = WINDOWPOS_CENTERED,
.w = DESIRED_SCREEN_WIDTH, .w = DESIRED_SCREEN_WIDTH,
.h = DESIRED_SCREEN_HEIGHT, .h = DESIRED_SCREEN_HEIGHT,
.vsync = 1, .vsync = 1,
@ -130,7 +137,7 @@ static const struct ConfigOption options[] = {
// Reads an entire line from a file (excluding the newline character) and returns an allocated string // Reads an entire line from a file (excluding the newline character) and returns an allocated string
// Returns NULL if no lines could be read from the file // Returns NULL if no lines could be read from the file
static char *read_file_line(FILE *file) { static char *read_file_line(fs_file_t *file) {
char *buffer; char *buffer;
size_t bufferSize = 8; size_t bufferSize = 8;
size_t offset = 0; // offset in buffer to write size_t offset = 0; // offset in buffer to write
@ -138,7 +145,7 @@ static char *read_file_line(FILE *file) {
buffer = malloc(bufferSize); buffer = malloc(bufferSize);
while (1) { while (1) {
// Read a line from the file // Read a line from the file
if (fgets(buffer + offset, bufferSize - offset, file) == NULL) { if (fs_readline(file, buffer + offset, bufferSize - offset) == NULL) {
free(buffer); free(buffer);
return NULL; // Nothing could be read. return NULL; // Nothing could be read.
} }
@ -151,7 +158,7 @@ static char *read_file_line(FILE *file) {
break; break;
} }
if (feof(file)) // EOF was reached if (fs_eof(file)) // EOF was reached
break; break;
// If no newline or EOF was reached, then the whole line wasn't read. // If no newline or EOF was reached, then the whole line wasn't read.
@ -205,24 +212,17 @@ static unsigned int tokenize_string(char *str, int maxTokens, char **tokens) {
// Gets the config file path and caches it // Gets the config file path and caches it
const char *configfile_name(void) { const char *configfile_name(void) {
static char cfgpath[SYS_MAX_PATH] = { 0 }; return (gCLIOpts.ConfigFile[0]) ? gCLIOpts.ConfigFile : CONFIGFILE_DEFAULT;
if (!cfgpath[0]) {
if (gCLIOpts.ConfigFile[0])
snprintf(cfgpath, sizeof(cfgpath), "%s", gCLIOpts.ConfigFile);
else
snprintf(cfgpath, sizeof(cfgpath), "%s/%s", sys_save_path(), CONFIGFILE_DEFAULT);
}
return cfgpath;
} }
// Loads the config file specified by 'filename' // Loads the config file specified by 'filename'
void configfile_load(const char *filename) { void configfile_load(const char *filename) {
FILE *file; fs_file_t *file;
char *line; char *line;
printf("Loading configuration from '%s'\n", filename); printf("Loading configuration from '%s'\n", filename);
file = fopen(filename, "r"); file = fs_open(filename);
if (file == NULL) { if (file == NULL) {
// Create a new config file and save defaults // Create a new config file and save defaults
printf("Config file '%s' not found. Creating it.\n", filename); printf("Config file '%s' not found. Creating it.\n", filename);
@ -286,7 +286,7 @@ void configfile_load(const char *filename) {
free(line); free(line);
} }
fclose(file); fs_close(file);
} }
// Writes the config file to 'filename' // Writes the config file to 'filename'
@ -295,7 +295,7 @@ void configfile_save(const char *filename) {
printf("Saving configuration to '%s'\n", filename); printf("Saving configuration to '%s'\n", filename);
file = fopen(filename, "w"); file = fopen(fs_get_write_path(filename), "w");
if (file == NULL) { if (file == NULL) {
// error // error
return; return;

View file

@ -14,6 +14,7 @@
#include "controller_sdl.h" #include "controller_sdl.h"
#include "../configfile.h" #include "../configfile.h"
#include "../platform.h" #include "../platform.h"
#include "../fs/fs.h"
#include "game/level_update.h" #include "game/level_update.h"
@ -92,15 +93,17 @@ static void controller_sdl_init(void) {
} }
// try loading an external gamecontroller mapping file // try loading an external gamecontroller mapping file
char gcpath[SYS_MAX_PATH]; uint64_t gcsize = 0;
snprintf(gcpath, sizeof(gcpath), "%s/gamecontrollerdb.txt", sys_save_path()); void *gcdata = fs_load_file("gamecontrollerdb.txt", &gcsize);
int nummaps = SDL_GameControllerAddMappingsFromFile(gcpath); if (gcdata && gcsize) {
if (nummaps < 0) { SDL_RWops *rw = SDL_RWFromConstMem(gcdata, gcsize);
snprintf(gcpath, sizeof(gcpath), "%s/gamecontrollerdb.txt", sys_data_path()); if (rw) {
nummaps = SDL_GameControllerAddMappingsFromFile(gcpath); int nummaps = SDL_GameControllerAddMappingsFromRW(rw, SDL_TRUE);
if (nummaps >= 0)
printf("loaded %d controller mappings from 'gamecontrollerdb.txt'\n", nummaps);
}
free(gcdata);
} }
if (nummaps >= 0)
printf("loaded %d controller mappings from '%s'\n", nummaps, gcpath);
#ifdef BETTERCAMERA #ifdef BETTERCAMERA
if (newcam_mouse == 1) if (newcam_mouse == 1)

137
src/pc/fs/dirtree.c Normal file
View file

@ -0,0 +1,137 @@
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "../platform.h"
#include "fs.h"
#include "dirtree.h"
static inline uint32_t dirtree_hash(const char *s, size_t len) {
// djb hash
uint32_t hash = 5381;
while (len--) hash = ((hash << 5) + hash) ^ *(s++);
return hash & (FS_NUMBUCKETS - 1);
}
bool fs_dirtree_init(fs_dirtree_t *tree, const size_t entry_len) {
memset(tree, 0, sizeof(*tree));
tree->root = malloc(entry_len);
if (!tree->root) return false;
tree->root->name = ""; // root
tree->root->is_dir = true;
tree->entry_len = entry_len;
return true;
}
void fs_dirtree_free(fs_dirtree_t *tree) {
if (!tree) return;
if (tree->root) free(tree->root);
for (int i = 0; i < FS_NUMBUCKETS; ++i) {
fs_dirtree_entry_t *ent, *next;
for (ent = tree->buckets[i]; ent; ent = next) {
next = ent->next_hash;
free(ent);
}
}
}
static inline fs_dirtree_entry_t *dirtree_add_ancestors(fs_dirtree_t *tree, char *name) {
fs_dirtree_entry_t *ent = tree->root;
// look for parent directory
char *last_sep = strrchr(name, '/');
if (!last_sep) return ent;
*last_sep = 0;
ent = fs_dirtree_find(tree, name);
if (ent) {
*last_sep = '/'; // put the separator back
return ent; // parent directory already in tree
}
// add the parent directory
ent = fs_dirtree_add(tree, name, true);
*last_sep = '/';
return ent;
}
fs_dirtree_entry_t *fs_dirtree_add(fs_dirtree_t *tree, char *name, const bool is_dir) {
fs_dirtree_entry_t *ent = fs_dirtree_find(tree, name);
if (ent) return ent;
// add the parent directory into the tree first
fs_dirtree_entry_t *parent = dirtree_add_ancestors(tree, name);
if (!parent) return NULL;
// we'll plaster the name at the end of the allocated chunk, after the actual entry
const size_t name_len = strlen(name);
const size_t allocsize = tree->entry_len + name_len + 1;
ent = calloc(1, allocsize);
if (!ent) return NULL;
ent->name = (const char *)ent + tree->entry_len;
strcpy((char *)ent->name, name);
const uint32_t hash = dirtree_hash(name, name_len);
ent->next_hash = tree->buckets[hash];
tree->buckets[hash] = ent;
ent->next_sibling = parent->next_child;
ent->is_dir = is_dir;
parent->next_child = ent;
return ent;
}
fs_dirtree_entry_t *fs_dirtree_find(fs_dirtree_t *tree, const char *name) {
if (!name) return NULL;
if (!*name) return tree->root;
const uint32_t hash = dirtree_hash(name, strlen(name));
fs_dirtree_entry_t *ent, *prev = NULL;
for (ent = tree->buckets[hash]; ent; ent = ent->next_hash) {
if (!strcmp(ent->name, name)) {
// if this path is not in front of the hash list, move it to the front
// in case of reccurring searches
if (prev) {
prev->next_hash = ent->next_hash;
ent->next_hash = tree->buckets[hash];
tree->buckets[hash] = ent;
}
return ent;
}
prev = ent;
}
return NULL;
}
static fs_walk_result_t dirtree_walk_impl(fs_dirtree_entry_t *ent, walk_fn_t walkfn, void *user, const bool recur) {
fs_walk_result_t res = FS_WALK_SUCCESS;;
ent = ent->next_child;
while (ent && (res == FS_WALK_SUCCESS)) {
if (ent->is_dir) {
if (recur && ent->next_child)
res = dirtree_walk_impl(ent, walkfn, user, recur);
} else if (!walkfn(user, ent->name)) {
res = FS_WALK_INTERRUPTED;
break;
}
ent = ent->next_sibling;
}
return res;
}
fs_walk_result_t fs_dirtree_walk(void *pack, const char *base, walk_fn_t walkfn, void *user, const bool recur) {
fs_dirtree_t *tree = (fs_dirtree_t *)pack;
fs_dirtree_entry_t *ent = fs_dirtree_find(tree, base);
if (!ent) return FS_WALK_NOTFOUND;
return dirtree_walk_impl(ent, walkfn, user, recur);
}

32
src/pc/fs/dirtree.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef _SM64_DIRTREE_H_
#define _SM64_DIRTREE_H_
#include <stdlib.h>
#include <stdbool.h>
#include "fs.h"
#define FS_NUMBUCKETS 64
typedef struct fs_dirtree_entry_s {
const char *name;
bool is_dir;
struct fs_dirtree_entry_s *next_hash, *next_child, *next_sibling;
} fs_dirtree_entry_t;
typedef struct {
fs_dirtree_entry_t *root;
fs_dirtree_entry_t *buckets[FS_NUMBUCKETS];
size_t entry_len;
} fs_dirtree_t;
bool fs_dirtree_init(fs_dirtree_t *tree, const size_t entry_len);
void fs_dirtree_free(fs_dirtree_t *tree);
fs_dirtree_entry_t *fs_dirtree_add(fs_dirtree_t *tree, char *name, const bool is_dir);
fs_dirtree_entry_t *fs_dirtree_find(fs_dirtree_t *tree, const char *name);
// the first arg is void* so this could be used in walk() methods of various packtypes
fs_walk_result_t fs_dirtree_walk(void *tree, const char *base, walk_fn_t walkfn, void *user, const bool recur);
#endif // _SM64_DIRTREE_H_

443
src/pc/fs/fs.c Normal file
View file

@ -0,0 +1,443 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <ctype.h>
#ifdef _WIN32
#include <direct.h>
#endif
#include "macros.h"
#include "../platform.h"
#include "fs.h"
char fs_gamedir[SYS_MAX_PATH] = "";
char fs_writepath[SYS_MAX_PATH] = "";
struct fs_dir_s {
void *pack;
const char *realpath;
fs_packtype_t *packer;
struct fs_dir_s *prev, *next;
};
extern fs_packtype_t fs_packtype_dir;
extern fs_packtype_t fs_packtype_zip;
static fs_packtype_t *fs_packers[] = {
&fs_packtype_dir,
&fs_packtype_zip,
};
static fs_dir_t *fs_searchpaths = NULL;
static inline fs_dir_t *fs_find_dir(const char *realpath) {
for (fs_dir_t *dir = fs_searchpaths; dir; dir = dir->next)
if (!sys_strcasecmp(realpath, dir->realpath))
return dir;
return NULL;
}
static int mount_cmp(const void *p1, const void *p2) {
const char *s1 = *(const char **)p1;
const char *s2 = *(const char **)p2;
// check if one or both of these are basepacks
const int plen = strlen(FS_BASEPACK_PREFIX);
const bool is_base1 = !strncmp(s1, FS_BASEPACK_PREFIX, plen);
const bool is_base2 = !strncmp(s2, FS_BASEPACK_PREFIX, plen);
// if both are basepacks, compare the postfixes only
if (is_base1 && is_base2) return strcmp(s1 + plen, s2 + plen);
// if only one is a basepack, it goes first
if (is_base1) return -1;
if (is_base2) return 1;
// otherwise strcmp order
return strcmp(s1, s2);
}
static void scan_path_dir(const char *ropath, const char *dir) {
char dirpath[SYS_MAX_PATH];
snprintf(dirpath, sizeof(dirpath), "%s/%s", ropath, dir);
if (!fs_sys_dir_exists(dirpath)) return;
// since filename order in readdir() isn't guaranteed, collect paths and sort them in strcmp() order
// (but with basepacks first)
fs_pathlist_t plist = fs_sys_enumerate(dirpath, false);
if (plist.paths) {
qsort(plist.paths, plist.numpaths, sizeof(char *), mount_cmp);
for (int i = 0; i < plist.numpaths; ++i)
fs_mount(plist.paths[i]);
fs_pathlist_free(&plist);
}
// mount the directory itself
fs_mount(dirpath);
}
bool fs_init(const char **rodirs, const char *gamedir, const char *writepath) {
char buf[SYS_MAX_PATH];
// expand and remember the write path
strncpy(fs_writepath, fs_convert_path(buf, sizeof(buf), writepath), sizeof(fs_writepath));
fs_writepath[sizeof(fs_writepath)-1] = 0;
printf("fs: writepath set to `%s`\n", fs_writepath);
// remember the game directory name
strncpy(fs_gamedir, gamedir, sizeof(fs_gamedir));
fs_gamedir[sizeof(fs_gamedir)-1] = 0;
printf("fs: gamedir set to `%s`\n", fs_gamedir);
// first, scan all possible paths and mount all basedirs in them
for (const char **p = rodirs; p && *p; ++p)
scan_path_dir(fs_convert_path(buf, sizeof(buf), *p), FS_BASEDIR);
scan_path_dir(fs_writepath, FS_BASEDIR);
// then mount all the gamedirs in them, if the game dir isn't the same
if (sys_strcasecmp(FS_BASEDIR, fs_gamedir)) {
for (const char **p = rodirs; p && *p; ++p)
scan_path_dir(fs_convert_path(buf, sizeof(buf), *p), fs_gamedir);
scan_path_dir(fs_writepath, fs_gamedir);
}
// as a special case, mount writepath itself
fs_mount(fs_writepath);
return true;
}
bool fs_mount(const char *realpath) {
if (fs_find_dir(realpath))
return false; // already mounted
const char *ext = sys_file_extension(realpath);
void *pack = NULL;
fs_packtype_t *packer = NULL;
bool tried = false;
for (unsigned int i = 0; i < sizeof(fs_packers) / sizeof(fs_packers[0]); ++i) {
if (ext && sys_strcasecmp(ext, fs_packers[i]->extension))
continue;
tried = true;
pack = fs_packers[i]->mount(realpath);
if (pack) {
packer = fs_packers[i];
break;
}
}
if (!pack || !packer) {
if (tried)
fprintf(stderr, "fs: could not mount '%s'\n", realpath);
return false;
}
fs_dir_t *dir = calloc(1, sizeof(fs_dir_t));
if (!dir) {
packer->unmount(pack);
return false;
}
dir->pack = pack;
dir->realpath = sys_strdup(realpath);
dir->packer = packer;
dir->prev = NULL;
dir->next = fs_searchpaths;
if (fs_searchpaths)
fs_searchpaths->prev = dir;
fs_searchpaths = dir;
printf("fs: mounting '%s'\n", realpath);
return true;
}
bool fs_unmount(const char *realpath) {
fs_dir_t *dir = fs_find_dir(realpath);
if (dir) {
dir->packer->unmount(dir->pack);
free((void *)dir->realpath);
if (dir->prev) dir->prev->next = dir->next;
if (dir->next) dir->next->prev = dir->prev;
if (dir == fs_searchpaths) fs_searchpaths = dir->next;
free(dir);
return true;
}
return false;
}
fs_walk_result_t fs_walk(const char *base, walk_fn_t walkfn, void *user, const bool recur) {
bool found = false;
for (fs_dir_t *dir = fs_searchpaths; dir; dir = dir->next) {
fs_walk_result_t res = dir->packer->walk(dir->pack, base, walkfn, user, recur);
if (res == FS_WALK_INTERRUPTED)
return res;
if (res != FS_WALK_NOTFOUND)
found = true;
}
return found ? FS_WALK_SUCCESS : FS_WALK_NOTFOUND;
}
bool fs_is_file(const char *fname) {
for (fs_dir_t *dir = fs_searchpaths; dir; dir = dir->next) {
if (dir->packer->is_file(dir->pack, fname))
return true;
}
return false;
}
bool fs_is_dir(const char *fname) {
for (fs_dir_t *dir = fs_searchpaths; dir; dir = dir->next) {
if (dir->packer->is_dir(dir->pack, fname))
return true;
}
return false;
}
fs_file_t *fs_open(const char *vpath) {
for (fs_dir_t *dir = fs_searchpaths; dir; dir = dir->next) {
fs_file_t *f = dir->packer->open(dir->pack, vpath);
if (f) {
f->parent = dir;
return f;
}
}
return NULL;
}
void fs_close(fs_file_t *file) {
if (!file) return;
file->parent->packer->close(file->parent->pack, file);
}
int64_t fs_read(fs_file_t *file, void *buf, const uint64_t size) {
if (!file) return -1;
return file->parent->packer->read(file->parent->pack, file, buf, size);
}
bool fs_seek(fs_file_t *file, const int64_t ofs) {
if (!file) return -1;
return file->parent->packer->seek(file->parent->pack, file, ofs);
}
int64_t fs_tell(fs_file_t *file) {
if (!file) return -1;
return file->parent->packer->tell(file->parent->pack, file);
}
int64_t fs_size(fs_file_t *file) {
if (!file) return -1;
return file->parent->packer->size(file->parent->pack, file);
}
bool fs_eof(fs_file_t *file) {
if (!file) return true;
return file->parent->packer->eof(file->parent->pack, file);
}
struct matchdata_s {
const char *prefix;
size_t prefix_len;
char *dst;
size_t dst_len;
};
static bool match_walk(void *user, const char *path) {
struct matchdata_s *data = (struct matchdata_s *)user;
if (!strncmp(path, data->prefix, data->prefix_len)) {
// found our lad, copy path to destination and terminate
strncpy(data->dst, path, data->dst_len);
data->dst[data->dst_len - 1] = 0;
return false;
}
return true;
}
const char *fs_match(char *outname, const size_t outlen, const char *prefix) {
struct matchdata_s data = {
.prefix = prefix,
.prefix_len = strlen(prefix),
.dst = outname,
.dst_len = outlen,
};
if (fs_walk("", match_walk, &data, true) == FS_WALK_INTERRUPTED)
return outname;
return NULL;
}
static bool enumerate_walk(void *user, const char *path) {
fs_pathlist_t *data = (fs_pathlist_t *)user;
if (data->listcap == data->numpaths) {
data->listcap *= 2;
char **newpaths = realloc(data->paths, data->listcap * sizeof(char *));
if (!newpaths) return false;
data->paths = newpaths;
}
data->paths[data->numpaths++] = sys_strdup(path);
return true;
}
fs_pathlist_t fs_enumerate(const char *base, const bool recur) {
char **paths = malloc(sizeof(char *) * 32);
fs_pathlist_t pathlist = { paths, 0, 32 };
if (!paths) return pathlist;
if (fs_walk(base, enumerate_walk, &pathlist, recur) == FS_WALK_INTERRUPTED)
fs_pathlist_free(&pathlist);
return pathlist;
}
void fs_pathlist_free(fs_pathlist_t *pathlist) {
if (!pathlist || !pathlist->paths) return;
for (int i = 0; i < pathlist->numpaths; ++i)
free(pathlist->paths[i]);
free(pathlist->paths);
pathlist->paths = NULL;
pathlist->numpaths = 0;
}
const char *fs_readline(fs_file_t *file, char *dst, uint64_t size) {
int64_t rx = 0;
char chr, *p;
// assume we got buffered input
for (p = dst, size--; size > 0; size--) {
if ((rx = fs_read(file, &chr, 1)) <= 0)
break;
*p++ = chr;
if (chr == '\n')
break;
}
*p = 0;
if (p == dst || rx <= 0)
return NULL;
return p;
}
void *fs_load_file(const char *vpath, uint64_t *outsize) {
fs_file_t *f = fs_open(vpath);
if (!f) return NULL;
int64_t size = fs_size(f);
if (size <= 0) {
fs_close(f);
return NULL;
}
void *buf = malloc(size);
if (!buf) {
fs_close(f);
return NULL;
}
int64_t rx = fs_read(f, buf, size);
fs_close(f);
if (rx < size) {
free(buf);
return NULL;
}
if (outsize) *outsize = size;
return buf;
}
const char *fs_get_write_path(const char *vpath) {
static char path[SYS_MAX_PATH];
snprintf(path, sizeof(path), "%s/%s", fs_writepath, vpath);
return path;
}
const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path) {
// ! means "executable directory"
if (path[0] == '!') {
snprintf(buf, bufsiz, "%s%s", sys_exe_path(), path + 1);
} else {
strncpy(buf, path, bufsiz);
buf[bufsiz-1] = 0;
}
// change all backslashes
for (char *p = buf; *p; ++p)
if (*p == '\\') *p = '/';
return buf;
}
/* these operate on the real file system */
bool fs_sys_file_exists(const char *name) {
struct stat st;
return (stat(name, &st) == 0 && S_ISREG(st.st_mode));
}
bool fs_sys_dir_exists(const char *name) {
struct stat st;
return (stat(name, &st) == 0 && S_ISDIR(st.st_mode));
}
bool fs_sys_walk(const char *base, walk_fn_t walk, void *user, const bool recur) {
char fullpath[SYS_MAX_PATH];
DIR *dir;
struct dirent *ent;
if (!(dir = opendir(base))) {
fprintf(stderr, "fs_dir_walk(): could not open `%s`\n", base);
return false;
}
bool ret = true;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] == 0 || ent->d_name[0] == '.') continue; // skip ./.. and hidden files
snprintf(fullpath, sizeof(fullpath), "%s/%s", base, ent->d_name);
if (fs_sys_dir_exists(fullpath)) {
if (recur) {
if (!fs_sys_walk(fullpath, walk, user, recur)) {
ret = false;
break;
}
}
} else {
if (!walk(user, fullpath)) {
ret = false;
break;
}
}
}
closedir(dir);
return ret;
}
fs_pathlist_t fs_sys_enumerate(const char *base, const bool recur) {
char **paths = malloc(sizeof(char *) * 32);
fs_pathlist_t pathlist = { paths, 0, 32 };
if (!paths) return pathlist;
if (!fs_sys_walk(base, enumerate_walk, &pathlist, recur))
fs_pathlist_free(&pathlist);
return pathlist;
}
bool fs_sys_mkdir(const char *name) {
#ifdef _WIN32
return _mkdir(name) == 0;
#else
return mkdir(name, 0777) == 0;
#endif
}

136
src/pc/fs/fs.h Normal file
View file

@ -0,0 +1,136 @@
#ifndef _SM64_FS_H_
#define _SM64_FS_H_
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include "../platform.h"
// FS_BASEDIR is usually defined in the build script
#ifndef FS_BASEDIR
# define FS_BASEDIR "res"
#endif
#ifndef FS_BASEPACK_PREFIX
# define FS_BASEPACK_PREFIX "base"
#endif
#define FS_TEXTUREDIR "gfx"
#define FS_SOUNDDIR "sound"
extern char fs_gamedir[];
extern char fs_userdir[];
extern const char *fs_ropaths[];
// receives the full path
// should return `true` if traversal should continue
// first arg is user data
typedef bool (*walk_fn_t)(void *, const char *);
typedef enum {
FS_WALK_SUCCESS = 0,
FS_WALK_INTERRUPTED = 1,
FS_WALK_NOTFOUND = 2,
FS_WALK_ERROR = 4,
} fs_walk_result_t;
// opaque searchpath directory type
typedef struct fs_dir_s fs_dir_t;
// virtual file handle
typedef struct fs_file_s {
void *handle; // opaque packtype-defined data
fs_dir_t *parent; // directory containing this file
} fs_file_t;
// list of paths; returned by fs_enumerate()
typedef struct {
char **paths;
int numpaths;
int listcap;
} fs_pathlist_t;
typedef struct {
const char *extension; // file extensions of this pack type
void *(*mount)(const char *rpath); // open and initialize pack at real path `rpath`
void (*unmount)(void *pack); // free pack
// walks the specified directory inside this pack, calling walkfn for each file
// returns FS_WALK_SUCCESS if the directory was successfully opened and walk() didn't ever return false
// returns FS_WALK_INTERRUPTED if the traversal started but walk() returned false at some point
// if recur is true, will recurse into subfolders
fs_walk_result_t (*walk)(void *pack, const char *base, walk_fn_t walkfn, void *user, const bool recur);
bool (*is_file)(void *pack, const char *path); // returns true if `path` exists in this pack and is a file
bool (*is_dir)(void *pack, const char *path); // returns true if `path` exists in this pack and is a directory
// file I/O functions; paths are virtual
fs_file_t *(*open)(void *pack, const char *path); // opens a virtual file contained in this pack for reading, returns NULL in case of error
int64_t (*read)(void *pack, fs_file_t *file, void *buf, const uint64_t size); // returns -1 in case of error
bool (*seek)(void *pack, fs_file_t *file, const int64_t ofs); // returns true if seek succeeded
int64_t (*tell)(void *pack, fs_file_t *file); // returns -1 in case of error, current virtual file position otherwise
int64_t (*size)(void *pack, fs_file_t *file); // returns -1 in case of error, size of the (uncompressed) file otherwise
bool (*eof)(void *pack, fs_file_t *file); // returns true if there's nothing more to read
void (*close)(void *pack, fs_file_t *file); // closes a virtual file previously opened with ->open()
} fs_packtype_t;
// takes the supplied NULL-terminated list of read-only directories and mounts all the packs in them,
// then mounts the directories themselves, then mounts all the packs in `gamedir`, then mounts `gamedir` itself,
// then does the same with `userdir`
// initializes the `fs_gamedir` and `fs_userdir` variables
bool fs_init(const char **rodirs, const char *gamedir, const char *userdir);
// mounts the pack at physical path `realpath` to the root of the filesystem
// packs mounted later take priority over packs mounted earlier
bool fs_mount(const char *realpath);
// removes the pack at physical path from the virtual filesystem
bool fs_unmount(const char *realpath);
/* generalized filesystem functions that call matching packtype functions for each pack in the searchpath */
// FIXME: this can walk in unorthodox patterns, since it goes through mountpoints linearly
fs_walk_result_t fs_walk(const char *base, walk_fn_t walkfn, void *user, const bool recur);
// returns a list of files in the `base` directory
fs_pathlist_t fs_enumerate(const char *base, const bool recur);
// call this on a list returned by fs_enumerate() to free it
void fs_pathlist_free(fs_pathlist_t *pathlist);
bool fs_is_file(const char *fname);
bool fs_is_dir(const char *fname);
fs_file_t *fs_open(const char *vpath);
void fs_close(fs_file_t *file);
int64_t fs_read(fs_file_t *file, void *buf, const uint64_t size);
const char *fs_readline(fs_file_t *file, char *dst, const uint64_t size);
bool fs_seek(fs_file_t *file, const int64_t ofs);
int64_t fs_tell(fs_file_t *file);
int64_t fs_size(fs_file_t *file);
bool fs_eof(fs_file_t *file);
void *fs_load_file(const char *vpath, uint64_t *outsize);
const char *fs_readline(fs_file_t *file, char *dst, uint64_t size);
// tries to find the first file with the filename that starts with `prefix`
// puts full filename into `outname` and returns it or returns NULL if nothing matches
const char *fs_match(char *outname, const size_t outlen, const char *prefix);
// takes a virtual path and prepends the write path to it
const char *fs_get_write_path(const char *vpath);
// expands special chars in paths and changes backslashes to forward slashes
const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path);
/* these operate on the real filesystem and are used by fs_packtype_dir */
bool fs_sys_walk(const char *base, walk_fn_t walk, void *user, const bool recur);
fs_pathlist_t fs_sys_enumerate(const char *base, const bool recur);
bool fs_sys_file_exists(const char *name);
bool fs_sys_dir_exists(const char *name);
bool fs_sys_mkdir(const char *name); // creates with 0777 by default
#endif // _SM64_FS_H_

117
src/pc/fs/fs_packtype_dir.c Normal file
View file

@ -0,0 +1,117 @@
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include "macros.h"
#include "../platform.h"
#include "fs.h"
static void *pack_dir_mount(const char *realpath) {
if (!fs_sys_dir_exists(realpath))
return NULL;
// the pack is actually just the real folder path
void *pack = (void *)sys_strdup(realpath);
return pack;
}
static void pack_dir_unmount(void *pack) {
free(pack);
}
struct walkdata_s {
size_t baselen;
walk_fn_t userwalk;
void *userdata;
};
// wrap the actual user walk function to return virtual paths instead of real paths
static bool packdir_walkfn(void *userdata, const char *path) {
struct walkdata_s *walk = (struct walkdata_s *)userdata;
return walk->userwalk(walk->userdata, path + walk->baselen);
}
static fs_walk_result_t pack_dir_walk(void *pack, const char *base, walk_fn_t walkfn, void *user, const bool recur) {
char path[SYS_MAX_PATH];
snprintf(path, SYS_MAX_PATH, "%s/%s", (const char *)pack, base);
if (!fs_sys_dir_exists(path))
return FS_WALK_NOTFOUND;
struct walkdata_s walkdata = { strlen((const char *)pack) + 1, walkfn, user };
return fs_sys_walk(path, packdir_walkfn, &walkdata, recur);
}
static bool pack_dir_is_file(void *pack, const char *fname) {
char path[SYS_MAX_PATH];
snprintf(path, sizeof(path), "%s/%s", (const char *)pack, fname);
return fs_sys_dir_exists(path);
}
static bool pack_dir_is_dir(void *pack, const char *fname) {
char path[SYS_MAX_PATH];
snprintf(path, sizeof(path), "%s/%s", (const char *)pack, fname);
return fs_sys_file_exists(path);
}
static fs_file_t *pack_dir_open(void *pack, const char *vpath) {
char path[SYS_MAX_PATH];
snprintf(path, sizeof(path), "%s/%s", (const char *)pack, vpath);
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fs_file_t *fsfile = malloc(sizeof(fs_file_t));
if (!fsfile) { fclose(f); return NULL; }
fsfile->parent = NULL;
fsfile->handle = f;
return fsfile;
}
static void pack_dir_close(UNUSED void *pack, fs_file_t *file) {
fclose((FILE *)file->handle);
free(file);
}
static int64_t pack_dir_read(UNUSED void *pack, fs_file_t *file, void *buf, const uint64_t size) {
return fread(buf, 1, size, (FILE *)file->handle);
}
static bool pack_dir_seek(UNUSED void *pack, fs_file_t *file, const int64_t ofs) {
return fseek((FILE *)file->handle, ofs, SEEK_SET) == 0;
}
static int64_t pack_dir_tell(UNUSED void *pack, fs_file_t *file) {
return ftell((FILE *)file->handle);
}
static int64_t pack_dir_size(UNUSED void *pack, fs_file_t *file) {
int64_t oldofs = ftell((FILE *)file->handle);
fseek((FILE *)file->handle, 0, SEEK_END);
int64_t size = ftell((FILE *)file->handle);
fseek((FILE *)file->handle, oldofs, SEEK_SET);
return size;
}
static bool pack_dir_eof(UNUSED void *pack, fs_file_t *file) {
return feof((FILE *)file->handle);
}
fs_packtype_t fs_packtype_dir = {
"",
pack_dir_mount,
pack_dir_unmount,
pack_dir_walk,
pack_dir_is_file,
pack_dir_is_dir,
pack_dir_open,
pack_dir_read,
pack_dir_seek,
pack_dir_tell,
pack_dir_size,
pack_dir_eof,
pack_dir_close,
};

486
src/pc/fs/fs_packtype_zip.c Normal file
View file

@ -0,0 +1,486 @@
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <tinfl.h>
#include "macros.h"
#include "../platform.h"
#include "fs.h"
#include "dirtree.h"
#define ZIP_BUFSIZE 16384
#define ZIP_EOCD_BUFSIZE 65578
#define ZIP_LFH_SIG 0x04034b50
#define ZIP_CDH_SIG 0x02014b50
#define ZIP_EOCD_SIG 0x06054b50
typedef struct {
fs_dirtree_t tree; // this should always be first, so this could be used as a dirtree root
const char *realpath; // physical path to the zip file
FILE *zipf; // open zip file handle, if any
} zip_pack_t;
typedef struct {
fs_dirtree_entry_t tree; // this should always be first, so this could be used as a dirtree entry
uint64_t ofs; // offset to compressed data in zip
uint16_t bits; // general purpose zip flags
uint16_t comptype; // compression method
uint32_t crc; // CRC-32
uint64_t comp_size; // size of compressed data in zip
uint64_t uncomp_size; // size of decompressed data
uint16_t attr_int; // internal attributes
uint32_t attr_ext; // external attributes
bool ofs_fixed; // if true, `ofs` points to the file data, otherwise to LFH
} zip_entry_t;
typedef struct {
zip_entry_t *entry; // the dirtree entry of this file
uint32_t comp_pos; // read position in compressed data
uint32_t uncomp_pos; // read position in uncompressed data
uint8_t *buffer; // decompression buffer (if compressed)
z_stream zstream; // tinfl zlib stream
FILE *fstream; // duplicate of zipf of the parent zip file
} zip_file_t;
static int64_t zip_find_eocd(FILE *f, int64_t *outlen) {
// the EOCD is somewhere in the last 65557 bytes of the file
// get the total file size
fseek(f, 0, SEEK_END);
const int64_t fsize = ftell(f);
if (fsize <= 16) return -1; // probably not a zip
const int64_t rx = (fsize < ZIP_EOCD_BUFSIZE ? fsize : ZIP_EOCD_BUFSIZE);
uint8_t *buf = malloc(rx);
if (!buf) return -1;
// read that entire chunk and search for EOCD backwards from the end
fseek(f, fsize - rx, SEEK_SET);
if (fread(buf, rx, 1, f)) {
for (int64_t i = rx - 8; i >= 0; --i) {
if ((buf[i + 0] == 0x50) && (buf[i + 1] == 0x4B) &&
(buf[i + 2] == 0x05) && (buf[i + 3] == 0x06)) {
// gotem
free(buf);
if (outlen) *outlen = fsize;
return fsize - rx + i;
}
}
}
free(buf);
return -1;
}
static bool zip_parse_eocd(FILE *f, uint64_t *cdir_ofs, uint64_t *data_ofs, uint64_t *count) {
int64_t fsize = 0;
// EOCD record struct
struct eocd_s {
uint32_t sig;
uint16_t this_disk;
uint16_t cdir_disk;
uint16_t disk_entry_count;
uint16_t total_entry_count;
uint32_t cdir_size;
uint32_t cdir_ofs;
uint16_t comment_len;
// zip comment follows
} __attribute__((__packed__));
struct eocd_s eocd;
// find the EOCD and seek to it
int64_t pos = zip_find_eocd(f, &fsize);
if (pos < 0) return false;
fseek(f, pos, SEEK_SET);
// read it
if (!fread(&eocd, sizeof(eocd), 1, f)) return false;
// double check the sig
if (LE_TO_HOST32(eocd.sig) != ZIP_EOCD_SIG) return false;
// disks should all be 0
if (eocd.this_disk || eocd.cdir_disk) return false;
// total entry count should be the same as disk entry count
if (eocd.disk_entry_count != eocd.total_entry_count) return false;
*count = LE_TO_HOST16(eocd.total_entry_count);
*cdir_ofs = LE_TO_HOST32(eocd.cdir_ofs);
eocd.cdir_size = LE_TO_HOST32(eocd.cdir_size);
// end of central dir can't be before central dir
if ((uint64_t)pos < *cdir_ofs + eocd.cdir_size) return false;
*data_ofs = (uint64_t)(pos - (*cdir_ofs + eocd.cdir_size));
*cdir_ofs += *data_ofs;
// make sure end of comment matches end of file
eocd.comment_len = LE_TO_HOST16(eocd.comment_len);
return ((pos + 22 + eocd.comment_len) == fsize);
}
static bool zip_fixup_offset(zip_file_t *zipfile) {
// LFH record struct
struct lfh_s {
uint32_t sig;
uint16_t version_required;
uint16_t bits;
uint16_t comptype;
uint16_t mod_time;
uint16_t mod_date;
uint32_t crc;
uint32_t comp_size;
uint32_t uncomp_size;
uint16_t fname_len;
uint16_t extra_len;
// file name, extra field and data follow
} __attribute__((__packed__));
struct lfh_s lfh;
zip_entry_t *ent = zipfile->entry;
fseek(zipfile->fstream, ent->ofs, SEEK_SET);
if (!fread(&lfh, sizeof(lfh), 1, zipfile->fstream)) return false;
// we only need these two
lfh.fname_len = LE_TO_HOST16(lfh.fname_len);
lfh.extra_len = LE_TO_HOST16(lfh.extra_len);
// ofs will now point to actual data
ent->ofs += sizeof(lfh) + lfh.fname_len + lfh.extra_len;
ent->ofs_fixed = true; // only need to do this once
return true;
}
static zip_entry_t *zip_load_entry(FILE *f, fs_dirtree_t *tree, const uint64_t data_ofs) {
// CDH record struct
struct cdh_s {
uint32_t sig;
uint16_t version_used;
uint16_t version_required;
uint16_t bits;
uint16_t comptype;
uint16_t mod_time;
uint16_t mod_date;
uint32_t crc;
uint32_t comp_size;
uint32_t uncomp_size;
uint16_t fname_len;
uint16_t extra_len;
uint16_t comment_len;
uint16_t start_disk;
uint16_t attr_int;
uint32_t attr_ext;
uint32_t lfh_ofs;
// file name, extra field and comment follow
} __attribute__((__packed__));
struct cdh_s cdh;
zip_entry_t zipent;
memset(&zipent, 0, sizeof(zipent));
if (!fread(&cdh, sizeof(cdh), 1, f)) return NULL;
// check cdir entry header signature
if (LE_TO_HOST32(cdh.sig) != ZIP_CDH_SIG) return NULL;
// byteswap and copy some important fields
zipent.bits = LE_TO_HOST16(cdh.bits);
zipent.comptype = LE_TO_HOST16(cdh.comptype);
zipent.crc = LE_TO_HOST32(cdh.crc);
zipent.comp_size = LE_TO_HOST32(cdh.comp_size);
zipent.uncomp_size = LE_TO_HOST32(cdh.uncomp_size);
zipent.ofs = LE_TO_HOST32(cdh.lfh_ofs);
zipent.attr_int = LE_TO_HOST16(cdh.attr_int);
zipent.attr_ext = LE_TO_HOST32(cdh.attr_ext);
cdh.fname_len = LE_TO_HOST16(cdh.fname_len);
cdh.comment_len = LE_TO_HOST16(cdh.comment_len);
cdh.extra_len = LE_TO_HOST16(cdh.extra_len);
// read the name
char *name = calloc(1, cdh.fname_len + 1);
if (!name) return NULL;
if (!fread(name, cdh.fname_len, 1, f)) { free(name); return NULL; }
// this is a directory if the name ends in a path separator
bool is_dir = false;
if (name[cdh.fname_len - 1] == '/') {
is_dir = true;
name[cdh.fname_len - 1] = 0;
}
name[cdh.fname_len] = 0;
// add to directory tree
zip_entry_t *retent = (zip_entry_t *)fs_dirtree_add(tree, name, is_dir);
free(name);
if (!retent) return NULL;
// copy the data we read into the new entry
zipent.tree = retent->tree;
memcpy(retent, &zipent, sizeof(zipent));
// this points to the LFH now; will be fixed up on file open
// while the CDH includes an "extra field length" field, it's usually different
retent->ofs += data_ofs;
// skip to the next CDH
fseek(f, cdh.extra_len + cdh.comment_len, SEEK_CUR);
return retent;
}
static inline bool zip_load_entries(FILE *f, fs_dirtree_t *tree, const uint64_t cdir_ofs, const uint64_t data_ofs, const uint64_t count) {
fseek(f, cdir_ofs, SEEK_SET);
for (uint64_t i = 0; i < count; ++i) {
if (!zip_load_entry(f, tree, data_ofs))
return false;
}
return true;
}
static inline bool is_zip(FILE *f) {
uint32_t sig = 0;
if (fread(&sig, sizeof(sig), 1, f)) {
// the first LFH might be at the start of the zip
if (LE_TO_HOST32(sig) == ZIP_LFH_SIG)
return true;
// no signature, might still be a zip because fuck you
// the only way now is to try and find the end of central directory
return zip_find_eocd(f, NULL) >= 0;
}
return false;
}
static void *pack_zip_mount(const char *realpath) {
uint64_t cdir_ofs, data_ofs, count;
zip_pack_t *pack = NULL;
FILE *f = NULL;
f = fopen(realpath, "rb");
if (!f) goto _fail;
if (!is_zip(f)) goto _fail;
pack = calloc(1, sizeof(zip_pack_t));
if (!pack) goto _fail;
if (!zip_parse_eocd(f, &cdir_ofs, &data_ofs, &count))
goto _fail;
if (!fs_dirtree_init(&pack->tree, sizeof(zip_entry_t)))
goto _fail;
if (!zip_load_entries(f, &pack->tree, cdir_ofs, data_ofs, count))
goto _fail;
pack->realpath = sys_strdup(realpath);
pack->zipf = f;
return pack;
_fail:
if (f) fclose(f);
if (pack) free(pack);
return NULL;
}
static void pack_zip_unmount(void *pack) {
zip_pack_t *zip = (zip_pack_t *)pack;
fs_dirtree_free(&zip->tree);
if (zip->realpath) free((void *)zip->realpath);
if (zip->zipf) fclose(zip->zipf);
free(zip);
}
static bool pack_zip_is_file(void *pack, const char *fname) {
zip_entry_t *ent = (zip_entry_t *)fs_dirtree_find((fs_dirtree_t *)pack, fname);
return ent && !ent->tree.is_dir;
}
static bool pack_zip_is_dir(void *pack, const char *fname) {
zip_entry_t *ent = (zip_entry_t *)fs_dirtree_find((fs_dirtree_t *)pack, fname);
return ent && ent->tree.is_dir;
}
static inline void pack_zip_close_zipfile(zip_file_t *zipfile) {
if (zipfile->buffer) {
inflateEnd(&zipfile->zstream);
free(zipfile->buffer);
}
if (zipfile->fstream) fclose(zipfile->fstream);
free(zipfile);
}
static fs_file_t *pack_zip_open(void *pack, const char *vpath) {
fs_file_t *fsfile = NULL;
zip_file_t *zipfile = NULL;
zip_pack_t *zip = (zip_pack_t *)pack;
zip_entry_t *ent = (zip_entry_t *)fs_dirtree_find((fs_dirtree_t *)zip, vpath);
if (!ent || ent->tree.is_dir) goto _fail; // we're expecting a fucking file here
zipfile = calloc(1, sizeof(zip_file_t));
if (!zipfile) goto _fail;
zipfile->entry = ent;
// obtain an additional file descriptor
// fdopen(dup(fileno())) is not very portable and might not create separate state
zipfile->fstream = fopen(zip->realpath, "rb");
if (!zipfile->fstream) goto _fail;
// make ent->ofs point to the actual file data if it doesn't already
if (!ent->ofs_fixed)
if (!zip_fixup_offset(zipfile))
goto _fail; // this shouldn't generally happen but oh well
// if there's compression, assume it's zlib
if (ent->comptype != 0) {
zipfile->buffer = malloc(ZIP_BUFSIZE);
if (!zipfile->buffer)
goto _fail;
if (inflateInit2(&zipfile->zstream, -MAX_WBITS) != Z_OK)
goto _fail;
}
fsfile = malloc(sizeof(fs_file_t));
if (!fsfile) goto _fail;
fsfile->handle = zipfile;
fsfile->parent = NULL;
// point to the start of the file data
fseek(zipfile->fstream, ent->ofs, SEEK_SET);
return fsfile;
_fail:
if (zipfile) pack_zip_close_zipfile(zipfile);
if (fsfile) free(fsfile);
return NULL;
}
static void pack_zip_close(UNUSED void *pack, fs_file_t *file) {
if (!file) return;
zip_file_t *zipfile = (zip_file_t *)file->handle;
if (zipfile) pack_zip_close_zipfile(zipfile);
free(file);
}
static int64_t pack_zip_read(UNUSED void *pack, fs_file_t *file, void *buf, const uint64_t size) {
zip_file_t *zipfile = (zip_file_t *)file->handle;
zip_entry_t *ent = zipfile->entry;
int64_t avail = ent->uncomp_size - zipfile->uncomp_pos;
int64_t max_read = ((int64_t)size > avail) ? avail : (int64_t)size;
int64_t rx = 0;
int err = 0;
if (max_read == 0) return 0;
if (ent->comptype == 0) {
// no compression, just read
rx = fread(buf, 1, size, zipfile->fstream);
} else {
zipfile->zstream.next_out = buf;
zipfile->zstream.avail_out = (unsigned int)max_read;
while (rx < max_read) {
const uint32_t before = (uint32_t)zipfile->zstream.total_out;
// check if we ran out of compressed bytes and read more if we did
if (zipfile->zstream.avail_in == 0) {
int32_t comp_rx = ent->comp_size - zipfile->comp_pos;
if (comp_rx > 0) {
if (comp_rx > ZIP_BUFSIZE) comp_rx = ZIP_BUFSIZE;
comp_rx = fread(zipfile->buffer, 1, comp_rx, zipfile->fstream);
if (comp_rx == 0) break;
zipfile->comp_pos += (uint32_t)comp_rx;
zipfile->zstream.next_in = zipfile->buffer;
zipfile->zstream.avail_in = (unsigned int)comp_rx;
}
}
// inflate
err = inflate(&zipfile->zstream, Z_SYNC_FLUSH);
rx += zipfile->zstream.total_out - before;
if (err != Z_OK) break;
}
}
zipfile->uncomp_pos += rx;
return rx;
}
static bool pack_zip_seek(UNUSED void *pack, fs_file_t *file, const int64_t ofs) {
zip_file_t *zipfile = (zip_file_t *)file->handle;
zip_entry_t *ent = zipfile->entry;
uint8_t buf[512];
if (ofs > (int64_t)ent->uncomp_size) return false;
if (ent->comptype == 0) {
if (fseek(zipfile->fstream, ofs + ent->ofs, SEEK_SET) == 0)
zipfile->uncomp_pos = ofs;
} else {
// if seeking backwards, gotta redecode the stream from the start until that point
// so we make a copy of the zstream and clear it with a new one
if (ofs < zipfile->uncomp_pos) {
z_stream zstream;
memset(&zstream, 0, sizeof(zstream));
if (inflateInit2(&zstream, -MAX_WBITS) != Z_OK)
return false;
// reset the underlying file handle back to the start
if (fseek(zipfile->fstream, ent->ofs, SEEK_SET) != 0)
return false;
// free and replace the old one
inflateEnd(&zipfile->zstream);
memcpy(&zipfile->zstream, &zstream, sizeof(zstream));
zipfile->uncomp_pos = zipfile->comp_pos = 0;
}
// continue decoding the stream until we hit the new offset
while (zipfile->uncomp_pos != ofs) {
uint32_t max_read = (uint32_t)(ofs - zipfile->uncomp_pos);
if (max_read > sizeof(buf)) max_read = sizeof(buf);
if (pack_zip_read(pack, file, buf, max_read) != max_read)
return false;
}
}
return true;
}
static int64_t pack_zip_tell(UNUSED void *pack, fs_file_t *file) {
return ((zip_file_t *)file->handle)->uncomp_pos;
}
static int64_t pack_zip_size(UNUSED void *pack, fs_file_t *file) {
zip_file_t *zipfile = (zip_file_t *)file->handle;
return zipfile->entry->uncomp_size;
}
static bool pack_zip_eof(UNUSED void *pack, fs_file_t *file) {
zip_file_t *zipfile = (zip_file_t *)file->handle;
return zipfile->uncomp_pos >= zipfile->entry->uncomp_size;
}
fs_packtype_t fs_packtype_zip = {
"zip",
pack_zip_mount,
pack_zip_unmount,
fs_dirtree_walk,
pack_zip_is_file,
pack_zip_is_dir,
pack_zip_open,
pack_zip_read,
pack_zip_seek,
pack_zip_tell,
pack_zip_size,
pack_zip_eof,
pack_zip_close,
};

View file

@ -317,7 +317,7 @@ static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(uint32_t shad
fprintf(stderr, "Vertex shader compilation failed\n"); fprintf(stderr, "Vertex shader compilation failed\n");
glGetShaderInfoLog(vertex_shader, max_length, &max_length, &error_log[0]); glGetShaderInfoLog(vertex_shader, max_length, &max_length, &error_log[0]);
fprintf(stderr, "%s\n", &error_log[0]); fprintf(stderr, "%s\n", &error_log[0]);
abort(); sys_fatal("vertex shader compilation failed (see terminal)");
} }
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
@ -331,7 +331,7 @@ static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(uint32_t shad
fprintf(stderr, "Fragment shader compilation failed\n"); fprintf(stderr, "Fragment shader compilation failed\n");
glGetShaderInfoLog(fragment_shader, max_length, &max_length, &error_log[0]); glGetShaderInfoLog(fragment_shader, max_length, &max_length, &error_log[0]);
fprintf(stderr, "%s\n", &error_log[0]); fprintf(stderr, "%s\n", &error_log[0]);
abort(); sys_fatal("fragment shader compilation failed (see terminal)");
} }
GLuint shader_program = glCreateProgram(); GLuint shader_program = glCreateProgram();

View file

@ -516,17 +516,16 @@ static void gfx_opengl_init(void) {
int vmajor, vminor; int vmajor, vminor;
bool is_es = false; bool is_es = false;
gl_get_version(&vmajor, &vminor, &is_es); gl_get_version(&vmajor, &vminor, &is_es);
if (vmajor < 2 && vminor < 2 && !is_es) { if (vmajor < 2 && vminor < 2 && !is_es)
fprintf(stderr, "OpenGL 1.2+ is required. Reported version: %s%d.%d\n", is_es ? "ES" : "", vmajor, vminor); sys_fatal("OpenGL 1.2+ is required. Reported version: %s%d.%d\n", is_es ? "ES" : "", vmajor, vminor);
abort();
}
// check extensions that we need // check extensions that we need
const bool supported = const bool supported =
gl_check_ext("GL_ARB_multitexture") && gl_check_ext("GL_ARB_multitexture") &&
gl_check_ext("GL_ARB_texture_env_combine"); gl_check_ext("GL_ARB_texture_env_combine");
if (!supported) abort(); if (!supported)
sys_fatal("required GL extensions are not supported");
gl_adv_fog = false; gl_adv_fog = false;

View file

@ -26,6 +26,7 @@
#include "../platform.h" #include "../platform.h"
#include "../configfile.h" #include "../configfile.h"
#include "../fs/fs.h"
#define SUPPORT_CHECK(x) assert(x) #define SUPPORT_CHECK(x) assert(x)
@ -494,6 +495,28 @@ static void import_texture_ci8(int tile) {
#else // EXTERNAL_DATA #else // EXTERNAL_DATA
static inline void load_texture(const char *fullpath) {
int w, h;
u64 imgsize = 0;
u8 *imgdata = fs_load_file(fullpath, &imgsize);
if (!imgdata) {
fprintf(stderr, "could not open texture: `%s`\n", fullpath);
return;
}
// TODO: implement stbi_callbacks or some shit instead of loading the whole texture
u8 *data = stbi_load_from_memory(imgdata, imgsize, &w, &h, NULL, 4);
free(imgdata);
if (!data) {
fprintf(stderr, "could not load texture: `%s`\n", fullpath);
return;
}
gfx_rapi->upload_texture(data, w, h);
stbi_image_free(data); // don't need this anymore
}
// this is taken straight from n64graphics // this is taken straight from n64graphics
static bool texname_to_texformat(const char *name, u8 *fmt, u8 *siz) { static bool texname_to_texformat(const char *name, u8 *fmt, u8 *siz) {
static const struct { static const struct {
@ -531,7 +554,7 @@ static bool texname_to_texformat(const char *name, u8 *fmt, u8 *siz) {
// calls import_texture() on every texture in the res folder // calls import_texture() on every texture in the res folder
// we can get the format and size from the texture files // we can get the format and size from the texture files
// and then cache them using gfx_texture_cache_lookup // and then cache them using gfx_texture_cache_lookup
static bool preload_texture(const char *path) { static bool preload_texture(void *user, const char *path) {
// strip off the extension // strip off the extension
char texname[SYS_MAX_PATH]; char texname[SYS_MAX_PATH];
strncpy(texname, path, sizeof(texname)); strncpy(texname, path, sizeof(texname));
@ -546,55 +569,20 @@ static bool preload_texture(const char *path) {
return true; // just skip it, might be a stray skybox or something return true; // just skip it, might be a stray skybox or something
} }
// strip off the data path char *actualname = texname;
const char *datapath = sys_data_path(); // strip off the prefix // TODO: make a fs_ function for this shit
const unsigned int datalen = strlen(datapath); if (!strncmp(FS_TEXTUREDIR "/", actualname, 4)) actualname += 4;
const char *actualname = (!strncmp(texname, datapath, datalen)) ?
texname + datalen + 1 : texname;
// skip any separators
while (*actualname == '/' || *actualname == '\\') ++actualname;
// this will be stored in the hashtable, so make a copy // this will be stored in the hashtable, so make a copy
actualname = sys_strdup(actualname); actualname = sys_strdup(actualname);
assert(actualname); assert(actualname);
struct TextureHashmapNode *n; struct TextureHashmapNode *n;
if (!gfx_texture_cache_lookup(0, &n, actualname, fmt, siz)) { if (!gfx_texture_cache_lookup(0, &n, actualname, fmt, siz))
// new texture, load it load_texture(path); // new texture, load it
int w, h;
u8 *data = stbi_load(path, &w, &h, NULL, 4);
if (!data) {
fprintf(stderr, "could not load texture: `%s`\n", path);
return false;
}
// upload it
gfx_rapi->upload_texture(data, w, h);
stbi_image_free(data);
}
return true; return true;
} }
static inline void load_texture(const char *name) {
static char fpath[SYS_MAX_PATH];
int w, h;
const char *texname = name;
if (!texname[0]) {
fprintf(stderr, "empty texture name at %p\n", texname);
return;
}
snprintf(fpath, sizeof(fpath), "%s/%s.png", sys_data_path(), texname);
u8 *data = stbi_load(fpath, &w, &h, NULL, 4);
if (!data) {
fprintf(stderr, "could not load texture: `%s`\n", fpath);
return;
}
gfx_rapi->upload_texture(data, w, h);
stbi_image_free(data); // don't need this anymore
}
#endif // EXTERNAL_DATA #endif // EXTERNAL_DATA
static void import_texture(int tile) { static void import_texture(int tile) {
@ -614,7 +602,9 @@ static void import_texture(int tile) {
#ifdef EXTERNAL_DATA #ifdef EXTERNAL_DATA
// the "texture data" is actually a C string with the path to our texture in it // the "texture data" is actually a C string with the path to our texture in it
// load it from an external image in our data path // load it from an external image in our data path
load_texture((const char*)rdp.loaded_texture[tile].addr); char texname[SYS_MAX_PATH];
snprintf(texname, sizeof(texname), FS_TEXTUREDIR "/%s.png", (const char*)rdp.loaded_texture[tile].addr);
load_texture(texname);
#else #else
// the texture data is actual texture data // the texture data is actual texture data
int t0 = get_time(); int t0 = get_time();
@ -625,7 +615,7 @@ static void import_texture(int tile) {
else if (siz == G_IM_SIZ_16b) { else if (siz == G_IM_SIZ_16b) {
import_texture_rgba16(tile); import_texture_rgba16(tile);
} else { } else {
abort(); sys_fatal("unsupported RGBA texture size: %u", siz);
} }
} else if (fmt == G_IM_FMT_IA) { } else if (fmt == G_IM_FMT_IA) {
if (siz == G_IM_SIZ_4b) { if (siz == G_IM_SIZ_4b) {
@ -635,7 +625,7 @@ static void import_texture(int tile) {
} else if (siz == G_IM_SIZ_16b) { } else if (siz == G_IM_SIZ_16b) {
import_texture_ia16(tile); import_texture_ia16(tile);
} else { } else {
abort(); sys_fatal("unsupported IA texture size: %u", siz);
} }
} else if (fmt == G_IM_FMT_CI) { } else if (fmt == G_IM_FMT_CI) {
if (siz == G_IM_SIZ_4b) { if (siz == G_IM_SIZ_4b) {
@ -643,7 +633,7 @@ static void import_texture(int tile) {
} else if (siz == G_IM_SIZ_8b) { } else if (siz == G_IM_SIZ_8b) {
import_texture_ci8(tile); import_texture_ci8(tile);
} else { } else {
abort(); sys_fatal("unsupported CI texture size: %u", siz);
} }
} else if (fmt == G_IM_FMT_I) { } else if (fmt == G_IM_FMT_I) {
if (siz == G_IM_SIZ_4b) { if (siz == G_IM_SIZ_4b) {
@ -651,10 +641,10 @@ static void import_texture(int tile) {
} else if (siz == G_IM_SIZ_8b) { } else if (siz == G_IM_SIZ_8b) {
import_texture_i8(tile); import_texture_i8(tile);
} else { } else {
abort(); sys_fatal("unsupported I texture size: %u", siz);
} }
} else { } else {
abort(); sys_fatal("unsupported texture format: %u", fmt);
} }
int t1 = get_time(); int t1 = get_time();
//printf("Time diff: %d\n", t1 - t0); //printf("Time diff: %d\n", t1 - t0);
@ -1764,8 +1754,8 @@ void gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi) {
#ifdef EXTERNAL_DATA #ifdef EXTERNAL_DATA
// preload all textures if needed // preload all textures if needed
if (configPrecacheRes) { if (configPrecacheRes) {
printf("Precaching textures from `%s`\n", sys_data_path()); printf("Precaching textures\n");
sys_dir_walk(sys_data_path(), preload_texture, true); fs_walk(FS_TEXTUREDIR, preload_texture, NULL, true);
} }
#endif #endif
} }

View file

@ -22,6 +22,7 @@
#include "cliopts.h" #include "cliopts.h"
#include "configfile.h" #include "configfile.h"
#include "controller/controller_api.h" #include "controller/controller_api.h"
#include "fs/fs.h"
#include "game/main.h" #include "game/main.h"
#include "game/thread6.h" #include "game/thread6.h"
@ -153,6 +154,8 @@ void main_func(void) {
main_pool_init(pool, pool + sizeof(pool) / sizeof(pool[0])); main_pool_init(pool, pool + sizeof(pool) / sizeof(pool[0]));
gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT); gEffectsMemoryPool = mem_pool_init(0x4000, MEMORY_POOL_LEFT);
fs_init(sys_ropaths, FS_BASEDIR, sys_user_path());
configfile_load(configfile_name()); configfile_load(configfile_name());
wm_api = &gfx_sdl; wm_api = &gfx_sdl;

View file

@ -1,17 +1,26 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdarg.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <ctype.h> #include <ctype.h>
#ifdef _WIN32
#include <direct.h>
#endif
#include "cliopts.h" #include "cliopts.h"
#include "fs/fs.h"
/* NULL terminated list of platform specific read-only data paths */
/* priority is top first */
const char *sys_ropaths[] = {
".", // working directory
"!", // executable directory
#if defined(__linux__) || defined(__unix__)
// some common UNIX directories for read only stuff
"/usr/local/share/sm64pc",
"/usr/share/sm64pc",
"/opt/sm64pc",
#endif
NULL,
};
/* these are not available on some platforms, so might as well */ /* these are not available on some platforms, so might as well */
@ -40,81 +49,24 @@ int sys_strcasecmp(const char *s1, const char *s2) {
return result; return result;
} }
/* file system stuff */ const char *sys_file_extension(const char *fname) {
const char *dot = strrchr(fname, '.');
bool sys_file_exists(const char *name) { if (!dot || !dot[1]) return NULL;
struct stat st; return dot + 1;
return (stat(name, &st) == 0 && S_ISREG(st.st_mode));
} }
bool sys_dir_exists(const char *name) { /* this calls a platform-specific impl function after forming the error message */
struct stat st;
return (stat(name, &st) == 0 && S_ISDIR(st.st_mode));
}
bool sys_dir_walk(const char *base, walk_fn_t walk, const bool recur) { static void sys_fatal_impl(const char *msg) __attribute__ ((noreturn));
char fullpath[SYS_MAX_PATH];
DIR *dir;
struct dirent *ent;
if (!(dir = opendir(base))) { void sys_fatal(const char *fmt, ...) {
fprintf(stderr, "sys_dir_walk(): could not open `%s`\n", base); static char msg[2048];
return false; va_list args;
} va_start(args, fmt);
vsnprintf(msg, sizeof(msg), fmt, args);
bool ret = true; va_end(args);
fflush(stdout); // push all crap out
while ((ent = readdir(dir)) != NULL) { sys_fatal_impl(msg);
if (ent->d_name[0] == 0 || ent->d_name[0] == '.') continue; // skip ./.. and hidden files
snprintf(fullpath, sizeof(fullpath), "%s/%s", base, ent->d_name);
if (sys_dir_exists(fullpath)) {
if (recur) {
if (!sys_dir_walk(fullpath, walk, recur)) {
ret = false;
break;
}
}
} else {
if (!walk(fullpath)) {
ret = false;
break;
}
}
}
closedir(dir);
return ret;
}
void *sys_load_res(const char *name) {
char path[SYS_MAX_PATH] = { 0 };
snprintf(path, sizeof(path), "%s/%s", sys_data_path(), name);
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
fseek(f, 0, SEEK_SET);
void *buf = malloc(size);
if (!buf) {
fclose(f);
return NULL;
}
fread(buf, 1, size, f);
fclose(f);
return buf;
}
bool sys_mkdir(const char *name) {
#ifdef _WIN32
return _mkdir(name) == 0;
#else
return mkdir(name, 0777) == 0;
#endif
} }
#if USE_SDL #if USE_SDL
@ -122,125 +74,50 @@ bool sys_mkdir(const char *name) {
// we can just ask SDL for most of this shit if we have it // we can just ask SDL for most of this shit if we have it
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
const char *sys_data_path(void) { const char *sys_user_path(void) {
static char path[SYS_MAX_PATH] = { 0 }; static char path[SYS_MAX_PATH] = { 0 };
// get it from SDL
if (!path[0]) { char *sdlpath = SDL_GetPrefPath("", "sm64pc");
// prefer the override, if it is set if (sdlpath) {
// "!" expands to executable path const unsigned int len = strlen(sdlpath);
if (gCLIOpts.DataPath[0]) { strncpy(path, sdlpath, sizeof(path));
if (gCLIOpts.DataPath[0] == '!') path[sizeof(path)-1] = 0;
snprintf(path, sizeof(path), "%s%s", sys_exe_path(), gCLIOpts.DataPath + 1); SDL_free(sdlpath);
else if (path[len-1] == '/' || path[len-1] == '\\')
snprintf(path, sizeof(path), "%s", gCLIOpts.DataPath); path[len-1] = 0; // strip the trailing separator
if (sys_dir_exists(path)) return path; if (!fs_sys_dir_exists(path) && !fs_sys_mkdir(path))
printf("Warning: Specified data path ('%s') doesn't exist\n", path); path[0] = 0;
}
// then the executable directory
snprintf(path, sizeof(path), "%s/" DATADIR, sys_exe_path());
if (sys_dir_exists(path)) return path;
// then the save path
snprintf(path, sizeof(path), "%s/" DATADIR, sys_save_path());
if (sys_dir_exists(path)) return path;
#if defined(__linux__) || defined(__unix__)
// on Linux/BSD try some common paths for read-only data
const char *try[] = {
"/usr/local/share/sm64pc/" DATADIR,
"/usr/share/sm64pc/" DATADIR,
"/opt/sm64pc/" DATADIR,
};
for (unsigned i = 0; i < sizeof(try) / sizeof(try[0]); ++i) {
if (sys_dir_exists(try[i])) {
strcpy(path, try[i]);
return path;
}
}
#endif
// hope for the best
strcpy(path, "./" DATADIR);
} }
return path;
}
const char *sys_save_path(void) {
static char path[SYS_MAX_PATH] = { 0 };
if (!path[0]) {
// if the override is set, use that
// "!" expands to executable path
if (gCLIOpts.SavePath[0]) {
if (gCLIOpts.SavePath[0] == '!')
snprintf(path, sizeof(path), "%s%s", sys_exe_path(), gCLIOpts.SavePath + 1);
else
snprintf(path, sizeof(path), "%s", gCLIOpts.SavePath);
if (!sys_dir_exists(path) && !sys_mkdir(path)) {
printf("Warning: Specified save path ('%s') doesn't exist and can't be created\n", path);
path[0] = 0; // doesn't exist and no write access
}
}
// didn't work? get it from SDL
if (!path[0]) {
char *sdlpath = SDL_GetPrefPath("", "sm64pc");
if (sdlpath) {
const unsigned int len = strlen(sdlpath);
strncpy(path, sdlpath, sizeof(path));
path[sizeof(path)-1] = 0;
SDL_free(sdlpath);
if (path[len-1] == '/' || path[len-1] == '\\')
path[len-1] = 0; // strip the trailing separator
if (!sys_dir_exists(path) && !sys_mkdir(path))
path[0] = 0;
}
}
// if all else fails, just store near the EXE
if (!path[0])
strcpy(path, sys_exe_path());
printf("Save path set to '%s'\n", path);
}
return path; return path;
} }
const char *sys_exe_path(void) { const char *sys_exe_path(void) {
static char path[SYS_MAX_PATH] = { 0 }; static char path[SYS_MAX_PATH] = { 0 };
char *sdlpath = SDL_GetBasePath();
if (!path[0]) { if (sdlpath && sdlpath[0]) {
char *sdlpath = SDL_GetBasePath(); // use the SDL path if it exists
if (sdlpath) { const unsigned int len = strlen(sdlpath);
// use the SDL path if it exists strncpy(path, sdlpath, sizeof(path));
const unsigned int len = strlen(sdlpath); path[sizeof(path)-1] = 0;
strncpy(path, sdlpath, sizeof(path)); SDL_free(sdlpath);
path[sizeof(path)-1] = 0; if (path[len-1] == '/' || path[len-1] == '\\')
SDL_free(sdlpath); path[len-1] = 0; // strip the trailing separator
if (path[len-1] == '/' || path[len-1] == '\\')
path[len-1] = 0; // strip the trailing separator
} else {
// hope for the best
strcpy(path, ".");
}
printf("Executable path set to '%s'\n", path);
} }
return path; return path;
} }
static void sys_fatal_impl(const char *msg) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR , "Fatal error", msg, NULL);
fprintf(stderr, "FATAL ERROR:\n%s\n", msg);
fflush(stderr);
exit(1);
}
#else #else
#warning "You might want to implement these functions for your platform" #warning "You might want to implement these functions for your platform"
const char *sys_data_path(void) { const char *sys_user_path(void) {
return ".";
}
const char *sys_save_path(void) {
return "."; return ".";
} }
@ -248,4 +125,10 @@ const char *sys_exe_path(void) {
return "."; return ".";
} }
static void sys_fatal_impl(const char *msg) {
fprintf(stderr, "FATAL ERROR:\n%s\n", msg);
fflush(stderr);
exit(1);
}
#endif // platform switch #endif // platform switch

View file

@ -7,29 +7,22 @@
/* Platform-specific functions and whatnot */ /* Platform-specific functions and whatnot */
#define DATADIR "res"
#define SYS_MAX_PATH 1024 // FIXME: define this on different platforms #define SYS_MAX_PATH 1024 // FIXME: define this on different platforms
// NULL terminated list of platform specific read-only data paths
extern const char *sys_ropaths[];
// crossplatform impls of misc stuff // crossplatform impls of misc stuff
char *sys_strdup(const char *src); char *sys_strdup(const char *src);
char *sys_strlwr(char *src); char *sys_strlwr(char *src);
int sys_strcasecmp(const char *s1, const char *s2); int sys_strcasecmp(const char *s1, const char *s2);
// filesystem stuff
bool sys_mkdir(const char *name); // creates with 0777 by default
bool sys_file_exists(const char *name);
bool sys_dir_exists(const char *name);
void *sys_load_res(const char *name);
// receives the full path
// should return `true` if traversal should continue
typedef bool (*walk_fn_t)(const char *);
// returns `true` if the directory was successfully opened and walk() didn't ever return false
bool sys_dir_walk(const char *base, walk_fn_t walk, const bool recur);
// path stuff // path stuff
const char *sys_data_path(void); const char *sys_user_path(void);
const char *sys_save_path(void);
const char *sys_exe_path(void); const char *sys_exe_path(void);
const char *sys_file_extension(const char *fname);
// shows an error message in some way and terminates the game
void sys_fatal(const char *fmt, ...) __attribute__ ((noreturn));
#endif // _SM64_PLATFORM_H_ #endif // _SM64_PLATFORM_H_

View file

@ -3,6 +3,9 @@
#include "lib/src/libultra_internal.h" #include "lib/src/libultra_internal.h"
#include "macros.h" #include "macros.h"
#include "platform.h" #include "platform.h"
#include "fs/fs.h"
#define SAVE_FILENAME "sm64_save_file.bin"
#ifdef TARGET_WEB #ifdef TARGET_WEB
#include <emscripten.h> #include <emscripten.h>
@ -120,17 +123,15 @@ s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes)
ret = 0; ret = 0;
} }
#else #else
char save_path[SYS_MAX_PATH] = { 0 }; fs_file_t *fp = fs_open(SAVE_FILENAME);
snprintf(save_path, sizeof(save_path), "%s/sm64_save_file.bin", sys_save_path());
FILE *fp = fopen(save_path, "rb");
if (fp == NULL) { if (fp == NULL) {
return -1; return -1;
} }
if (fread(content, 1, 512, fp) == 512) { if (fs_read(fp, content, 512) == 512) {
memcpy(buffer, content + address * 8, nbytes); memcpy(buffer, content + address * 8, nbytes);
ret = 0; ret = 0;
} }
fclose(fp); fs_close(fp);
#endif #endif
return ret; return ret;
} }
@ -152,9 +153,7 @@ s32 osEepromLongWrite(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes
}, content); }, content);
s32 ret = 0; s32 ret = 0;
#else #else
char save_path[SYS_MAX_PATH] = { 0 }; FILE *fp = fopen(fs_get_write_path(SAVE_FILENAME), "wb");
snprintf(save_path, sizeof(save_path), "%s/sm64_save_file.bin", sys_save_path());
FILE *fp = fopen(save_path, "wb");
if (fp == NULL) { if (fp == NULL) {
return -1; return -1;
} }

22
tools/mkzip.py Normal file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python3
import sys
import os
import zipfile
if len(sys.argv) < 3:
print('usage: mkzip <lstfile> <zipfile>')
sys.exit(1)
lst = []
with open(sys.argv[1], 'r') as f:
for line in f:
line = line.strip()
if line == '' or line[0] == '#':
continue
tok = line.split()
lst.append((tok[0], tok[1]))
with zipfile.ZipFile(sys.argv[2], 'w', allowZip64=False) as zipf:
for (fname, aname) in lst:
zipf.write(fname, arcname=aname)