commit 206a5b7cadf0763665bc9cd02d088541fde6687b
Author: veclavtalica
Date: Mon Jul 8 03:44:20 2024 +0300
awesome!!!
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1779aa5
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+*.png filter=lfs diff=lfs merge=lfs -text
+*.ogg filter=lfs diff=lfs merge=lfs -text
+*.xm filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..86abade
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.vs/
+.vscode/
+.idea/
+.build/
+build/
+out/
+
+# this is created when using Conan
+CMakeUserPresets.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..7cd6d1e
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,148 @@
+cmake_minimum_required(VERSION 3.21)
+
+project(emerald LANGUAGES C)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
+
+# dependencies
+find_package(SDL2 REQUIRED)
+find_package(SDL2_image REQUIRED)
+find_package(SDL2_ttf REQUIRED)
+
+set(PHYSFS_BUILD_SHARED FALSE)
+set(PHYSFS_DISABLE_INSTALL TRUE)
+set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall")
+add_subdirectory(third-party/physfs)
+
+
+set(SOURCE_FILES
+ third-party/physfs/extras/physfsrwops.c
+ src/config.h
+ src/context.h
+ src/context.c
+ src/main.c
+ src/util.c src/util.h
+ src/rendering.c src/rendering.h
+ src/textures.c src/textures.h
+ src/input.c src/input.h
+ src/text.c src/text.h
+ src/game_api.h
+
+ src/game/game.c src/game/game.h
+ src/game/state.h
+
+ src/game/player.c src/game/player.h
+ src/game/world.c src/game/world.h
+
+ src/game/scenes/scene.c src/game/scenes/scene.h
+ src/game/scenes/title.c src/game/scenes/title.h
+ src/game/scenes/ingame.c src/game/scenes/ingame.h
+)
+
+# target
+add_executable(${PROJECT_NAME} ${SOURCE_FILES})
+source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_FILES})
+
+set_target_properties(${PROJECT_NAME} PROPERTIES
+ C_STANDARD 11
+ C_STANDARD_REQUIRED ON
+ C_EXTENSIONS OFF)
+
+# distribution definitions
+set(ORGANIZATION_NAME "wanp" CACHE STRING
+ "App developer/publisher's identifier")
+set(APP_NAME "baron" CACHE STRING
+ "App identifier")
+set(PACKAGE_EXTENSION "btw" CACHE STRING
+ "File extension used to look for data archives")
+
+# build options
+# LTO will be used on release builds
+if(MSVC)
+ # close enough i say
+ target_compile_options(${PROJECT_NAME} PRIVATE
+ /W4
+ $<$:/GL>)
+ target_link_options(${PROJECT_NAME} PRIVATE
+ $<$:/LTCG>)
+else()
+ set(WARNING_FLAGS
+ -Wall
+ -Wextra
+ -pedantic-errors
+ -Wshadow
+ -Wdouble-promotion
+ -Wconversion -Wno-sign-conversion
+ -Werror=vla
+ -Werror=alloca)
+ set(BUILD_FLAGS
+ # these SHOULDN'T break anything...
+ -fno-math-errno
+ -ffp-contract=fast
+ -fno-signed-zeros
+ -fno-trapping-math
+ -freciprocal-math)
+ set(BUILD_FLAGS_RELEASE
+ -flto)
+ set(BUILD_FLAGS_DEBUG
+ -g3
+ -fsanitize-trap=undefined)
+ target_compile_options(${PROJECT_NAME} PRIVATE
+ ${WARNING_FLAGS}
+ ${BUILD_FLAGS}
+ $<$:${BUILD_FLAGS_RELEASE}>
+ $<$:${BUILD_FLAGS_DEBUG}>)
+ target_link_options(${PROJECT_NAME} PRIVATE
+ ${BUILD_FLAGS}
+ $<$:${BUILD_FLAGS_RELEASE}>
+ $<$:${BUILD_FLAGS_DEBUG}>)
+
+endif()
+
+target_compile_definitions(${PROJECT_NAME} PRIVATE
+ ORGANIZATION_NAME="${ORGANIZATION_NAME}"
+ APP_NAME="${APP_NAME}"
+ PACKAGE_EXTENSION="${PACKAGE_EXTENSION}")
+
+target_include_directories(${PROJECT_NAME}
+ PRIVATE
+ third-party/physfs/src
+ third-party/physfs/extras
+)
+
+# header-only libraries should be marked as "system includes"
+# to suppress compiler warnings in their code (it's not my problem after all)
+target_include_directories(${PROJECT_NAME}
+ SYSTEM
+ PRIVATE
+ third-party/stb
+)
+
+# system libraries
+find_library(MATH_LIBRARY m)
+if(MATH_LIBRARY)
+ target_link_libraries(${PROJECT_NAME} PUBLIC ${MATH_LIBRARY})
+endif()
+
+# third-party libraries
+target_link_libraries(${PROJECT_NAME} PUBLIC
+ SDL2::SDL2
+ SDL2::SDL2main
+ SDL2_image::SDL2_image
+ SDL2_ttf::SDL2_ttf
+ physfs-static
+)
+
+# copy dlls for baby windows
+add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy -t $
+ $
+ COMMAND_EXPAND_LISTS
+)
+
+# zip up assets
+# currently, you must run cmake from the project root dir for this to work correctly
+#file(ARCHIVE_CREATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/data.${PACKAGE_EXTENSION}
+# PATHS ${PROJECT_SOURCE_DIR}/assets
+# FORMAT zip
+#)
diff --git a/conanfile.py b/conanfile.py
new file mode 100644
index 0000000..81a2b7b
--- /dev/null
+++ b/conanfile.py
@@ -0,0 +1,23 @@
+from conan import ConanFile
+from conan.tools.cmake import cmake_layout, CMake
+
+
+class Emerald(ConanFile):
+ settings = 'os', 'compiler', 'build_type', 'arch'
+ generators = 'CMakeToolchain', 'CMakeDeps'
+
+ def requirements(self):
+ self.requires('sdl/2.28.3')
+ self.requires('sdl_image/2.6.3')
+ self.requires('sdl_ttf/2.20.2')
+
+ def build_requirements(self):
+ self.tool_requires('cmake/3.22.6')
+
+ def layout(self):
+ cmake_layout(self)
+
+ def build(self):
+ cmake = CMake(self)
+ cmake.configure()
+ cmake.build()
diff --git a/data/assets/player/baron-walk.png b/data/assets/player/baron-walk.png
new file mode 100644
index 0000000..9cc95c7
--- /dev/null
+++ b/data/assets/player/baron-walk.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:833f3c278d2f44fc7d3ee0daf3c5335ba1f30a339c4bff2c9e36837eb2b7e76e
+size 407
diff --git a/data/assets/red.png b/data/assets/red.png
new file mode 100644
index 0000000..8e36186
--- /dev/null
+++ b/data/assets/red.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7f5b7ad6f1983409ebde2b28b8921c09c0b65d06fcddb91e73ab79579684c2c2
+size 301
diff --git a/data/assets/title.png b/data/assets/title.png
new file mode 100644
index 0000000..5105cab
--- /dev/null
+++ b/data/assets/title.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e1f4550ad453f5c6e4fdb138289dabb2ba0cef08a124fa1058c4b7b2c6b8b3b3
+size 3089
diff --git a/data/assets/white.png b/data/assets/white.png
new file mode 100644
index 0000000..1ea9bfd
--- /dev/null
+++ b/data/assets/white.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:78074d5fd845d8f97e7e4a7d9cb98cae16ec6ffbab1b566400ae261f9999f411
+size 298
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..c1d6e95
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,31 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/*
+ * this file is for configuration values which are to be set at
+ * compile time. generally speaking, it's for things that would be unwise to
+ * change mid-development without considering the work it might take to
+ * adapt the game logic.
+ *
+ * if you're looking for distribution-related definitions like
+ * APP_NAME, you should know that they are set from CMake.
+ */
+
+
+#define TICKS_PER_SECOND 60
+#define FIXED_DELTA_TIME (1.0 / TICKS_PER_SECOND)
+
+#define RENDER_BASE_WIDTH 640
+#define RENDER_BASE_HEIGHT 360
+
+#define TEXTURE_ATLAS_SIZE 2048
+#define TEXTURE_ATLAS_BIT_DEPTH 32
+#define TEXTURE_ATLAS_FORMAT SDL_PIXELFORMAT_RGBA32
+
+#define NUM_KEYBIND_SLOTS 8
+
+/* 1024 * 1024 */
+/* #define UMKA_STACK_SIZE 1048576 */
+
+
+#endif
diff --git a/src/context.c b/src/context.c
new file mode 100644
index 0000000..45cd133
--- /dev/null
+++ b/src/context.c
@@ -0,0 +1,3 @@
+#include "context.h"
+
+t_ctx ctx;
diff --git a/src/context.h b/src/context.h
new file mode 100644
index 0000000..55d4d8f
--- /dev/null
+++ b/src/context.h
@@ -0,0 +1,68 @@
+#ifndef CONTEXT_H
+#define CONTEXT_H
+
+
+#include "config.h"
+#include "private/rendering.h"
+#include "textures.h"
+#include "input.h"
+#include "game_api.h"
+
+#include
+
+#include
+#include
+
+
+typedef struct context {
+ /* the program's actual argc and argv */
+ int argc;
+ char **argv;
+
+ struct texture_cache texture_cache;
+ struct input_state input;
+
+ struct sprite_primitive *render_queue_sprites;
+ struct rect_primitive *render_queue_rectangles;
+ struct circle_primitive *render_queue_circles;
+
+ struct audio_channel *audio_channels;
+
+ struct circle_radius_cache_item *circle_radius_hash;
+
+ /* main loop machinery */
+ int64_t clocks_per_second;
+ int64_t prev_frame_time;
+ int64_t desired_frametime; /* how long one tick should be */
+ int64_t frame_accumulator;
+ int64_t delta_averager_residual;
+ int64_t time_averager[4];
+ uint64_t tick_count;
+ uint64_t step_count;
+
+ /* set just once on startup */
+ uint64_t random_seed;
+
+ /* this should be a multiple of TICKS_PER_SECOND */
+ /* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
+ /* it can be changed at runtime; any resulting logic anomalies are bugs */
+ unsigned int update_multiplicity;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ uint32_t window_id;
+ int window_w;
+ int window_h;
+
+ bool was_successful;
+
+ /* you may read from and write to these from game code */
+ void *udata;
+ bool debug;
+ bool is_running;
+ bool resync_flag;
+} t_ctx;
+
+extern t_ctx ctx;
+
+#endif
diff --git a/src/game/game.c b/src/game/game.c
new file mode 100644
index 0000000..1808774
--- /dev/null
+++ b/src/game/game.c
@@ -0,0 +1,70 @@
+#include "game.h"
+#include "../textures.h"
+#include "../game_api.h"
+#include "state.h"
+#include "scenes/scene.h"
+#include "scenes/title.h"
+
+#include
+#include
+#include
+
+
+void game_step(int64_t delta) {
+ (void)delta;
+}
+
+
+void game_tick(void) {
+ if (ctx.tick_count == 0) {
+ ctx.udata = ccalloc(1, sizeof (struct state));
+ struct state *state = ctx.udata;
+ state->ctx = &ctx;
+ state->scene = title_scene(state);
+
+ input_add_action(&ctx.input, "debug_dump_atlases");
+ input_bind_action_scancode(&ctx.input, "debug_dump_atlases", SDL_SCANCODE_HOME);
+
+ input_add_action(&ctx.input, "debug_toggle");
+ input_bind_action_scancode(&ctx.input, "debug_toggle", SDL_SCANCODE_BACKSPACE);
+
+ input_add_action(&ctx.input, "player_left");
+ input_bind_action_scancode(&ctx.input, "player_left", SDL_SCANCODE_LEFT);
+
+ input_add_action(&ctx.input, "player_right");
+ input_bind_action_scancode(&ctx.input, "player_right", SDL_SCANCODE_RIGHT);
+
+ input_add_action(&ctx.input, "player_jump");
+ input_bind_action_scancode(&ctx.input, "player_jump", SDL_SCANCODE_X);
+
+ input_add_action(&ctx.input, "ui_accept");
+ input_bind_action_scancode(&ctx.input, "ui_accept", SDL_SCANCODE_RETURN);
+ }
+
+ struct state *state = ctx.udata;
+
+ if (input_is_action_just_pressed(&ctx.input, "debug_dump_atlases")) {
+ textures_dump_atlases(&ctx.texture_cache);
+ }
+
+ if (input_is_action_just_pressed(&ctx.input, "debug_toggle")) {
+ ctx.debug = !ctx.debug;
+ }
+
+ state->scene->tick(state);
+
+ /* there's a scene switch pending, we can do it now that the tick is done */
+ if (state->next_scene != NULL) {
+ state->scene->end(state);
+ state->scene = state->next_scene;
+ state->is_scene_switching = false;
+ state->next_scene = NULL;
+ }
+}
+
+
+void game_end(void) {
+ struct state *state = ctx.udata;
+ state->scene->end(state);
+ free(state);
+}
diff --git a/src/game/game.h b/src/game/game.h
new file mode 100644
index 0000000..e89834c
--- /dev/null
+++ b/src/game/game.h
@@ -0,0 +1,15 @@
+#ifndef GAME_H
+#define GAME_H
+
+
+#include "../game_api.h"
+
+#include
+
+
+void game_step(int64_t delta);
+void game_tick(void);
+void game_end(void);
+
+
+#endif
diff --git a/src/game/player.c b/src/game/player.c
new file mode 100644
index 0000000..22d0ac4
--- /dev/null
+++ b/src/game/player.c
@@ -0,0 +1,303 @@
+#include "player.h"
+#include "world.h"
+#include "../game_api.h"
+
+#include
+
+#include
+#include
+#include
+
+
+static void update_timers(struct player *player) {
+ tick_timer(&player->jump_air_timer);
+ tick_timer(&player->jump_coyote_timer);
+ tick_timer(&player->jump_buffer_timer);
+}
+
+
+static void input_move(struct input_state *input, struct player *player) {
+ /* apply horizontal damping when the player stops moving */
+ /* in other words, make it decelerate to a standstill */
+ if (!input_is_action_pressed(input, "player_left") &&
+ !input_is_action_pressed(input, "player_right"))
+ {
+ player->dx *= player->horizontal_damping;
+ }
+
+ int input_dir = 0;
+ if (input_is_action_pressed(input, "player_left"))
+ input_dir = -1;
+ if (input_is_action_pressed(input, "player_right"))
+ input_dir = 1;
+ if (input_is_action_pressed(input, "player_left") &&
+ input_is_action_pressed(input, "player_right"))
+ input_dir = 0;
+
+ player->dx += (float)input_dir * player->run_horizontal_speed;
+ player->dx = SDL_clamp(player->dx, -player->max_dx, player->max_dx);
+
+ if (fabs(player->dx) < player->run_horizontal_speed) {
+ player->dx = 0;
+ }
+}
+
+
+static void jump(struct player *player) {
+ player->jump_coyote_timer = 0;
+ player->jump_buffer_timer = 0;
+ player->dy = player->jump_force_initial;
+ player->action = PLAYER_ACTION_JUMP;
+ player->jump_air_timer = player->jump_air_ticks;
+}
+
+
+static void input_jump(struct input_state *input, struct player *player) {
+ player->current_gravity_multiplier = player->jump_default_multiplier;
+
+ if (input_is_action_just_pressed(input, "player_jump")) {
+ player->jump_air_timer = 0;
+ player->jump_buffer_timer = player->jump_buffer_ticks;
+
+ if (player->action == PLAYER_ACTION_GROUND || player->jump_coyote_timer > 0) {
+ jump(player);
+ }
+ }
+
+ if (input_is_action_pressed(input, "player_jump")) {
+ if (player->action != PLAYER_ACTION_GROUND && player->jump_air_timer > 0) {
+ player->current_gravity_multiplier = player->jump_boosted_multiplier;
+ player->dy += player->jump_force_increase;
+ }
+ }
+}
+
+
+static void update_collider_x(struct player *player) {
+ player->collider_x.w = player->rect.w;
+ player->collider_x.h = player->rect.h - 8;
+ player->collider_x.x = player->rect.x;
+ player->collider_x.y = player->rect.y + ((player->rect.h - player->collider_x.h) / 2);
+}
+
+
+static void update_collider_y(struct player *player) {
+ player->collider_y.w = player->rect.w;
+ player->collider_y.h = player->rect.h;
+ player->collider_y.x = player->rect.x + ((player->rect.w - player->collider_y.w) / 2);
+ player->collider_y.y = player->rect.y;
+}
+
+
+static void apply_gravity(struct player *player, float gravity) {
+ player->dy -= gravity * player->current_gravity_multiplier;
+ player->dy = fmax(player->dy, -player->terminal_velocity);
+
+ if (player->dy < 0) {
+ /* just started falling */
+ if (player->action == PLAYER_ACTION_GROUND) {
+ player->jump_coyote_timer = player->jump_coyote_ticks;
+ }
+
+ player->action = PLAYER_ACTION_FALL;
+ }
+}
+
+
+/* returns whether or not a correction was applied */
+static bool corner_correct(struct player *player, t_frect collision) {
+ /*
+ * somewhat of a hack here. we only want to do corner correction
+ * if the corner in question really is the corner of a "platform,"
+ * and not simply the edge of a single tile in a row of many
+ * tiles, as that would briefly clip the player into the ceiling,
+ * halting movement. the dumbest way i could think of to fix this
+ * is to simply ensure that there's no tile to possibly clip into
+ * before correcting, by checking if there's a tile right above
+ * the center of the player
+ */
+ if (world_is_tile_at(player->world,
+ player->rect.x + (player->rect.w / 2),
+ player->rect.y - 1))
+ {
+ return false;
+ }
+
+ float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
+ float collision_center_x = collision.x + (collision.w / 2);
+ float abs_difference = fabs(player_center_x - collision_center_x);
+
+ /* we're good, no correction needed */
+ if (abs_difference < player->jump_corner_correction_offset)
+ return false;
+
+ /* collision was on player's right side */
+ if (player_center_x < collision_center_x) {
+ player->rect.x -= collision.w / 2;
+ }
+ /* collision was on player's left side */
+ else if (player_center_x > collision_center_x) {
+ player->rect.x += collision.w / 2;
+ }
+
+ return true;
+}
+
+
+static void calc_collisions_x(struct player *player) {
+ t_frect collision;
+ bool is_colliding = world_find_intersect_frect(player->world, player->collider_x, &collision);
+ if (!is_colliding) return;
+
+ float player_center_x = player->collider_x.x + (player->collider_x.w / 2);
+ float collision_center_x = collision.x + (collision.w / 2);
+
+ enum collision_direction { COLLISION_LEFT = -1, COLLISION_RIGHT = 1 };
+ enum collision_direction dir_x =
+ player_center_x > collision_center_x ? COLLISION_LEFT : COLLISION_RIGHT;
+
+ player->rect.x -= collision.w * (float)dir_x;
+ player->dx = 0;
+}
+
+
+static void calc_collisions_y(struct player *player) {
+ t_frect collision;
+ bool is_colliding = world_find_intersect_frect(player->world, player->collider_y, &collision);
+ if (!is_colliding) return;
+
+ float player_center_y = player->collider_y.y + (player->collider_y.h / 2);
+ float collision_center_y = collision.y + (collision.h / 2);
+
+ enum collision_direction { COLLISION_ABOVE = -1, COLLISION_BELOW = 1 };
+ enum collision_direction dir_y =
+ player_center_y > collision_center_y ? COLLISION_ABOVE : COLLISION_BELOW;
+
+ /* before the resolution */
+ if (dir_y == COLLISION_ABOVE) {
+ if (corner_correct(player, collision)) {
+ return;
+ }
+ }
+
+ /* resolution */
+ player->rect.y -= collision.h * (float)dir_y;
+ player->dy = 0;
+
+ /* after the resolution */
+ if (dir_y == COLLISION_BELOW) {
+ /* bandaid fix for float precision-related jittering */
+ player->rect.y = ceilf(player->rect.y);
+
+ player->action = PLAYER_ACTION_GROUND;
+
+ if (player->jump_buffer_timer > 0) {
+ jump(player);
+ apply_gravity(player, player->world->gravity);
+ }
+ } else if (dir_y == COLLISION_ABOVE) {
+ player->jump_air_timer = 0;
+ }
+}
+
+
+struct player *player_create(struct world *world) {
+ struct player *player = cmalloc(sizeof *player);
+
+ *player = (struct player) {
+ .world = world,
+
+ .sprite_w = 48,
+ .sprite_h = 48,
+
+ .rect = (t_frect) {
+ .x = 92,
+ .y = 200,
+ .w = 16,
+ .h = 32,
+ },
+
+ .action = PLAYER_ACTION_GROUND,
+
+ .collider_thickness = 42,
+ .max_dx = 8,
+ .run_horizontal_speed = 0.5f,
+ .horizontal_damping = 0.9f,
+ .current_gravity_multiplier = 1,
+ .terminal_velocity = 30,
+
+ .jump_force_initial = 10,
+ .jump_force_increase = 1,
+ .jump_air_ticks = 18,
+ .jump_coyote_ticks = 8,
+ .jump_buffer_ticks = 8,
+
+ .jump_default_multiplier = 1.4f,
+ .jump_boosted_multiplier = 1,
+
+ .jump_corner_correction_offset = 16.0f,
+ };
+
+ return player;
+}
+
+
+static void drawdef(struct player *player) {
+ push_sprite("/assets/player/baron-walk.png",
+ (t_frect) {
+ .x = player->rect.x + ((player->rect.w - player->sprite_w) / 2),
+ .y = player->rect.y - 8,
+ .w = player->sprite_w,
+ .h = player->sprite_h,
+ });
+
+ push_circle((t_fvec2) { 256, 128 },
+ 24,
+ (t_color) { 255, 0, 0, 255 });
+}
+
+
+static void drawdef_debug(struct player *player) {
+ if (!ctx.debug)
+ return;
+
+ /* const int info_separation = 24; */
+ /* const struct RectPrimitive info_theme = { */
+ /* .x = 8, */
+ /* .r = 0, .g = 0, .b = 0, .a = 255, */
+ /* }; */
+
+ push_rectangle(player->collider_x,
+ (t_color){ 0, 0, 255, 128 });
+
+ push_rectangle(player->collider_y,
+ (t_color){ 0, 0, 255, 128 });
+}
+
+
+void player_destroy(struct player *player) {
+ free(player);
+}
+
+
+void player_calc(struct player *player) {
+ update_timers(player);
+
+ input_move(&ctx.input, player);
+ input_jump(&ctx.input, player);
+
+ player->rect.x += player->dx;
+ update_collider_x(player);
+ calc_collisions_x(player);
+
+ apply_gravity(player, player->world->gravity);
+ player->rect.y -= player->dy;
+ update_collider_y(player);
+ calc_collisions_y(player);
+
+ update_collider_x(player);
+ update_collider_y(player);
+
+ drawdef(player);
+ drawdef_debug(player);
+}
diff --git a/src/game/player.h b/src/game/player.h
new file mode 100644
index 0000000..6de0601
--- /dev/null
+++ b/src/game/player.h
@@ -0,0 +1,66 @@
+#ifndef PLAYER_H
+#define PLAYER_H
+
+
+#include "../game_api.h"
+
+
+struct world;
+
+
+enum player_action {
+ PLAYER_ACTION_GROUND,
+ PLAYER_ACTION_FALL,
+ PLAYER_ACTION_JUMP,
+};
+
+
+struct player {
+ struct world *world;
+
+ /* visual */
+ float sprite_w;
+ float sprite_h;
+
+ /* body */
+ t_frect rect;
+
+ /* state */
+ enum player_action action;
+
+ /* physics */
+ t_frect collider_x;
+ t_frect collider_y;
+ int collider_thickness;
+ float dx;
+ float dy;
+ float max_dx;
+ float run_horizontal_speed;
+ float horizontal_damping;
+ float current_gravity_multiplier;
+ float terminal_velocity;
+
+ /* jumping */
+ float jump_force_initial;
+ float jump_force_increase; /* aka "jump power", increment of dy per tick */
+ int jump_air_ticks;
+ int jump_air_timer;
+ int jump_coyote_ticks;
+ int jump_coyote_timer;
+ int jump_buffer_ticks;
+ int jump_buffer_timer;
+
+ /* gravity multipliers */
+ float jump_default_multiplier;
+ float jump_boosted_multiplier;
+
+ float jump_corner_correction_offset; /* from center */
+};
+
+
+struct player *player_create(struct world *world);
+void player_destroy(struct player *player);
+void player_calc(struct player *player);
+
+
+#endif
diff --git a/src/game/scenes/ingame.c b/src/game/scenes/ingame.c
new file mode 100644
index 0000000..24b9496
--- /dev/null
+++ b/src/game/scenes/ingame.c
@@ -0,0 +1,32 @@
+#include "ingame.h"
+#include "title.h"
+#include "scene.h"
+
+
+static void ingame_tick(struct state *state) {
+ struct scene_ingame *scn = (struct scene_ingame *)state->scene;
+
+ world_drawdef(scn->world);
+ player_calc(scn->player);
+}
+
+
+static void ingame_end(struct state *state) {
+ struct scene_ingame *scn = (struct scene_ingame *)state->scene;
+ player_destroy(scn->player);
+ world_destroy(scn->world);
+}
+
+
+struct scene *ingame_scene(struct state *state) {
+ (void)state;
+
+ struct scene_ingame *new_scene = ccalloc(1, sizeof *new_scene);
+ new_scene->base.tick = ingame_tick;
+ new_scene->base.end = ingame_end;
+
+ new_scene->world = world_create();
+ new_scene->player = player_create(new_scene->world);
+
+ return (struct scene *)new_scene;
+}
diff --git a/src/game/scenes/ingame.h b/src/game/scenes/ingame.h
new file mode 100644
index 0000000..bf24e37
--- /dev/null
+++ b/src/game/scenes/ingame.h
@@ -0,0 +1,23 @@
+#ifndef INGAME_H
+#define INGAME_H
+
+
+#include "../../game_api.h"
+#include "../state.h"
+#include "scene.h"
+#include "../player.h"
+#include "../world.h"
+
+
+struct scene_ingame {
+ struct scene base;
+
+ struct world *world;
+ struct player *player;
+};
+
+
+struct scene *ingame_scene(struct state *state);
+
+
+#endif
diff --git a/src/game/scenes/scene.c b/src/game/scenes/scene.c
new file mode 100644
index 0000000..4dd8a82
--- /dev/null
+++ b/src/game/scenes/scene.c
@@ -0,0 +1,8 @@
+#include "scene.h"
+#include "../state.h"
+
+
+void switch_to(struct state *state, struct scene *(*scene_func)(struct state *)) {
+ state->next_scene = scene_func(state);
+ state->is_scene_switching = true;
+}
diff --git a/src/game/scenes/scene.h b/src/game/scenes/scene.h
new file mode 100644
index 0000000..26b712d
--- /dev/null
+++ b/src/game/scenes/scene.h
@@ -0,0 +1,16 @@
+#ifndef SCENE_H
+#define SCENE_H
+
+
+struct state;
+struct scene {
+ char *id;
+ void (*tick)(struct state *);
+ void (*end)(struct state *);
+};
+
+
+void switch_to(struct state *state, struct scene *(*scene_func)(struct state *));
+
+
+#endif
diff --git a/src/game/scenes/title.c b/src/game/scenes/title.c
new file mode 100644
index 0000000..ed9b32a
--- /dev/null
+++ b/src/game/scenes/title.c
@@ -0,0 +1,41 @@
+#include "title.h"
+#include "ingame.h"
+#include "../world.h"
+#include "../player.h"
+#include "../../game_api.h"
+
+
+static void title_tick(struct state *state) {
+ struct scene_title *scn = (struct scene_title *)state->scene;
+ (void)scn;
+
+ if (input_is_action_just_pressed(&state->ctx->input, "ui_accept")) {
+ switch_to(state, ingame_scene);
+ }
+
+
+ push_sprite("/assets/title.png", (t_frect) {
+ (RENDER_BASE_WIDTH / 2) - (320 / 2), 64, 320, 128
+ });
+}
+
+
+static void title_end(struct state *state) {
+ struct scene_title *scn = (struct scene_title *)state->scene;
+ player_destroy(scn->player);
+ world_destroy(scn->world);
+}
+
+
+struct scene *title_scene(struct state *state) {
+ (void)state;
+
+ struct scene_title *new_scene = ccalloc(1, sizeof *new_scene);
+ new_scene->base.tick = title_tick;
+ new_scene->base.end = title_end;
+
+ new_scene->world = world_create();
+ new_scene->player = player_create(new_scene->world);
+
+ return (struct scene *)new_scene;
+}
diff --git a/src/game/scenes/title.h b/src/game/scenes/title.h
new file mode 100644
index 0000000..18fd876
--- /dev/null
+++ b/src/game/scenes/title.h
@@ -0,0 +1,23 @@
+#ifndef TITLE_H
+#define TITLE_H
+
+
+#include "../../game_api.h"
+#include "../state.h"
+#include "scene.h"
+#include "../player.h"
+#include "../world.h"
+
+
+struct scene_title {
+ struct scene base;
+
+ struct world *world;
+ struct player *player;
+};
+
+
+struct scene *title_scene(struct state *state);
+
+
+#endif
diff --git a/src/game/state.h b/src/game/state.h
new file mode 100644
index 0000000..865ac90
--- /dev/null
+++ b/src/game/state.h
@@ -0,0 +1,16 @@
+#ifndef STATE_H
+#define STATE_H
+
+
+#include "../game_api.h"
+
+
+struct state {
+ t_ctx *ctx;
+ struct scene *scene;
+ struct scene *next_scene;
+ bool is_scene_switching;
+};
+
+
+#endif
diff --git a/src/game/world.c b/src/game/world.c
new file mode 100644
index 0000000..c1c9752
--- /dev/null
+++ b/src/game/world.c
@@ -0,0 +1,187 @@
+#include "world.h"
+#include "../game_api.h"
+
+#include
+#include
+#include
+#include
+
+
+static void update_tiles(struct world *world) {
+ for (size_t row = 0; row < world->tilemap_height; ++row) {
+ for (size_t col = 0; col < world->tilemap_width; ++col) {
+ world->tiles[(row * world->tilemap_width) + col] = (struct tile) {
+ .rect = (t_rect) {
+ .x = (int)col * world->tile_size,
+ .y = (int)row * world->tile_size,
+ .w = world->tile_size,
+ .h = world->tile_size,
+ },
+ .type = world->tilemap[row][col],
+ };
+ }
+ }
+}
+
+
+static t_vec2 to_grid_location(struct world *world, float x, float y) {
+ return (t_vec2) {
+ .x = (int)floor(x / (float)world->tile_size),
+ .y = (int)floor(y / (float)world->tile_size),
+ };
+}
+
+
+static void drawdef_debug(struct world *world) {
+ if (!ctx.debug) return;
+
+ for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
+ if (world->tiles[i].type == TILE_TYPE_VOID) continue;
+
+ push_rectangle(to_frect(world->tiles[i].rect),
+ (t_color) { 255, 0, 255, 128 });
+ }
+}
+
+
+struct world *world_create(void) {
+ struct world *world = cmalloc(sizeof *world);
+
+ *world = (struct world) {
+ .tiles = NULL,
+ .tile_size = 42,
+ .tilemap_width = 20,
+ .tilemap_height = 12,
+ .gravity = 1,
+ };
+
+ /* create the tilemap */
+ /* it simply stores what's in each tile as a 2d array */
+ /* on its own, it's entirely unrelated to drawing or logic */
+ world->tilemap = cmalloc(sizeof *world->tilemap * world->tilemap_height);
+ for (size_t i = 0; i < world->tilemap_height; ++i) {
+ world->tilemap[i] = cmalloc(sizeof **world->tilemap * world->tilemap_width);
+
+ for (size_t j = 0; j < world->tilemap_width; ++j) {
+ world->tilemap[i][j] = TILE_TYPE_VOID;
+ }
+ }
+
+ for (size_t i = 0; i < 12; ++i) {
+ world->tilemap[world->tilemap_height-2][2+i] = TILE_TYPE_SOLID;
+ }
+ world->tilemap[world->tilemap_height-3][8] = TILE_TYPE_SOLID;
+ world->tilemap[world->tilemap_height-4][8] = TILE_TYPE_SOLID;
+ world->tilemap[world->tilemap_height-5][10] = TILE_TYPE_SOLID;
+ world->tilemap[world->tilemap_height-6][10] = TILE_TYPE_SOLID;
+ for (size_t i = 0; i < 7; ++i) {
+ world->tilemap[world->tilemap_height-6][12+i] = TILE_TYPE_SOLID;
+ }
+
+ /* the tiles array contains data meant to be used by other logic */
+ /* most importantly, it is used to draw the tiles */
+ const size_t tile_count = world->tilemap_height * world->tilemap_width;
+ world->tiles = cmalloc(sizeof *world->tiles * tile_count);
+ update_tiles(world);
+
+ return world;
+}
+
+
+void world_destroy(struct world *world) {
+ free(world->tiles);
+
+ for (size_t i = 0; i < world->tilemap_height; ++i) {
+ free(world->tilemap[i]);
+ }
+ free(world->tilemap);
+
+ free(world);
+}
+
+
+void world_drawdef(struct world *world) {
+ for (size_t i = 0; i < world->tilemap_height * world->tilemap_width; ++i) {
+ if (world->tiles[i].type == TILE_TYPE_VOID)
+ continue;
+
+ push_sprite("/assets/white.png", to_frect(world->tiles[i].rect));
+ }
+
+ drawdef_debug(world);
+}
+
+
+bool world_find_intersect_frect(struct world *world, t_frect rect, t_frect *intersection) {
+ bool is_intersecting = false;
+
+ const size_t tile_count = world->tilemap_height * world->tilemap_width;
+ for (size_t i = 0; i < tile_count; ++i) {
+ if (world->tiles[i].type == TILE_TYPE_VOID)
+ continue;
+
+ t_frect tile_frect = {
+ .x = (float)(world->tiles[i].rect.x),
+ .y = (float)(world->tiles[i].rect.y),
+ .w = (float)(world->tiles[i].rect.w),
+ .h = (float)(world->tiles[i].rect.h),
+ };
+
+ if (intersection == NULL) {
+ t_frect temp;
+ is_intersecting = intersect_frect(&rect, &tile_frect, &temp);
+ } else {
+ is_intersecting = intersect_frect(&rect, &tile_frect, intersection);
+ }
+
+ if (is_intersecting)
+ break;
+ }
+
+ return is_intersecting;
+}
+
+
+bool world_find_intersect_rect(struct world *world, t_rect rect, t_rect *intersection) {
+ bool is_intersecting = false;
+
+ const size_t tile_count = world->tilemap_height * world->tilemap_width;
+ for (size_t i = 0; i < tile_count; ++i) {
+ if (world->tiles[i].type == TILE_TYPE_VOID)
+ continue;
+
+ t_rect *tile_rect = &world->tiles[i].rect;
+
+ if (intersection == NULL) {
+ t_rect temp;
+ is_intersecting = intersect_rect(&rect, tile_rect, &temp);
+ } else {
+ is_intersecting = intersect_rect(&rect, tile_rect, intersection);
+ }
+
+ if (is_intersecting)
+ break;
+ }
+
+ return is_intersecting;
+}
+
+
+bool world_is_tile_at(struct world *world, float x, float y) {
+ t_vec2 position_in_grid = to_grid_location(world, x, y);
+ return world->tilemap[position_in_grid.y][position_in_grid.x] != TILE_TYPE_VOID;
+}
+
+
+void world_place_tile(struct world *world, float x, float y) {
+ t_vec2 position_in_grid = to_grid_location(world, x, y);
+ world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_SOLID;
+ update_tiles(world);
+}
+
+
+void world_remove_tile(struct world *world, float x, float y) {
+ t_vec2 position_in_grid = to_grid_location(world, x, y);
+ world->tilemap[position_in_grid.y][position_in_grid.x] = TILE_TYPE_VOID;
+ update_tiles(world);
+}
diff --git a/src/game/world.h b/src/game/world.h
new file mode 100644
index 0000000..9abd2f0
--- /dev/null
+++ b/src/game/world.h
@@ -0,0 +1,44 @@
+#ifndef WORLD_H
+#define WORLD_H
+
+#include "../game_api.h"
+
+#include
+#include
+
+
+enum tile_type {
+ TILE_TYPE_VOID,
+ TILE_TYPE_SOLID,
+};
+
+
+struct tile {
+ t_rect rect;
+ enum tile_type type;
+};
+
+
+struct world {
+ enum tile_type **tilemap;
+ struct tile *tiles;
+
+ int tile_size;
+ unsigned int tilemap_width;
+ unsigned int tilemap_height;
+ size_t tile_nonvoid_count;
+ float gravity;
+};
+
+
+struct world *world_create(void);
+void world_destroy(struct world *world);
+void world_drawdef(struct world *world);
+bool world_find_intersect_frect(struct world *world, t_frect rect, t_frect *intersection);
+bool world_find_intersect_rect(struct world *world, t_rect rect, t_rect *intersection);
+bool world_is_tile_at(struct world *world, float x, float y);
+void world_place_tile(struct world *world, float x, float y);
+void world_remove_tile(struct world *world, float x, float y);
+
+
+#endif
diff --git a/src/game_api.h b/src/game_api.h
new file mode 100644
index 0000000..79efee9
--- /dev/null
+++ b/src/game_api.h
@@ -0,0 +1,13 @@
+/* include this header in game code to get the usable parts of the engine */
+#ifndef GAME_API_H
+#define GAME_API_H
+
+
+#include "context.h"
+#include "rendering.h"
+#include "util.h"
+
+#include
+
+
+#endif
diff --git a/src/input.c b/src/input.c
new file mode 100644
index 0000000..0117801
--- /dev/null
+++ b/src/input.c
@@ -0,0 +1,296 @@
+#include "input.h"
+#include "util.h"
+
+#include
+#include
+
+#include
+#include
+
+
+static void update_action_pressed_state(struct input_state *input, struct action *action) {
+ for (size_t i = 0; i < SDL_arraysize(action->bindings); ++i) {
+ switch (action->bindings[i].source) {
+ case BUTTON_SOURCE_NOT_SET:
+ break;
+ case BUTTON_SOURCE_KEYBOARD_PHYSICAL:
+ /* not pressed */
+ if (input->keyboard_state[action->bindings[i].code.scancode] == 0) {
+ action->just_changed = action->is_pressed;
+ action->is_pressed = false;
+ }
+ /* pressed */
+ else {
+ action->just_changed = !action->is_pressed;
+ action->is_pressed = true;
+ return;
+ }
+ break;
+ case BUTTON_SOURCE_MOUSE:
+ /* not pressed */
+ if ((input->mouse_state & action->bindings[i].code.mouse_button) == 0) {
+ action->just_changed = action->is_pressed;
+ action->is_pressed = false;
+ }
+ /* pressed */
+ else {
+ action->just_changed = !action->is_pressed;
+ action->is_pressed = true;
+
+ /*
+ * SDL_RenderWindowToLogical will turn window mouse
+ * coords into a position inside the logical render
+ * area. this has to be done to get an accurate point
+ * that can actually be used in game logic
+ */
+ SDL_RenderWindowToLogical(input->renderer,
+ input->mouse_window_position.x,
+ input->mouse_window_position.y,
+ &action->position.x,
+ &action->position.y);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+
+static void input_bind_code_to_action(struct input_state *input,
+ char *action_name,
+ enum button_source source,
+ union button_code code)
+{
+ struct action_hash_item *action_item = shgetp_null(input->action_hash, action_name);
+ if (action_item == NULL) {
+ log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
+ return;
+ }
+ struct action *action = &action_item->value;
+
+ /* check every binding to make sure this code isn't already bound */
+ for (size_t i = 0; i < SDL_arraysize(action->bindings); ++i) {
+ struct button *binding = &action->bindings[i];
+
+ if (binding->source != source)
+ break;
+
+ bool is_already_bound = false;
+ switch (binding->source) {
+ case BUTTON_SOURCE_NOT_SET:
+ break;
+ case BUTTON_SOURCE_KEYBOARD_PHYSICAL:
+ is_already_bound = binding->code.scancode == code.scancode;
+ break;
+ case BUTTON_SOURCE_KEYBOARD_CHARACTER:
+ is_already_bound = binding->code.keycode == code.keycode;
+ break;
+ case BUTTON_SOURCE_GAMEPAD:
+ is_already_bound = binding->code.gamepad_button == code.gamepad_button;
+ break;
+ case BUTTON_SOURCE_MOUSE:
+ is_already_bound = binding->code.mouse_button == code.mouse_button;
+ break;
+ }
+
+ if (is_already_bound) {
+ log_warn("(%s) Code already bound to action \"%s\".", __func__, action_name);
+ return;
+ }
+ }
+
+ /* if we're at max bindings, forget the first element and shift the rest */
+ if (action->num_bindings == SDL_arraysize(action->bindings)) {
+ --action->num_bindings;
+ size_t shifted_size = (sizeof action->bindings) - (sizeof action->bindings[0]);
+ memmove(action->bindings, action->bindings + 1, shifted_size);
+ }
+
+ action->bindings[action->num_bindings++] = (struct button) {
+ .source = source,
+ .code = code,
+ };
+}
+
+
+static void input_unbind_code_from_action(struct input_state *input,
+ char *action_name,
+ enum button_source source,
+ union button_code code)
+{
+ struct action_hash_item *action_item = shgetp_null(input->action_hash, action_name);
+ if (action_item == NULL) {
+ log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
+ return;
+ }
+ struct action *action = &action_item->value;
+
+ /* check every binding to make sure this code is bound */
+ size_t index = 0;
+ bool is_bound = false;
+ for (index = 0; index < SDL_arraysize(action->bindings); ++index) {
+ struct button *binding = &action->bindings[index];
+
+ if (binding->source != source)
+ continue;
+
+ switch (binding->source) {
+ case BUTTON_SOURCE_NOT_SET:
+ break;
+ case BUTTON_SOURCE_KEYBOARD_PHYSICAL:
+ is_bound = binding->code.scancode == code.scancode;
+ break;
+ case BUTTON_SOURCE_KEYBOARD_CHARACTER:
+ is_bound = binding->code.keycode == code.keycode;
+ break;
+ case BUTTON_SOURCE_GAMEPAD:
+ is_bound = binding->code.gamepad_button == code.gamepad_button;
+ break;
+ case BUTTON_SOURCE_MOUSE:
+ is_bound = binding->code.mouse_button == code.mouse_button;
+ break;
+ }
+
+ if (is_bound)
+ break;
+ }
+
+ if (!is_bound) {
+ log_warn("(%s) Code is not bound to action \"%s\".", __func__, action_name);
+ return;
+ }
+
+ /* remove the element to unbind and shift the rest so there isn't a gap */
+ if (action->num_bindings == SDL_arraysize(action->bindings)) {
+ size_t elements_after_index = action->num_bindings - index;
+ size_t shifted_size = elements_after_index * (sizeof action->bindings[0]);
+ memmove(action->bindings + index, action->bindings + index + 1, shifted_size);
+ --action->num_bindings;
+ }
+}
+
+
+void input_state_init(struct input_state *input, SDL_Renderer *renderer) {
+ sh_new_strdup(input->action_hash);
+ input->renderer = renderer;
+}
+
+
+void input_state_deinit(struct input_state *input) {
+ shfree(input->action_hash);
+}
+
+
+void input_state_update(struct input_state *input) {
+ input->keyboard_state = SDL_GetKeyboardState(NULL);
+ input->mouse_state = SDL_GetMouseState(&input->mouse_window_position.x,
+ &input->mouse_window_position.y);
+
+ for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
+ struct action *action = &input->action_hash[i].value;
+ update_action_pressed_state(input, action);
+ }
+}
+
+
+void input_bind_action_scancode(struct input_state *input,
+ char *action_name,
+ SDL_Scancode scancode)
+{
+ input_bind_code_to_action(input,
+ action_name,
+ BUTTON_SOURCE_KEYBOARD_PHYSICAL,
+ (union button_code) { .scancode = scancode });
+}
+
+
+void input_unbind_action_scancode(struct input_state *input,
+ char *action_name,
+ SDL_Scancode scancode)
+{
+ input_unbind_code_from_action(input,
+ action_name,
+ BUTTON_SOURCE_KEYBOARD_PHYSICAL,
+ (union button_code) { .scancode = scancode });
+}
+
+
+void input_bind_action_mouse(struct input_state *input,
+ char *action_name,
+ uint8_t mouse_button)
+{
+ input_bind_code_to_action(input,
+ action_name,
+ BUTTON_SOURCE_MOUSE,
+ (union button_code) { .mouse_button = mouse_button});
+}
+
+
+void input_unbind_action_mouse(struct input_state *input,
+ char *action_name,
+ uint8_t mouse_button)
+{
+ input_unbind_code_from_action(input,
+ action_name,
+ BUTTON_SOURCE_MOUSE,
+ (union button_code) { .mouse_button = mouse_button});
+}
+
+
+void input_add_action(struct input_state *input, char *action_name) {
+ if (shgeti(input->action_hash, action_name) >= 0) {
+ log_warn("(%s) Action \"%s\" is already registered.", __func__, action_name);
+ return;
+ }
+
+ shput(input->action_hash, action_name, (struct action) { 0 });
+}
+
+
+void input_delete_action(struct input_state *input, char *action_name) {
+ if (shdel(input->action_hash, action_name) == 0)
+ log_warn("(%s) Action \"%s\" is not registered.", __func__, action_name);
+}
+
+
+bool input_is_action_pressed(struct input_state *input, char *action_name) {
+ struct action_hash_item *action = shgetp_null(input->action_hash, action_name);
+ if (action == NULL) {
+ log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
+ return false;
+ }
+ return action->value.is_pressed;
+}
+
+
+bool input_is_action_just_pressed(struct input_state *input, char *action_name) {
+ struct action_hash_item *action = shgetp_null(input->action_hash, action_name);
+ if (action == NULL) {
+ log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
+ return false;
+ }
+ return action->value.is_pressed && action->value.just_changed;
+}
+
+
+bool input_is_action_just_released(struct input_state *input, char *action_name) {
+ struct action_hash_item *action = shgetp_null(input->action_hash, action_name);
+ if (action == NULL) {
+ log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
+ return false;
+ }
+ return !action->value.is_pressed && action->value.just_changed;
+}
+
+
+t_fvec2 input_get_action_position(struct input_state *input, char *action_name) {
+ struct action_hash_item *action = shgetp_null(input->action_hash, action_name);
+ if (action == NULL) {
+ log_warn("(%s) Action \"%s\" does not exist.", __func__, action_name);
+ return (t_fvec2) { 0 };
+ }
+
+ return action->value.position;
+}
diff --git a/src/input.h b/src/input.h
new file mode 100644
index 0000000..fab59b3
--- /dev/null
+++ b/src/input.h
@@ -0,0 +1,92 @@
+#ifndef INPUT_H
+#define INPUT_H
+
+#include "config.h"
+#include "util.h"
+
+#include
+
+#include
+#include
+
+
+enum button_source {
+ BUTTON_SOURCE_NOT_SET,
+ BUTTON_SOURCE_KEYBOARD_PHYSICAL,
+ BUTTON_SOURCE_KEYBOARD_CHARACTER,
+ BUTTON_SOURCE_GAMEPAD,
+ BUTTON_SOURCE_MOUSE,
+};
+
+
+union button_code {
+ SDL_Scancode scancode;
+ SDL_Keycode keycode;
+ SDL_GameControllerButton gamepad_button;
+ uint8_t mouse_button; /* SDL_BUTTON_ enum */
+};
+
+
+/* an input to which an action is bound */
+/* it is not limited to literal buttons */
+struct button {
+ enum button_source source;
+ union button_code code;
+};
+
+
+/* represents the collective state of a group of buttons */
+/* that is, changes in the states of any of the bound buttons will affect it */
+struct action {
+ size_t num_bindings;
+ struct button bindings[NUM_KEYBIND_SLOTS];
+ t_fvec2 position; /* set if applicable */
+ bool is_pressed;
+ bool just_changed;
+};
+
+
+struct action_hash_item {
+ char *key;
+ struct action value;
+};
+
+
+struct input_state {
+ struct action_hash_item *action_hash;
+ SDL_Renderer *renderer; /* some input relates to the screen in some way */
+ const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
+ uint32_t mouse_state; /* SDL mouse button bitmask */
+ t_vec2 mouse_window_position;
+ enum button_source last_active_source;
+ bool is_anything_just_pressed;
+};
+
+
+void input_state_init(struct input_state *input, SDL_Renderer *renderer);
+void input_state_deinit(struct input_state *input);
+void input_state_update(struct input_state *input);
+
+void input_bind_action_scancode(struct input_state *input,
+ char *action_name,
+ SDL_Scancode scancode);
+void input_unbind_action_scancode(struct input_state *input,
+ char *action_name,
+ SDL_Scancode scancode);
+void input_bind_action_mouse(struct input_state *input,
+ char *action_name,
+ uint8_t mouse_button);
+void input_unbind_action_mouse(struct input_state *input,
+ char *action_name,
+ uint8_t mouse_button);
+
+void input_add_action(struct input_state *input, char *action_name);
+void input_delete_action(struct input_state *input, char *action_name);
+
+bool input_is_action_pressed(struct input_state *input, char *action_name);
+bool input_is_action_just_pressed(struct input_state *input, char *action_name);
+bool input_is_action_just_released(struct input_state *input, char *action_name);
+t_fvec2 input_get_action_position(struct input_state *input, char *action_name);
+
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..8804245
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,306 @@
+#include "context.h"
+#include "rendering.h"
+#include "input.h"
+#include "util.h"
+#include "textures.h"
+#include "game/game.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+static void poll_events(void) {
+ SDL_Event e;
+
+ while (SDL_PollEvent(&e)) {
+ switch (e.type) {
+ case SDL_QUIT:
+ ctx.is_running = false;
+ break;
+
+ case SDL_WINDOWEVENT:
+ if (e.window.windowID != ctx.window_id)
+ break;
+
+ switch (e.window.event) {
+ case SDL_WINDOWEVENT_RESIZED:
+ break;
+ }
+
+ break;
+ }
+ }
+}
+
+
+void main_loop(void) {
+ /*
+ if (!ctx.is_running) {
+ end(ctx);
+ clean_up(ctx);
+ emscripten_cancel_main_loop();
+ }
+ */
+
+ /* frame timer */
+ int64_t current_frame_time = SDL_GetPerformanceCounter();
+ int64_t delta_time = current_frame_time - ctx.prev_frame_time;
+ ctx.prev_frame_time = current_frame_time;
+
+ /* handle unexpected timer anomalies (overflow, extra slow frames, etc) */
+ if (delta_time > ctx.desired_frametime * 8) { /* ignore extra-slow frames */
+ delta_time = ctx.desired_frametime;
+ }
+ delta_time = MAX(0, delta_time);
+
+ /* vsync time snapping */
+ /* get the refresh rate of the current display (it might not always be the same one) */
+ int display_framerate = 60; /* a reasonable guess */
+ SDL_DisplayMode current_display_mode;
+ int current_display_index = SDL_GetWindowDisplayIndex(ctx.window);
+ if (SDL_GetCurrentDisplayMode(current_display_index, ¤t_display_mode) == 0)
+ display_framerate = current_display_mode.refresh_rate;
+
+ int64_t snap_hz = display_framerate;
+ if (snap_hz <= 0)
+ snap_hz = 60;
+
+ /* these are to snap delta time to vsync values if it's close enough */
+ int64_t vsync_maxerror = (int64_t)((double)ctx.clocks_per_second * 0.0002);
+ size_t snap_frequency_count = 8;
+ for (size_t i = 0; i < snap_frequency_count; ++i) {
+ int64_t frequency = (ctx.clocks_per_second / snap_hz) * (i+1);
+
+ if (llabs(delta_time - frequency) < vsync_maxerror) {
+ delta_time = frequency;
+ break;
+ }
+ }
+
+ /* delta time averaging */
+ size_t time_averager_count = SDL_arraysize(ctx.time_averager);
+
+ for (size_t i = 0; i < time_averager_count - 1; ++i) {
+ ctx.time_averager[i] = ctx.time_averager[i+1];
+ }
+ ctx.time_averager[time_averager_count - 1] = delta_time;
+ int64_t averager_sum = 0;
+ for (size_t i = 0; i < time_averager_count; ++i) {
+ averager_sum += ctx.time_averager[i];
+ }
+ delta_time = averager_sum / time_averager_count;
+
+ ctx.delta_averager_residual += averager_sum % time_averager_count;
+ delta_time += ctx.delta_averager_residual / time_averager_count;
+ ctx.delta_averager_residual %= time_averager_count;
+
+ /* add to the accumulator */
+ ctx.frame_accumulator += delta_time;
+
+ /* spiral of death protection */
+ if (ctx.frame_accumulator > ctx.desired_frametime * 8) {
+ ctx.resync_flag = true;
+ }
+
+ /* timer resync if requested */
+ if (ctx.resync_flag) {
+ ctx.frame_accumulator = 0;
+ delta_time = ctx.desired_frametime;
+ ctx.resync_flag = false;
+ }
+
+ /* finally, let's get to work */
+ poll_events();
+ input_state_update(&ctx.input);
+
+ /* do we _always_ have to be dry? */
+ if (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) {
+ while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) {
+ for (size_t i = 0; i < ctx.update_multiplicity; ++i) {
+ render_queue_clear();
+
+ game_tick();
+
+ ctx.frame_accumulator -= ctx.desired_frametime;
+ ctx.tick_count = (ctx.tick_count % ULLONG_MAX) + 1;
+ }
+ }
+ } else {
+ render_queue_clear();
+ }
+
+ /* the "step" will always run as long as there is time to spare. */
+ /* without interpolation, this is preferable to fixed ticks if the additional */
+ /* delta time calculations (like in physics code) are acceptable */
+ game_step(delta_time);
+ ctx.step_count = (ctx.step_count % ULLONG_MAX) + 1;
+
+ render();
+}
+
+
+static bool initialize(void) {
+ if (SDL_Init(SDL_INIT_EVERYTHING) == -1) {
+ CRY_SDL("SDL initialization failed.");
+ return false;
+ }
+
+ /* init got far enough to create a window */
+ ctx.window = SDL_CreateWindow("emerald",
+ SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED,
+ RENDER_BASE_WIDTH,
+ RENDER_BASE_HEIGHT,
+ SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE);
+ if (ctx.window == NULL) {
+ CRY_SDL("Window creation failed.");
+ goto fail;
+ }
+
+ /* might need this to have multiple windows */
+ ctx.window_id = SDL_GetWindowID(ctx.window);
+
+ /* now that we have a window, we know a renderer can be created */
+ ctx.renderer = SDL_CreateRenderer(ctx.window, -1, SDL_RENDERER_PRESENTVSYNC);
+
+ /* SDL_SetHint(SDL_HINT_RENDER_LOGICAL_SIZE_MODE, "overscan"); */
+ /* SDL_RenderSetIntegerScale(ctx.renderer, SDL_TRUE); */
+ SDL_RenderSetLogicalSize(ctx.renderer, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT);
+ SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
+
+ /* images */
+ if (IMG_Init(IMG_INIT_PNG) == 0) {
+ CRY_SDL("SDL_image initialization failed.");
+ goto fail;
+ }
+
+ /* filesystem time */
+ /* TODO: ANDROID: see the warning in physicsfs PHYSFS_init docs/header */
+ if (!PHYSFS_init(ctx.argv[0]) ||
+ !PHYSFS_setSaneConfig(ORGANIZATION_NAME, APP_NAME, PACKAGE_EXTENSION, false, true))
+ {
+ CRY_PHYSFS("Filesystem initialization failed.");
+ goto fail;
+ }
+
+ /* you could change this at runtime if you wanted */
+ ctx.update_multiplicity = 1;
+
+ /* debug mode _defaults_ to being enabled on debug builds. */
+ /* you should be able to enable it at runtime on any build */
+#ifndef NDEBUG
+ ctx.debug = true;
+#else
+ ctx.debug = false;
+#endif
+
+ /* random seeding */
+ /* SDL_GetPerformanceCounter returns some platform-dependent number. */
+ /* it should vary between game instances. i checked! random enough for me. */
+ ctx.random_seed = SDL_GetPerformanceCounter();
+ srand((unsigned int)ctx.random_seed);
+ stbds_rand_seed(ctx.random_seed);
+
+ /* main loop machinery */
+ ctx.is_running = true;
+ ctx.resync_flag = true;
+ ctx.clocks_per_second = SDL_GetPerformanceFrequency();
+ ctx.prev_frame_time = SDL_GetPerformanceCounter();
+ ctx.desired_frametime = ctx.clocks_per_second / TICKS_PER_SECOND;
+ ctx.frame_accumulator = 0;
+ ctx.tick_count = 0;
+ ctx.step_count = 0;
+
+ /* delta time averaging */
+ ctx.delta_averager_residual = 0;
+ for (size_t i = 0; i < SDL_arraysize(ctx.time_averager); ++i) {
+ ctx.time_averager[i] = ctx.desired_frametime;
+ }
+
+ /* rendering */
+ /* these are dynamic arrays and will be allocated lazily by stb_ds */
+ ctx.render_queue_sprites = NULL;
+ ctx.render_queue_rectangles = NULL;
+ ctx.render_queue_circles = NULL;
+ ctx.circle_radius_hash = NULL;
+
+ textures_cache_init(&ctx.texture_cache, ctx.window);
+ if (TTF_Init() < 0) {
+ CRY_SDL("SDL_ttf initialization failed.");
+ goto fail;
+ }
+
+ /* input */
+ input_state_init(&ctx.input, ctx.renderer);
+
+ /* scripting */
+ /*
+ if (!scripting_init(ctx)) {
+ goto fail;
+ }
+ */
+
+ return true;
+
+fail:
+ SDL_Quit();
+ return false;
+}
+
+
+/* will not be called on an abnormal exit */
+static void clean_up(void) {
+ /*
+ scripting_deinit(ctx);
+ */
+
+ input_state_deinit(&ctx.input);
+
+ arrfree(ctx.render_queue_sprites);
+ arrfree(ctx.render_queue_rectangles);
+ textures_cache_deinit(&ctx.texture_cache);
+
+ PHYSFS_deinit();
+ TTF_Quit();
+ IMG_Quit();
+ SDL_Quit();
+}
+
+
+int main(int argc, char **argv) {
+ ctx.argc = argc;
+ ctx.argv = argv;
+
+
+ if (!initialize())
+ return EXIT_FAILURE;
+
+ for (int i = 1; i < (argc - 1); ++i) {
+ if (strcmp(argv[i], "--data-dir") == 0) {
+ if (!PHYSFS_mount(argv[i+1], NULL, true)) {
+ CRY_PHYSFS("Data dir mount override failed.");
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ ctx.was_successful = true;
+ while (ctx.is_running)
+ main_loop();
+
+ game_end();
+ clean_up();
+
+ return ctx.was_successful ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/private/rendering.h b/src/private/rendering.h
new file mode 100644
index 0000000..2c1dee4
--- /dev/null
+++ b/src/private/rendering.h
@@ -0,0 +1,34 @@
+#ifndef PRIVATE_RENDERING_H
+#define PRIVATE_RENDERING_H
+
+#include "../rendering.h"
+#include "../util.h"
+
+#include
+
+#include
+
+struct sprite_primitive {
+ t_frect rect;
+ t_color color;
+ double rotation;
+ char *path;
+ SDL_BlendMode blend_mode;
+ int atlas_index;
+ int layer;
+ bool flip_x;
+ bool flip_y;
+};
+
+struct rect_primitive {
+ t_frect rect;
+ t_color color;
+};
+
+struct circle_primitive {
+ float radius;
+ t_color color;
+ t_fvec2 position;
+};
+
+#endif
diff --git a/src/rendering.c b/src/rendering.c
new file mode 100644
index 0000000..0d5092b
--- /dev/null
+++ b/src/rendering.c
@@ -0,0 +1,367 @@
+#include "private/rendering.h"
+#include "context.h"
+#include "config.h"
+#include "textures.h"
+
+#include
+#include
+
+#include
+#include
+
+
+void render_queue_clear(void) {
+ /* since i don't intend to free the queues, */
+ /* it's faster and simpler to just "start over" */
+ /* and start overwriting the existing data */
+ arrsetlen(ctx.render_queue_sprites, 0);
+ arrsetlen(ctx.render_queue_rectangles, 0);
+ arrsetlen(ctx.render_queue_circles, 0);
+}
+
+
+/*
+ * an implementation note:
+ * try to avoid doing expensive work in the push functions,
+ * because they will be called multiple times in the main loop
+ * before anything is really rendered
+ */
+
+/* sprite */
+void push_sprite(char *path, t_frect rect) {
+ textures_load(&ctx.texture_cache, path);
+
+ struct sprite_primitive sprite = {
+ .rect = rect,
+ .color = (t_color) { 255, 255, 255, 255 },
+ .path = path,
+ .rotation = 0.0,
+ .blend_mode = SDL_BLENDMODE_BLEND,
+ .atlas_index =
+ textures_get_atlas_index(&ctx.texture_cache, path),
+ .layer = 0,
+ .flip_x = false,
+ .flip_y = false,
+ };
+
+ arrput(ctx.render_queue_sprites, sprite);
+}
+
+
+void push_sprite_ex(t_frect rect, t_push_sprite_args args) {
+ textures_load(&ctx.texture_cache, args.path);
+
+ struct sprite_primitive sprite = {
+ .rect = rect,
+ .color = args.color,
+ .path = args.path,
+ .rotation = args.rotation,
+ .blend_mode = args.blend_mode,
+ .atlas_index =
+ textures_get_atlas_index(&ctx.texture_cache, args.path),
+ .layer = args.layer,
+ .flip_x = args.flip_x,
+ .flip_y = args.flip_y,
+ };
+
+ arrput(ctx.render_queue_sprites, sprite);
+}
+
+
+/* rectangle */
+void push_rectangle(t_frect rect, t_color color) {
+ struct rect_primitive rectangle = {
+ .rect = rect,
+ .color = color,
+ };
+
+ arrput(ctx.render_queue_rectangles, rectangle);
+}
+
+
+/* circle */
+void push_circle(t_fvec2 position, float radius, t_color color) {
+ struct circle_primitive circle = {
+ .radius = radius,
+ .color = color,
+ .position = position,
+ };
+
+ arrput(ctx.render_queue_circles, circle);
+}
+
+
+/* rendering */
+static void render_background(void) {
+ SDL_SetRenderDrawColor(ctx.renderer, 230, 230, 230, 255);
+ SDL_Rect rect = {
+ 0,
+ 0,
+ ctx.window_w,
+ ctx.window_h
+ };
+ SDL_RenderFillRect(ctx.renderer, &rect);
+
+ SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255);
+}
+
+
+/* compare functions for the sort in render_sprites */
+static int cmp_atlases(const void *a, const void *b) {
+ int index_a = ((const struct sprite_primitive *)a)->atlas_index;
+ int index_b = ((const struct sprite_primitive *)b)->atlas_index;
+
+ return (index_a > index_b) - (index_a < index_b);
+}
+
+
+static int cmp_blend_modes(const void *a, const void *b) {
+ SDL_BlendMode mode_a = ((const struct sprite_primitive *)a)->blend_mode;
+ SDL_BlendMode mode_b = ((const struct sprite_primitive *)b)->blend_mode;
+
+ return (mode_a > mode_b) - (mode_a < mode_b);
+}
+
+
+static int cmp_colors(const void *a, const void *b) {
+ t_color color_a = ((const struct sprite_primitive *)a)->color;
+ t_color color_b = ((const struct sprite_primitive *)b)->color;
+
+ /* check reds */
+ if (color_a.r < color_b.r)
+ return -1;
+ else if (color_a.r > color_b.r)
+ return 1;
+
+ /* reds were equal, check greens */
+ else if (color_a.g < color_b.g)
+ return -1;
+ else if (color_a.g > color_b.g)
+ return 1;
+
+ /* greens were equal, check blues */
+ else if (color_a.b < color_b.b)
+ return -1;
+ else if (color_a.b > color_b.b)
+ return 1;
+
+ /* blues were equal, check alphas */
+ else if (color_a.a < color_b.a)
+ return -1;
+ else if (color_a.a > color_b.a)
+ return 1;
+
+ /* entirely equal */
+ else
+ return 0;
+}
+
+
+static int cmp_layers(const void *a, const void *b) {
+ int layer_a = ((const struct sprite_primitive *)a)->layer;
+ int layer_b = ((const struct sprite_primitive *)b)->layer;
+
+ return (layer_a > layer_b) - (layer_a < layer_b);
+}
+
+
+/* necessary to allow the renderer to draw in batches in the best case */
+static void sort_sprites(struct sprite_primitive *sprites) {
+ size_t sprites_len = arrlenu(sprites);
+ qsort(sprites, sprites_len, sizeof *sprites, cmp_atlases);
+ qsort(sprites, sprites_len, sizeof *sprites, cmp_blend_modes);
+ qsort(sprites, sprites_len, sizeof *sprites, cmp_colors);
+ qsort(sprites, sprites_len, sizeof *sprites, cmp_layers);
+}
+
+
+static void render_sprite(struct sprite_primitive *sprite) {
+ SDL_Rect srcrect_value = { 0 };
+ SDL_Rect *srcrect = &srcrect_value;
+ SDL_Texture *texture = NULL;
+
+ /* loner */
+ if (sprite->atlas_index == -1) {
+ srcrect = NULL;
+ texture = textures_get_loner(&ctx.texture_cache, sprite->path);
+ } else {
+ *srcrect = textures_get_srcrect(&ctx.texture_cache, sprite->path);
+ texture = textures_get_atlas(&ctx.texture_cache, sprite->atlas_index);
+ }
+
+ SDL_RendererFlip flip = SDL_FLIP_NONE;
+ if (sprite->flip_x)
+ flip |= SDL_FLIP_HORIZONTAL;
+ if (sprite->flip_y)
+ flip |= SDL_FLIP_VERTICAL;
+
+ SDL_SetTextureColorMod(texture,
+ sprite->color.r,
+ sprite->color.g,
+ sprite->color.b);
+ SDL_SetTextureAlphaMod(texture, sprite->color.a);
+ SDL_SetTextureBlendMode(texture, sprite->blend_mode);
+
+ SDL_Rect rect = {
+ (int)sprite->rect.x,
+ (int)sprite->rect.y,
+ (int)sprite->rect.w,
+ (int)sprite->rect.h
+ };
+
+ SDL_RenderCopyEx(ctx.renderer,
+ texture,
+ srcrect,
+ &rect,
+ sprite->rotation,
+ NULL,
+ flip);
+}
+
+
+static void render_rectangle(struct rect_primitive *rectangle) {
+ SDL_SetRenderDrawColor(ctx.renderer,
+ rectangle->color.r,
+ rectangle->color.g,
+ rectangle->color.b,
+ rectangle->color.a);
+
+ SDL_Rect rect = {
+ (int)rectangle->rect.x,
+ (int)rectangle->rect.y,
+ (int)rectangle->rect.w,
+ (int)rectangle->rect.h
+ };
+
+ SDL_RenderFillRect(ctx.renderer, &rect);
+ SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255);
+}
+
+
+/* vertices_out and indices_out MUST BE FREED */
+static void create_circle_geometry(t_fvec2 position,
+ t_color color,
+ float radius,
+ size_t num_vertices,
+ SDL_Vertex **vertices_out,
+ int **indices_out)
+{
+ SDL_Vertex *vertices = cmalloc(sizeof *vertices * (num_vertices + 1));
+ int *indices = cmalloc(sizeof *indices * (num_vertices * 3));
+
+ /* the angle (in radians) to rotate by on each iteration */
+ float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
+
+ vertices[0].position.x = (float)position.x;
+ vertices[0].position.y = (float)position.y;
+ vertices[0].color.r = color.r;
+ vertices[0].color.g = color.g;
+ vertices[0].color.b = color.b;
+ vertices[0].color.a = color.a;
+ vertices[0].tex_coord = (SDL_FPoint){ 0, 0 };
+
+ /* this point will rotate around the center */
+ float start_x = 0.0f - radius;
+ float start_y = 0.0f;
+
+ for (size_t i = 1; i < num_vertices + 1; ++i) {
+ float final_seg_rotation_angle = (float)i * seg_rotation_angle;
+
+ vertices[i].position.x =
+ cos(final_seg_rotation_angle) * start_x -
+ sin(final_seg_rotation_angle) * start_y;
+ vertices[i].position.y =
+ cos(final_seg_rotation_angle) * start_y +
+ sin(final_seg_rotation_angle) * start_x;
+
+ vertices[i].position.x += position.x;
+ vertices[i].position.y += position.y;
+
+ vertices[i].color.r = color.r;
+ vertices[i].color.g = color.g;
+ vertices[i].color.b = color.b;
+ vertices[i].color.a = color.a;
+
+ vertices[i].tex_coord = (SDL_FPoint){ 0, 0 };
+
+
+ size_t triangle_offset = 3 * (i - 1);
+
+ /* center point index */
+ indices[triangle_offset] = 0;
+ /* generated point index */
+ indices[triangle_offset + 1] = (int)i;
+
+ size_t index = (i + 1) % num_vertices;
+ if (index == 0)
+ index = num_vertices;
+ indices[triangle_offset + 2] = (int)index;
+ }
+
+ *vertices_out = vertices;
+ *indices_out = indices;
+}
+
+
+static void render_circle(struct circle_primitive *circle) {
+ SDL_Vertex *vertices = NULL;
+ int *indices = NULL;
+ int num_vertices = (int)circle->radius;
+
+ create_circle_geometry(circle->position,
+ circle->color,
+ circle->radius,
+ num_vertices,
+ &vertices,
+ &indices);
+
+ SDL_RenderGeometry(ctx.renderer, NULL,
+ vertices,
+ num_vertices + 1, /* vertices + center vertex */
+ indices,
+ num_vertices * 3);
+
+ free(vertices);
+ free(indices);
+}
+
+
+static void render_sprites(void) {
+ if (ctx.texture_cache.is_dirty)
+ textures_update_current_atlas(&ctx.texture_cache);
+
+ sort_sprites(ctx.render_queue_sprites);
+
+ for (size_t i = 0; i < arrlenu(ctx.render_queue_sprites); ++i) {
+ render_sprite(&ctx.render_queue_sprites[i]);
+ }
+}
+
+
+static void render_rectangles(void) {
+ for (size_t i = 0; i < arrlenu(ctx.render_queue_rectangles); ++i) {
+ render_rectangle(&ctx.render_queue_rectangles[i]);
+ }
+}
+
+
+static void render_circles(void) {
+ for (size_t i = 0; i < arrlenu(ctx.render_queue_circles); ++i) {
+ render_circle(&ctx.render_queue_circles[i]);
+ }
+}
+
+
+void render(void) {
+ SDL_SetRenderDrawBlendMode(ctx.renderer, SDL_BLENDMODE_BLEND);
+ SDL_SetRenderDrawColor(ctx.renderer, 0, 0, 0, 255);
+ SDL_RenderClear(ctx.renderer);
+ SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255);
+
+ render_background();
+ render_sprites();
+ render_rectangles();
+ render_circles();
+
+ SDL_RenderPresent(ctx.renderer);
+}
diff --git a/src/rendering.h b/src/rendering.h
new file mode 100644
index 0000000..018f79f
--- /dev/null
+++ b/src/rendering.h
@@ -0,0 +1,41 @@
+#ifndef RENDERING_H
+#define RENDERING_H
+
+#include "util.h"
+
+#include
+
+#include
+
+typedef struct push_sprite_args {
+ char *path;
+ int layer;
+ t_color color;
+ double rotation;
+ SDL_BlendMode blend_mode;
+ bool flip_x;
+ bool flip_y;
+} t_push_sprite_args;
+
+/* clears all render queues */
+void render_queue_clear(void);
+
+/* pushes a sprite onto the sprite render queue */
+/* this is a simplified version of push_sprite_ex for the most common case. */
+/* it assumes you want no color modulation, no rotation, no flip, layer 0, and alpha blending */
+void push_sprite(char *path, t_frect rect);
+
+/* pushes a sprite onto the sprite render queue */
+/* note that if args is zeroed out, the color will be transparent black */
+void push_sprite_ex(t_frect rect, t_push_sprite_args args);
+
+/* pushes a filled rectangle onto the rectangle render queue */
+void push_rectangle(t_frect rect, t_color color);
+
+/* pushes a filled circle onto the circle render queue */
+void push_circle(t_fvec2 position, float radius, t_color color);
+
+/* renders the background, then the primitives in all render queues */
+void render(void);
+
+#endif
diff --git a/src/scripting.c b/src/scripting.c
new file mode 100644
index 0000000..9d425b3
--- /dev/null
+++ b/src/scripting.c
@@ -0,0 +1,139 @@
+#include "scripting.h"
+#include "util.h"
+#include "context.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+static void msgbox(UmkaStackSlot *params, UmkaStackSlot *result) {
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION,
+ params[1].ptrVal, params[0].ptrVal, NULL);
+}
+
+static void umka_log_info(UmkaStackSlot *params, UmkaStackSlot *result) {
+ log_info(params[0].ptrVal);
+}
+
+static void umka_log_critical(UmkaStackSlot *params, UmkaStackSlot *result) {
+ log_critical(params[0].ptrVal);
+}
+
+static void umka_log_warn(UmkaStackSlot *params, UmkaStackSlot *result) {
+ log_warn(params[0].ptrVal);
+}
+
+static void is_action_pressed(UmkaStackSlot *params, UmkaStackSlot *result) {
+ struct state *state = params[1].ptrVal;
+ t_ctx *ctx = state->hidden_ptr;
+
+ bool value = input_is_action_pressed(&ctx->input, params[0].ptrVal);
+ result->uintVal = value;
+}
+
+static void is_action_just_pressed(UmkaStackSlot *params, UmkaStackSlot *result) {
+ struct state *state = params[1].ptrVal;
+ t_ctx *ctx = state->hidden_ptr;
+
+ bool value = input_is_action_just_pressed(&ctx->input, params[0].ptrVal);
+ result->uintVal = value;
+}
+
+static void is_action_just_released(UmkaStackSlot *params, UmkaStackSlot *result) {
+ struct state *state = params[1].ptrVal;
+ t_ctx *ctx = state->hidden_ptr;
+
+ bool value = input_is_action_just_released(&ctx->input, params[0].ptrVal);
+ result->uintVal = value;
+}
+
+static void get_action_position(UmkaStackSlot *params, UmkaStackSlot *result) {
+ struct state *state = params[2].ptrVal;
+ t_ctx *ctx = state->hidden_ptr;
+
+ t_fvec2 *position = params[0].ptrVal;
+ *position = input_get_action_position(&ctx->input, params[1].ptrVal);
+
+ // the result is in a hidden result pointer allocated by Umka
+ result->ptrVal = params[0].ptrVal;
+}
+
+static void register_api(void *umka) {
+ umkaAddFunc(umka, "msgbox", msgbox);
+ umkaAddFunc(umka, "logInfo", umka_log_info);
+ umkaAddFunc(umka, "logCritical", umka_log_critical);
+ umkaAddFunc(umka, "logWarn", umka_log_warn);
+ umkaAddFunc(umka, "cImplIsActionPressed", is_action_pressed);
+ umkaAddFunc(umka, "cImplIsActionJustPressed", is_action_just_pressed);
+ umkaAddFunc(umka, "cImplIsActionJustReleased", is_action_just_released);
+ umkaAddFunc(umka, "getActionPosition", get_action_position);
+}
+
+bool scripting_init(t_ctx *ctx) {
+ if (!PHYSFS_exists("/scripts/main.um")) {
+ CRY("Failed to initialize scripting", "Could not find a main.um (we need it)");
+ return false;
+ }
+
+ ctx->umka = umkaAlloc();
+
+ char *main_script = file_to_str("/scripts/main.um");
+ bool umka_ok = umkaInit(ctx->umka,
+ "main.um",
+ main_script,
+ UMKA_STACK_SIZE,
+ NULL,
+ ctx->argc,
+ ctx->argv,
+ false,
+ false,
+ NULL);
+ free(main_script);
+
+ if (!umka_ok) {
+ CRY("Failed to initialize scripting", "Unknown Umka error");
+ return false;
+ }
+
+ /* all Umka files are compiled even if they're never used */
+ char **dir_file_names = PHYSFS_enumerateFiles("/scripts");
+ for (char **i = dir_file_names; *i != NULL; ++i) {
+ char *file_name = *i;
+
+ if (!strends(file_name, ".um"))
+ continue;
+
+ /* got this one already */
+ if (strcmp(file_name, "main.um") == 0)
+ continue;
+
+ /* need to figure out the actual path (as opposed to the lone file name) */
+ const char *path_prefix = "/scripts/";
+ size_t path_size = snprintf(NULL, 0, "%s%s", path_prefix, file_name) + 1;
+ char *path = cmalloc(path_size);
+ snprintf(path, path_size, "%s%s", path_prefix, file_name);
+
+ char *contents = file_to_str(path);
+ umkaAddModule(ctx->umka, file_name, contents);
+
+ free(path);
+ free(contents);
+ }
+ PHYSFS_freeList(dir_file_names);
+
+ register_api(ctx->umka);
+ if (!umkaCompile(ctx->umka)) {
+ cry_umka(ctx->umka);
+ return false;
+ }
+
+ return true;
+}
+
+void scripting_deinit(t_ctx *ctx) {
+ umkaFree(ctx->umka);
+}
diff --git a/src/scripting.h b/src/scripting.h
new file mode 100644
index 0000000..700ec80
--- /dev/null
+++ b/src/scripting.h
@@ -0,0 +1,18 @@
+#ifndef SCRIPTING_H
+#define SCRIPTING_H
+
+#include
+
+#include
+#include
+
+typedef struct context t_ctx;
+
+struct state {
+ t_ctx *hidden_ptr;
+ uint64_t tick_count;
+};
+
+bool scripting_init(void);
+
+#endif
diff --git a/src/text.c b/src/text.c
new file mode 100644
index 0000000..727a43b
--- /dev/null
+++ b/src/text.c
@@ -0,0 +1,5 @@
+#include "text.h"
+
+#include "SDL_ttf.h"
+
+
diff --git a/src/text.h b/src/text.h
new file mode 100644
index 0000000..2b8f06a
--- /dev/null
+++ b/src/text.h
@@ -0,0 +1,29 @@
+#ifndef TEXT_H
+#define TEXT_H
+
+
+#include "util.h"
+
+#include "SDL.h"
+#include "SDL_ttf.h"
+
+
+struct text {
+ char *text;
+ t_color color;
+ int ptsize;
+};
+
+
+struct text_cache_item {
+ char *key;
+ struct text *value;
+};
+
+
+struct text_cache {
+ struct text_cache_item *hash;
+};
+
+
+#endif
diff --git a/src/textures.c b/src/textures.c
new file mode 100644
index 0000000..ef26380
--- /dev/null
+++ b/src/textures.c
@@ -0,0 +1,357 @@
+#include "textures.h"
+#include "config.h"
+#include "util.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+
+static SDL_Surface *image_to_surface(char *path) {
+ SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
+ if (handle == NULL)
+ goto fail;
+
+ SDL_Surface *result = IMG_Load_RW(handle, true);
+ if (result == NULL)
+ goto fail;
+
+ SDL_SetSurfaceBlendMode(result, SDL_BLENDMODE_NONE);
+ SDL_SetSurfaceRLE(result, true);
+
+ return result;
+
+fail:
+ CRY(path, "Failed to load image. Aborting...");
+ die_abruptly();
+}
+
+
+/* adds a new, blank atlas surface to the cache */
+static void add_new_atlas(struct texture_cache *cache) {
+ SDL_PixelFormat *native_format =
+ SDL_AllocFormat(SDL_GetWindowPixelFormat(cache->window));
+
+ /* the window format won't have an alpha channel, so we figure this out */
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ uint32_t a_mask = 0x000000FF;
+#else
+ uint32_t a_mask = 0xFF000000;
+#endif
+
+ SDL_Surface *new_atlas = SDL_CreateRGBSurface(0,
+ TEXTURE_ATLAS_SIZE,
+ TEXTURE_ATLAS_SIZE,
+ TEXTURE_ATLAS_BIT_DEPTH,
+ native_format->Rmask,
+ native_format->Gmask,
+ native_format->Bmask,
+ a_mask);
+ SDL_FreeFormat(native_format);
+
+ SDL_SetSurfaceRLE(new_atlas, true);
+ arrput(cache->atlas_surfaces, new_atlas);
+
+ SDL_Texture *new_atlas_texture =
+ SDL_CreateTextureFromSurface(cache->renderer, new_atlas);
+ arrput(cache->atlas_textures, new_atlas_texture);
+}
+
+
+static void recreate_current_atlas_texture(struct texture_cache *cache) {
+ /* TODO: figure out if SDL_UpdateTexture alone is faster than blitting */
+ SDL_Surface *atlas_surface = cache->atlas_surfaces[cache->atlas_index];
+
+ /* clear */
+ SDL_FillRect(atlas_surface, NULL, 0);
+
+ /* blit the texture surfaces onto the atlas */
+ for (size_t i = 0; i < shlenu(cache->hash); ++i) {
+ if (cache->hash[i].value.atlas_index != cache->atlas_index)
+ continue;
+
+ SDL_BlitSurface(cache->hash[i].value.data,
+ NULL,
+ atlas_surface,
+ &cache->hash[i].value.srcrect);
+ }
+
+ /* texturize it! */
+ SDL_LockSurface(atlas_surface);
+ SDL_UpdateTexture(cache->atlas_textures[cache->atlas_index],
+ NULL,
+ atlas_surface->pixels,
+ atlas_surface->pitch);
+ SDL_UnlockSurface(atlas_surface);
+}
+
+
+/* uses the textures currently in the cache to create an array of stbrp_rects */
+static stbrp_rect *create_rects_from_cache(struct texture_cache *cache) {
+ stbrp_rect *rects = NULL;
+ for (size_t i = 0; i < shlenu(cache->hash); ++i) {
+ SDL_Surface *surface_data = cache->hash[i].value.data;
+ stbrp_rect new_rect = {
+ .w = surface_data->w,
+ .h = surface_data->h,
+ };
+
+ arrput(rects, new_rect);
+ }
+ return rects;
+}
+
+
+/* returns an array which contains a _copy_ of every unpacked rect in rects. */
+/* each of these copies will have their original index in rects saved in */
+/* their `id` field, which is an int. */
+static stbrp_rect *filter_unpacked_rects(stbrp_rect *rects) {
+ stbrp_rect *unpacked_rects = NULL;
+ for (size_t i = 0; i < arrlenu(rects); ++i) {
+ /* already packed */
+ if (rects[i].was_packed)
+ continue;
+
+ arrput(unpacked_rects, rects[i]);
+ /* stb_rect_pack mercifully gives you a free userdata int */
+ /* the index is saved there so the original array can be updated later */
+ unpacked_rects[arrlenu(unpacked_rects)-1].id = (int)i;
+ }
+ return unpacked_rects;
+}
+
+
+/* updates the original rects array with the data from packed_rects */
+/* returns true if all rects were packed successfully */
+static bool update_rects(struct texture_cache *cache, stbrp_rect *rects, stbrp_rect *packed_rects) {
+ /* !!! do not grow either of the arrays !!! */
+ /* the reallocation will try to reassign the array pointer, to no effect. */
+ /* see stb_ds.h */
+ bool packed_all = true;
+
+ for (size_t i = 0; i < arrlenu(packed_rects); ++i) {
+ /* we can check if any rects failed to be packed right here */
+ /* it's not ideal, but it avoids another iteration */
+ if (!packed_rects[i].was_packed) {
+ packed_all = false;
+ continue;
+ }
+
+ rects[packed_rects[i].id] = packed_rects[i];
+ /* while the order of the elements in the hash map is unknown to us, */
+ /* their equivalents in `rects` are in that same (unknown) order, which means */
+ /* we can use the index we had saved to find the original texture struct */
+ cache->hash[packed_rects[i].id].value.atlas_index = cache->atlas_index;
+ }
+
+ return packed_all;
+}
+
+
+/* updates the atlas location of every rect in the cache */
+static void update_texture_rects_in_atlas(struct texture_cache *cache, stbrp_rect *rects) {
+ for (size_t i = 0; i < arrlenu(rects); ++i) {
+ cache->hash[i].value.srcrect = (SDL_Rect) {
+ .x = rects[i].x,
+ .y = rects[i].y,
+ .w = rects[i].w,
+ .h = rects[i].h,
+ };
+ }
+}
+
+
+void textures_cache_init(struct texture_cache *cache, SDL_Window *window) {
+ cache->window = window;
+ cache->renderer = SDL_GetRenderer(window);
+ sh_new_arena(cache->hash);
+ sh_new_arena(cache->loner_hash);
+
+ cache->node_buffer = cmalloc(sizeof *cache->node_buffer * TEXTURE_ATLAS_SIZE);
+
+ add_new_atlas(cache);
+ recreate_current_atlas_texture(cache);
+}
+
+
+void textures_cache_deinit(struct texture_cache *cache) {
+ /* free atlas textures */
+ for (size_t i = 0; i < arrlenu(cache->atlas_textures); ++i) {
+ SDL_DestroyTexture(cache->atlas_textures[i]);
+ }
+ arrfree(cache->atlas_textures);
+
+ /* free atlas surfaces */
+ for (size_t i = 0; i < arrlenu(cache->atlas_surfaces); ++i) {
+ SDL_FreeSurface(cache->atlas_surfaces[i]);
+ }
+ arrfree(cache->atlas_surfaces);
+
+ /* free cache hashes */
+ for (size_t i = 0; i < shlenu(cache->hash); ++i) {
+ SDL_FreeSurface(cache->hash[i].value.data);
+ }
+ shfree(cache->hash);
+
+ for (size_t i = 0; i < shlenu(cache->loner_hash); ++i) {
+ SDL_FreeSurface(cache->loner_hash[i].value.data);
+ }
+ shfree(cache->loner_hash);
+
+ free(cache->node_buffer);
+}
+
+
+void textures_dump_atlases(struct texture_cache *cache) {
+ PHYSFS_mkdir("/dump");
+
+ const char string_template[] = "/dump/atlas%zd.png";
+ char buf[2048]; /* larger than will ever be necessary */
+
+ size_t i = 0;
+ for (; i < arrlenu(cache->atlas_surfaces); ++i) {
+ snprintf(buf, sizeof buf, string_template, i);
+
+ SDL_RWops *handle = PHYSFSRWOPS_openWrite(buf);
+
+ if (handle == NULL) {
+ CRY("Texture atlas dump failed.", "File could not be opened");
+ return;
+ }
+
+ IMG_SavePNG_RW(cache->atlas_surfaces[i], handle, true);
+ log_info("Dumped atlas %s", buf);
+ }
+
+ size_t num_loners = shlenu(cache->loner_hash);
+ log_info("%zd atlases dumped. %zd loners left undumped.", i, num_loners);
+}
+
+
+void textures_load(struct texture_cache *cache, char *path) {
+ /* no need to do anything if it was loaded already */
+ if (shgeti(cache->hash, path) >= 0 || shgeti(cache->loner_hash, path) >= 0)
+ return;
+
+ SDL_Surface *surface = image_to_surface(path);
+ struct texture new_texture;
+ new_texture.data = surface;
+
+ /* it's a "loner texture," it doesn't fit in an atlas so it's not in one */
+ if (surface->w > TEXTURE_ATLAS_SIZE || surface->h > TEXTURE_ATLAS_SIZE) {
+ new_texture.loner_data = SDL_CreateTextureFromSurface(cache->renderer, surface);
+ new_texture.atlas_index = -1;
+ shput(cache->loner_hash, path, new_texture);
+ } else {
+ new_texture.atlas_index = cache->atlas_index;
+ shput(cache->hash, path, new_texture);
+ cache->is_dirty = true;
+ }
+}
+
+
+void textures_update_current_atlas(struct texture_cache *cache) {
+ /* this function makes a lot more sense if you read stb_rect_pack.h */
+ stbrp_context pack_ctx; /* target info */
+ stbrp_init_target(&pack_ctx,
+ TEXTURE_ATLAS_SIZE,
+ TEXTURE_ATLAS_SIZE,
+ cache->node_buffer,
+ TEXTURE_ATLAS_SIZE);
+
+ stbrp_rect *rects = create_rects_from_cache(cache);
+
+ /* we have to keep packing, and creating atlases if necessary, */
+ /* until all rects have been packed. */
+ /* ideally, this will not iterate more than once. */
+ bool textures_remaining = true;
+ while (textures_remaining) {
+ stbrp_rect *rects_to_pack = filter_unpacked_rects(rects);
+ stbrp_pack_rects(&pack_ctx, rects_to_pack, (int)arrlen(rects_to_pack));
+
+ textures_remaining = !update_rects(cache, rects, rects_to_pack);
+ arrfree(rects_to_pack); /* got what we needed */
+
+ /* some textures couldn't be packed */
+ if (textures_remaining) {
+ update_texture_rects_in_atlas(cache, rects);
+ recreate_current_atlas_texture(cache);
+
+ /* need a new atlas for next time */
+ add_new_atlas(cache);
+ ++cache->atlas_index;
+ }
+ };
+
+ update_texture_rects_in_atlas(cache, rects);
+ recreate_current_atlas_texture(cache);
+
+ cache->is_dirty = false;
+
+ arrfree(rects);
+}
+
+
+SDL_Rect textures_get_srcrect(struct texture_cache *cache, char *path) {
+ struct texture_cache_item *texture = shgetp_null(cache->hash, path);
+ if (texture == NULL) {
+ CRY("Texture lookup failed.",
+ "Tried to get texture that isn't loaded.");
+ return (SDL_Rect){ 0, 0, 0, 0 };
+ }
+ return texture->value.srcrect;
+}
+
+
+int textures_get_atlas_index(struct texture_cache *cache, char *path) {
+ struct texture_cache_item *texture = shgetp_null(cache->hash, path);
+
+ /* it might be a loner texture */
+ if (texture == NULL) {
+ texture = shgetp_null(cache->loner_hash, path);
+
+ /* never mind it's just not there at all */
+ if (texture == NULL) {
+ CRY("Texture atlas index lookup failed.",
+ "Tried to get atlas index of texture that isn't loaded.");
+ return INT_MIN;
+ }
+ }
+
+ return texture->value.atlas_index;
+}
+
+
+SDL_Texture *textures_get_atlas(struct texture_cache *cache, int index) {
+ /* out of bounds */
+ if (arrlen(cache->atlas_textures) < index + 1 || index < 0)
+ return NULL;
+
+ return cache->atlas_textures[index];
+}
+
+
+SDL_Texture *textures_get_loner(struct texture_cache *cache, char *path) {
+ struct texture_cache_item *texture = shgetp_null(cache->loner_hash, path);
+
+ if (texture == NULL) {
+ CRY("Loner texture lookup failed.",
+ "Tried to get texture that isn't loaded.");
+ return NULL;
+ }
+
+ return texture->value.loner_data;
+}
+
+
+size_t textures_get_num_atlases(struct texture_cache *cache) {
+ return cache->atlas_index + 1;
+}
diff --git a/src/textures.h b/src/textures.h
new file mode 100644
index 0000000..bff3cbb
--- /dev/null
+++ b/src/textures.h
@@ -0,0 +1,78 @@
+#ifndef TEXTURES_H
+#define TEXTURES_H
+
+
+#include
+#include
+
+#include
+
+
+struct texture {
+ SDL_Rect srcrect; /* position in atlas */
+ SDL_Surface *data; /* original image data */
+ SDL_Texture *loner_data; /* loner textures store their data directly */
+ int atlas_index; /* which atlas the texture is in */
+ int8_t layer;
+};
+
+
+struct texture_cache_item {
+ char *key;
+ struct texture value;
+};
+
+
+/* use the public API to create and manipulate instances of this structure */
+struct texture_cache {
+ /* from context */
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+
+ struct texture_cache_item *hash;
+ struct texture_cache_item *loner_hash;
+
+ stbrp_node *node_buffer; /* used internally by stb_rect_pack */
+
+ SDL_Surface **atlas_surfaces;
+ SDL_Texture **atlas_textures;
+ int atlas_index;
+
+ bool is_dirty; /* current atlas needs to be recreated */
+};
+
+
+void textures_cache_init(struct texture_cache *cache, SDL_Window *window);
+void textures_cache_deinit(struct texture_cache *cache);
+
+/* for debugging */
+void textures_dump_atlases(struct texture_cache *cache);
+
+/* loads an image if it isn't in the cache, otherwise a no-op. */
+/* can be called from anywhere at any time after init, useful if you want to */
+/* preload textures you know will definitely be used */
+void textures_load(struct texture_cache *cache, char *path);
+
+/* repacks the current texture atlas based on the texture cache */
+void textures_update_current_atlas(struct texture_cache *cache);
+
+/* returns a rect in a texture cache atlas based on a path, for drawing */
+/* if the texture is not found, returns a zero-filled rect (so check w or h) */
+SDL_Rect textures_get_srcrect(struct texture_cache *cache, char *path);
+
+/* returns which atlas the texture in the path is in, starting from 0 */
+/* if the texture is not found, returns INT_MIN */
+int textures_get_atlas_index(struct texture_cache *cache, char *path);
+
+/* returns a pointer to the atlas at `index` */
+/* if the index is out of bounds, returns NULL. */
+/* you can get the index via texture_get_atlas_index */
+SDL_Texture *textures_get_atlas(struct texture_cache *cache, int index);
+
+SDL_Texture *textures_get_loner(struct texture_cache *cache, char *path);
+
+/* returns the number of atlases in the cache */
+size_t textures_get_num_atlases(struct texture_cache *cache);
+
+
+#endif
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..6509132
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,191 @@
+#include "util.h"
+
+#include
+#include
+#define STB_DS_IMPLEMENTATION
+#define STBDS_ASSERT SDL_assert
+#define STBDS_REALLOC(c,p,s) crealloc(p, s)
+#define STBDS_FREE(c,p) free(p)
+#include
+#define STB_RECT_PACK_IMPLEMENTATION
+#define STBRP_ASSERT SDL_assert
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+
+void cry_impl(const char *file, const int line, const char *title, const char *text) {
+ SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION,
+ "TEARS AT %s:%d: %s ... %s", file, line, title, text);
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, text, NULL);
+}
+
+
+static void log_impl(const char *restrict format, va_list args, SDL_LogPriority priority) {
+ SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION,
+ priority,
+ format,
+ args);
+}
+
+
+void log_info(const char *restrict format, ...) {
+ va_list args;
+ va_start(args, format);
+ log_impl(format, args, SDL_LOG_PRIORITY_INFO);
+ va_end(args);
+}
+
+
+void log_critical(const char *restrict format, ...) {
+ va_list args;
+ va_start(args, format);
+ log_impl(format, args, SDL_LOG_PRIORITY_CRITICAL);
+ va_end(args);
+}
+
+
+void log_warn(const char *restrict format, ...) {
+ va_list args;
+ va_start(args, format);
+ log_impl(format, args, SDL_LOG_PRIORITY_WARN);
+ va_end(args);
+}
+
+
+noreturn static void alloc_failure_death(void) {
+ log_critical("Allocation failure. Aborting NOW.");
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
+ "MEMORY ALLOCATION FAILURE.",
+ "FAILED TO ALLOCATE MEMORY. "
+ "YOU MIGHT BE UNLUCKY. "
+ "THE GAME WILL EXIT NOW.",
+ NULL);
+
+ die_abruptly();
+}
+
+
+noreturn void die_abruptly(void) {
+ /* a zombie window will linger if we don't at least try to quit SDL */
+ SDL_Quit();
+ abort();
+}
+
+
+void *cmalloc(size_t size) {
+ void *ptr = malloc(size);
+ if (ptr == NULL)
+ alloc_failure_death();
+ return ptr;
+}
+
+
+void *crealloc(void *ptr, size_t size) {
+ void *out = realloc(ptr, size);
+ if (out == NULL)
+ alloc_failure_death();
+ return out;
+}
+
+
+void *ccalloc(size_t num, size_t size) {
+ void *ptr = calloc(num, size);
+ if (ptr == NULL)
+ alloc_failure_death();
+ return ptr;
+}
+
+
+int64_t file_to_bytes(char *path, unsigned char **buf_out) {
+ SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
+
+ if (handle == NULL) {
+ return -1;
+ }
+
+ int64_t data_size = SDL_RWseek(handle, 0, RW_SEEK_END);
+ SDL_RWseek(handle, 0, RW_SEEK_SET); /* reset offset into data */
+
+ *buf_out = cmalloc(data_size);
+ SDL_RWread(handle, *buf_out, sizeof **buf_out, data_size / sizeof **buf_out);
+ SDL_RWclose(handle); /* we got all we needed from the stream */
+
+ return data_size;
+}
+
+
+char *file_to_str(char *path) {
+ SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
+
+ if (handle == NULL) {
+ return NULL;
+ }
+
+ int64_t data_size = SDL_RWseek(handle, 0, RW_SEEK_END);
+ SDL_RWseek(handle, 0, RW_SEEK_SET); /* reset offset into data */
+
+ char *str_out = cmalloc(data_size + 1); /* data plus final null */
+ size_t len = data_size / sizeof *str_out;
+
+ SDL_RWread(handle, str_out, sizeof *str_out, len);
+ SDL_RWclose(handle); /* we got all we needed from the stream */
+
+ str_out[len] = '\0';
+
+ return str_out;
+}
+
+
+bool strends(const char *str, const char *suffix) {
+ size_t str_length = strlen(str);
+ size_t suffix_length = strlen(suffix);
+
+ if (suffix_length > str_length)
+ return false;
+
+ return memcmp((str + str_length) - suffix_length, suffix, suffix_length) == 0;
+}
+
+
+bool intersect_rect(const t_rect *a, const t_rect *b, t_rect *result) {
+ SDL_Rect a_sdl = { a->x, a->y, a->w, a->h };
+ SDL_Rect b_sdl = { b->x, b->y, b->w, b->h };
+ SDL_Rect result_sdl = { 0 };
+
+ bool intersection = SDL_IntersectRect(&a_sdl, &b_sdl, &result_sdl);
+
+ *result = (t_rect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
+ return intersection;
+}
+
+
+bool intersect_frect(const t_frect *a, const t_frect *b, t_frect *result) {
+ SDL_FRect a_sdl = { a->x, a->y, a->w, a->h };
+ SDL_FRect b_sdl = { b->x, b->y, b->w, b->h };
+ SDL_FRect result_sdl = { 0 };
+
+ bool intersection = SDL_IntersectFRect(&a_sdl, &b_sdl, &result_sdl);
+
+ *result = (t_frect){ result_sdl.x, result_sdl.y, result_sdl.w, result_sdl.h };
+ return intersection;
+}
+
+
+t_frect to_frect(t_rect rect) {
+ return (t_frect) {
+ .h = (float)rect.h,
+ .w = (float)rect.w,
+ .x = (float)rect.x,
+ .y = (float)rect.y,
+ };
+}
+
+
+void tick_timer(int *value) {
+ *value = MAX(*value - 1, 0);
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..c6bd2e1
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,120 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+
+/* */
+/* GENERAL UTILITIES */
+/* */
+
+void cry_impl(const char *file, const int line, const char *title, const char *text);
+#define CRY(title, text) cry_impl(__FILE__, __LINE__, title, text)
+#define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError())
+#define CRY_PHYSFS(title) \
+cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))
+
+
+void log_info(const char *restrict format, ...);
+void log_critical(const char *restrict format, ...);
+void log_warn(const char *restrict format, ...);
+
+
+/* for when there's absolutely no way to continue */
+noreturn void die_abruptly(void);
+
+
+/* "critical" allocation functions which will log and abort() on failure. */
+/* if it is reasonable to handle this gracefully, use the standard versions. */
+/* the stb implementations will be configured to use these */
+void *cmalloc(size_t size);
+void *crealloc(void *ptr, size_t size);
+void *ccalloc(size_t num, size_t size);
+
+
+/* DON'T FORGET ABOUT DOUBLE EVALUATION */
+/* YOU THINK YOU WON'T, AND THEN YOU DO */
+/* C23's typeof could fix it, so i will */
+/* leave them as macros to be replaced. */
+/* use the macro in tgmath.h for floats */
+#define MAX SDL_max
+#define MIN SDL_min
+
+
+/* sets buf_out to a pointer to a byte buffer which must be freed. */
+/* returns the size of this buffer. */
+int64_t file_to_bytes(char *path, unsigned char **buf_out);
+
+/* returns a pointer to a string which must be freed */
+char *file_to_str(char *path);
+
+
+/* returns true if str ends with suffix */
+bool strends(const char *str, const char *suffix);
+
+
+/* */
+/* GAME LOGIC UTILITIES */
+/* */
+
+/* 32-bit color data */
+typedef struct color {
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+ uint8_t a;
+} t_color;
+
+
+/* a rectangle with the origin at the upper left (integer) */
+typedef struct rect {
+ int x, y;
+ int w, h;
+} t_rect;
+
+
+bool intersect_rect(const t_rect *a, const t_rect *b, t_rect *result);
+
+
+/* a rectangle with the origin at the upper left (floating point) */
+typedef struct frect {
+ float x, y;
+ float w, h;
+} t_frect;
+
+
+bool intersect_frect(const t_frect *a, const t_frect *b, t_frect *result);
+
+t_frect to_frect(t_rect rect);
+
+
+/* a point in some space (integer) */
+typedef struct vec2 {
+ int x, y;
+} t_vec2;
+
+
+/* a point in some space (floating point) */
+typedef struct fvec2 {
+ float x, y;
+} t_fvec2;
+
+
+/* decrements an lvalue (which should be an int), stopping at 0 */
+/* meant for tick-based timers in game logic */
+/*
+ * example:
+ * tick_timer(&player->jump_air_timer);
+ */
+void tick_timer(int *value);
+
+
+#endif
diff --git a/third-party/README.md b/third-party/README.md
new file mode 100644
index 0000000..2ba1d2d
--- /dev/null
+++ b/third-party/README.md
@@ -0,0 +1,3 @@
+libraries that don't make sense to get from the system or Conan.
+
+avoid modifying the source if possible, it makes everything a mess later
\ No newline at end of file
diff --git a/third-party/physfs/.github/FUNDING.yml b/third-party/physfs/.github/FUNDING.yml
new file mode 100644
index 0000000..5cf911b
--- /dev/null
+++ b/third-party/physfs/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: [icculus]
+patreon: icculus
diff --git a/third-party/physfs/.github/workflows/main.yml b/third-party/physfs/.github/workflows/main.yml
new file mode 100644
index 0000000..15b9cfd
--- /dev/null
+++ b/third-party/physfs/.github/workflows/main.yml
@@ -0,0 +1,30 @@
+name: Build
+
+on: [push, pull_request]
+
+jobs:
+ Build:
+ name: ${{ matrix.platform.name }}
+ runs-on: ${{ matrix.platform.os }}
+ strategy:
+ matrix:
+ platform: # !!! FIXME: figure out an efficient way to get SDL2 on the Windows/Mac bots.
+ - { name: Linux, os: ubuntu-20.04, flags: -GNinja }
+ - { name: MinGW, os: windows-latest, flags: -GNinja -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_SYSTEM_NAME=Windows }
+ - { name: Windows, os: windows-latest }
+ - { name: MacOS, os: macos-latest }
+ steps:
+ - name: Setup Linux dependencies
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update
+ sudo apt-get install ninja-build
+ - name: Setup MinGW dependencies
+ if: contains(matrix.platform.name, 'MinGW')
+ run: choco install ninja
+ - name: Get PhysicsFS sources
+ uses: actions/checkout@v2
+ - name: Configure CMake
+ run: cmake -B build ${{ matrix.platform.flags }}
+ - name: Build
+ run: cmake --build build/
diff --git a/third-party/physfs/.github/workflows/os2.yml b/third-party/physfs/.github/workflows/os2.yml
new file mode 100644
index 0000000..c948576
--- /dev/null
+++ b/third-party/physfs/.github/workflows/os2.yml
@@ -0,0 +1,20 @@
+name: Build (OS/2)
+
+on: [push, pull_request]
+
+jobs:
+ os2:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: open-watcom/setup-watcom@v0
+ - name: Build physfs.dll
+ run: |
+ cd src
+ wmake -f Makefile.os2
+ cd ..
+ - name: distclean
+ run: |
+ cd src
+ wmake -f Makefile.os2 distclean
+ cd ..
diff --git a/third-party/physfs/.gitignore b/third-party/physfs/.gitignore
new file mode 100644
index 0000000..7878ec0
--- /dev/null
+++ b/third-party/physfs/.gitignore
@@ -0,0 +1,2 @@
+cmake-build
+
diff --git a/third-party/physfs/CMakeLists.txt b/third-party/physfs/CMakeLists.txt
new file mode 100644
index 0000000..b3291cc
--- /dev/null
+++ b/third-party/physfs/CMakeLists.txt
@@ -0,0 +1,345 @@
+# PhysicsFS; a portable, flexible file i/o abstraction.
+#
+# Please see the file LICENSE.txt in the source's root directory.
+
+# The CMake project file is meant to get this compiling on all sorts of
+# platforms quickly, and serve as the way Unix platforms and Linux distros
+# package up official builds, but you don't _need_ to use this; we have
+# built PhysicsFS to (hopefully) be able to drop into your project and
+# compile, using preprocessor checks for platform-specific bits instead of
+# testing in here.
+
+set(PHYSFS_VERSION 3.2.0)
+
+cmake_minimum_required(VERSION 3.0)
+
+project(PhysicsFS VERSION ${PHYSFS_VERSION} LANGUAGES C )
+
+include(GNUInstallDirs)
+
+# Increment this if/when we break backwards compatibility.
+set(PHYSFS_SOVERSION 1)
+
+set(PHYSFS_M_SRCS)
+set(PHYSFS_CPP_SRCS)
+
+# I hate that they define "WIN32" ... we're about to move to Win64...I hope!
+
+if(APPLE)
+ set(OTHER_LDFLAGS ${OTHER_LDFLAGS} "-framework IOKit -framework Foundation")
+ list(APPEND PHYSFS_M_SRCS src/physfs_platform_apple.m)
+endif()
+
+if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")
+ add_compile_options(-Wall)
+ # Don't use -rpath.
+ set(CMAKE_SKIP_RPATH ON CACHE BOOL "Skip RPATH" FORCE)
+endif()
+
+if(CMAKE_C_COMPILER_ID STREQUAL "SunPro")
+ add_definitions(-erroff=E_EMPTY_TRANSLATION_UNIT)
+ add_definitions(-xldscope=hidden)
+endif()
+
+if(HAIKU)
+ # We add this explicitly, since we don't want CMake to think this
+ # is a C++ project unless we're on Haiku.
+ list(APPEND PHYSFS_CPP_SRCS src/physfs_platform_haiku.cpp)
+ find_library(BE_LIBRARY be)
+ find_library(ROOT_LIBRARY root)
+ list(APPEND OPTIONAL_LIBRARY_LIBS ${BE_LIBRARY} ${ROOT_LIBRARY})
+endif()
+
+if(CMAKE_SYSTEM_NAME STREQUAL "WindowsPhone" OR CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
+ set(WINRT TRUE)
+endif()
+
+if(WINRT)
+ list(APPEND PHYSFS_CPP_SRCS src/physfs_platform_winrt.cpp)
+endif()
+
+if(UNIX AND NOT WIN32 AND NOT APPLE) # (MingW and such might be UNIX _and_ WINDOWS!)
+ find_library(PTHREAD_LIBRARY pthread)
+ if(PTHREAD_LIBRARY)
+ set(OPTIONAL_LIBRARY_LIBS ${OPTIONAL_LIBRARY_LIBS} ${PTHREAD_LIBRARY})
+ endif()
+endif()
+
+if(PHYSFS_CPP_SRCS)
+ enable_language(CXX)
+endif()
+
+# Almost everything is "compiled" here, but things that don't apply to the
+# build are #ifdef'd out. This is to make it easy to embed PhysicsFS into
+# another project or bring up a new build system: just compile all the source
+# code and #define the things you want.
+set(PHYSFS_SRCS
+ src/physfs.c
+ src/physfs_byteorder.c
+ src/physfs_unicode.c
+ src/physfs_platform_posix.c
+ src/physfs_platform_unix.c
+ src/physfs_platform_windows.c
+ src/physfs_platform_os2.c
+ src/physfs_platform_qnx.c
+ src/physfs_platform_android.c
+ src/physfs_archiver_dir.c
+ src/physfs_archiver_unpacked.c
+ src/physfs_archiver_grp.c
+ src/physfs_archiver_hog.c
+ src/physfs_archiver_7z.c
+ src/physfs_archiver_mvl.c
+ src/physfs_archiver_qpak.c
+ src/physfs_archiver_wad.c
+ src/physfs_archiver_zip.c
+ src/physfs_archiver_slb.c
+ src/physfs_archiver_iso9660.c
+ src/physfs_archiver_vdf.c
+ ${PHYSFS_CPP_SRCS}
+ ${PHYSFS_M_SRCS}
+)
+
+
+# Archivers ...
+# These are (mostly) on by default now, so these options are only useful for
+# disabling them.
+
+option(PHYSFS_ARCHIVE_ZIP "Enable ZIP support" TRUE)
+if(NOT PHYSFS_ARCHIVE_ZIP)
+ add_definitions(-DPHYSFS_SUPPORTS_ZIP=0)
+endif()
+
+option(PHYSFS_ARCHIVE_7Z "Enable 7zip support" TRUE)
+if(NOT PHYSFS_ARCHIVE_7Z)
+ add_definitions(-DPHYSFS_SUPPORTS_7Z=0)
+endif()
+
+option(PHYSFS_ARCHIVE_GRP "Enable Build Engine GRP support" TRUE)
+if(NOT PHYSFS_ARCHIVE_GRP)
+ add_definitions(-DPHYSFS_SUPPORTS_GRP=0)
+endif()
+
+option(PHYSFS_ARCHIVE_WAD "Enable Doom WAD support" TRUE)
+if(NOT PHYSFS_ARCHIVE_WAD)
+ add_definitions(-DPHYSFS_SUPPORTS_WAD=0)
+endif()
+
+option(PHYSFS_ARCHIVE_HOG "Enable Descent I/II HOG support" TRUE)
+if(NOT PHYSFS_ARCHIVE_HOG)
+ add_definitions(-DPHYSFS_SUPPORTS_HOG=0)
+endif()
+
+option(PHYSFS_ARCHIVE_MVL "Enable Descent I/II MVL support" TRUE)
+if(NOT PHYSFS_ARCHIVE_MVL)
+ add_definitions(-DPHYSFS_SUPPORTS_MVL=0)
+endif()
+
+option(PHYSFS_ARCHIVE_QPAK "Enable Quake I/II QPAK support" TRUE)
+if(NOT PHYSFS_ARCHIVE_QPAK)
+ add_definitions(-DPHYSFS_SUPPORTS_QPAK=0)
+endif()
+
+option(PHYSFS_ARCHIVE_SLB "Enable I-War / Independence War SLB support" TRUE)
+if(NOT PHYSFS_ARCHIVE_SLB)
+ add_definitions(-DPHYSFS_SUPPORTS_SLB=0)
+endif()
+
+option(PHYSFS_ARCHIVE_ISO9660 "Enable ISO9660 support" TRUE)
+if(NOT PHYSFS_ARCHIVE_ISO9660)
+ add_definitions(-DPHYSFS_SUPPORTS_ISO9660=0)
+endif()
+
+option(PHYSFS_ARCHIVE_VDF "Enable Gothic I/II VDF archive support" TRUE)
+if(NOT PHYSFS_ARCHIVE_VDF)
+ add_definitions(-DPHYSFS_SUPPORTS_VDF=0)
+endif()
+
+
+option(PHYSFS_BUILD_STATIC "Build static library" TRUE)
+if(PHYSFS_BUILD_STATIC)
+ add_library(physfs-static STATIC ${PHYSFS_SRCS})
+ add_library(PhysFS::PhysFS-static ALIAS physfs-static)
+ set_target_properties(physfs-static PROPERTIES EXPORT_NAME PhysFS-static)
+ # Don't rename this on Windows, since DLLs will also produce an import
+ # library named "physfs.lib" which would conflict; Unix tend to like the
+ # same library name with a different extension for static libs, but
+ # Windows can just have a separate name.
+ if(NOT MSVC)
+ set_target_properties(physfs-static PROPERTIES OUTPUT_NAME "physfs")
+ endif()
+ if(WINRT)
+ # Ignore LNK4264 warnings; we don't author any WinRT components, just consume them, so we're okay in a static library.
+ set_target_properties(physfs-static PROPERTIES VS_WINRT_COMPONENT True)
+ set_target_properties(physfs-static PROPERTIES STATIC_LIBRARY_FLAGS "/ignore:4264")
+ endif()
+ if(WIN32 OR WINRT OR OS2)
+ # no dll exports from the static library
+ target_compile_definitions(physfs-static PRIVATE "PHYSFS_STATIC")
+ endif()
+ target_include_directories(physfs-static PUBLIC "$")
+ target_link_libraries(physfs-static PRIVATE ${OPTIONAL_LIBRARY_LIBS} ${OTHER_LDFLAGS})
+ set(PHYSFS_LIB_TARGET physfs-static)
+ list(APPEND PHYSFS_INSTALL_TARGETS "physfs-static")
+endif()
+
+option(PHYSFS_BUILD_SHARED "Build shared library" TRUE)
+if(PHYSFS_BUILD_SHARED)
+ add_library(physfs SHARED ${PHYSFS_SRCS})
+ add_library(PhysFS::PhysFS ALIAS physfs)
+ set_target_properties(physfs PROPERTIES MACOSX_RPATH 1)
+ set_target_properties(physfs PROPERTIES VERSION ${PHYSFS_VERSION})
+ set_target_properties(physfs PROPERTIES SOVERSION ${PHYSFS_SOVERSION})
+ set_target_properties(physfs PROPERTIES EXPORT_NAME PhysFS)
+ if(WINRT)
+ set_target_properties(physfs PROPERTIES VS_WINRT_COMPONENT True)
+ endif()
+ if(OS2) # OS/2 does not support a DLL name longer than 8 characters.
+ set_target_properties(physfs PROPERTIES OUTPUT_NAME "physfs")
+ endif()
+ target_include_directories(physfs PUBLIC "$")
+ target_link_libraries(physfs PRIVATE ${OPTIONAL_LIBRARY_LIBS} ${OTHER_LDFLAGS})
+ set(PHYSFS_LIB_TARGET physfs)
+ list(APPEND PHYSFS_INSTALL_TARGETS "physfs")
+endif()
+
+if(NOT PHYSFS_BUILD_SHARED AND NOT PHYSFS_BUILD_STATIC)
+ message(FATAL "Both shared and static libraries are disabled!")
+endif()
+
+# CMake FAQ says I need this...
+if(PHYSFS_BUILD_SHARED AND PHYSFS_BUILD_STATIC AND NOT WIN32)
+ set_target_properties(physfs PROPERTIES CLEAN_DIRECT_OUTPUT 1)
+ set_target_properties(physfs-static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
+endif()
+
+option(PHYSFS_BUILD_TEST "Build stdio test program." TRUE)
+mark_as_advanced(PHYSFS_BUILD_TEST)
+if(PHYSFS_BUILD_TEST)
+ find_path(READLINE_H readline/readline.h)
+ find_path(HISTORY_H readline/history.h)
+ if(READLINE_H AND HISTORY_H)
+ find_library(CURSES_LIBRARY NAMES curses ncurses)
+ if(CURSES_LIBRARY)
+ set(CMAKE_REQUIRED_LIBRARIES ${CURSES_LIBRARY})
+ find_library(READLINE_LIBRARY readline)
+ if(READLINE_LIBRARY)
+ set(HAVE_SYSTEM_READLINE TRUE)
+ list(APPEND TEST_PHYSFS_LIBS ${READLINE_LIBRARY} ${CURSES_LIBRARY})
+ include_directories(SYSTEM ${READLINE_H} ${HISTORY_H})
+ add_definitions(-DPHYSFS_HAVE_READLINE=1)
+ endif()
+ endif()
+ endif()
+ add_executable(test_physfs test/test_physfs.c)
+ target_link_libraries(test_physfs PRIVATE ${PHYSFS_LIB_TARGET} ${TEST_PHYSFS_LIBS} ${OTHER_LDFLAGS})
+ list(APPEND PHYSFS_INSTALL_TARGETS test_physfs)
+endif()
+
+option(PHYSFS_DISABLE_INSTALL "Disable installing PhysFS" OFF)
+if(NOT PHYSFS_DISABLE_INSTALL)
+
+ install(TARGETS ${PHYSFS_INSTALL_TARGETS} EXPORT PhysFSExport
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+ install(FILES src/physfs.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+
+ install(EXPORT PhysFSExport
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/PhysFS"
+ FILE PhysFSConfig.cmake
+ NAMESPACE PhysFS::
+ )
+
+ if(NOT MSVC)
+ configure_file(
+ "extras/physfs.pc.in"
+ "extras/physfs.pc"
+ @ONLY
+ )
+
+ install(
+ FILES "${CMAKE_CURRENT_BINARY_DIR}/extras/physfs.pc"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
+ )
+ endif()
+endif()
+
+option(PHYSFS_BUILD_DOCS "Build doxygen based documentation" TRUE)
+if(PHYSFS_BUILD_DOCS)
+ find_package(Doxygen)
+ if(DOXYGEN_FOUND)
+ set(PHYSFS_OUTPUT_DOXYFILE "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile")
+ configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile"
+ "${PHYSFS_OUTPUT_DOXYFILE}"
+ COPYONLY
+ )
+ file(APPEND "${PHYSFS_OUTPUT_DOXYFILE}" "\n\n# Below auto-generated by cmake...\n\n")
+ file(APPEND "${PHYSFS_OUTPUT_DOXYFILE}" "PROJECT_NUMBER = \"${PHYSFS_VERSION}\"\n")
+ file(APPEND "${PHYSFS_OUTPUT_DOXYFILE}" "OUTPUT_DIRECTORY = \"${CMAKE_CURRENT_BINARY_DIR}/docs\"\n")
+ file(APPEND "${PHYSFS_OUTPUT_DOXYFILE}" "\n# End auto-generated section.\n\n")
+
+ set(PHYSFS_TARGETNAME_DOCS "docs" CACHE STRING "Name of 'docs' build target")
+
+ add_custom_target(
+ ${PHYSFS_TARGETNAME_DOCS}
+ ${DOXYGEN_EXECUTABLE} "${PHYSFS_OUTPUT_DOXYFILE}"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ COMMENT "Building documentation in 'docs' directory..."
+ )
+
+ else()
+ message(STATUS "Doxygen not found. You won't be able to build documentation.")
+ endif()
+endif()
+
+if(UNIX)
+ set(PHYSFS_TARBALL "${CMAKE_CURRENT_SOURCE_DIR}/../physfs-${PHYSFS_VERSION}.tar.gz")
+
+ set(PHYSFS_TARGETNAME_DIST "dist" CACHE STRING "Name of 'dist' build target")
+ add_custom_target(
+ ${PHYSFS_TARGETNAME_DIST}
+ git archive --prefix="physfs-${PHYSFS_VERSION}/" --output="${PHYSFS_TARBALL}" HEAD
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ COMMENT "Building source tarball '${PHYSFS_TARBALL}'..."
+ )
+
+ set(PHYSFS_TARGETNAME_UNINSTALL "uninstall" CACHE STRING "Name of 'uninstall' build target")
+ add_custom_target(
+ ${PHYSFS_TARGETNAME_UNINSTALL}
+ "${CMAKE_CURRENT_SOURCE_DIR}/extras/uninstall.sh"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
+ COMMENT "Uninstall the project..."
+ )
+endif()
+
+
+macro(message_bool_option _NAME _VALUE)
+ if(${_VALUE})
+ message(STATUS " ${_NAME}: enabled")
+ else()
+ message(STATUS " ${_NAME}: disabled")
+ endif()
+endmacro()
+
+message(STATUS "PhysicsFS will build with the following options:")
+message_bool_option("ZIP support" PHYSFS_ARCHIVE_ZIP)
+message_bool_option("7zip support" PHYSFS_ARCHIVE_7Z)
+message_bool_option("GRP support" PHYSFS_ARCHIVE_GRP)
+message_bool_option("WAD support" PHYSFS_ARCHIVE_WAD)
+message_bool_option("HOG support" PHYSFS_ARCHIVE_HOG)
+message_bool_option("MVL support" PHYSFS_ARCHIVE_MVL)
+message_bool_option("QPAK support" PHYSFS_ARCHIVE_QPAK)
+message_bool_option("SLB support" PHYSFS_ARCHIVE_SLB)
+message_bool_option("VDF support" PHYSFS_ARCHIVE_VDF)
+message_bool_option("ISO9660 support" PHYSFS_ARCHIVE_ISO9660)
+message_bool_option("Build static library" PHYSFS_BUILD_STATIC)
+message_bool_option("Build shared library" PHYSFS_BUILD_SHARED)
+message_bool_option("Build stdio test program" PHYSFS_BUILD_TEST)
+message_bool_option("Build Doxygen documentation" PHYSFS_BUILD_DOCS)
+if(PHYSFS_BUILD_TEST)
+ message_bool_option(" Use readline in test program" HAVE_SYSTEM_READLINE)
+endif()
+
+# end of CMakeLists.txt ...
diff --git a/third-party/physfs/LICENSE.txt b/third-party/physfs/LICENSE.txt
new file mode 100644
index 0000000..e8d37a1
--- /dev/null
+++ b/third-party/physfs/LICENSE.txt
@@ -0,0 +1,17 @@
+Copyright (c) 2001-2022 Ryan C. Gordon and others.
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
diff --git a/third-party/physfs/README.txt b/third-party/physfs/README.txt
new file mode 100644
index 0000000..2c24a50
--- /dev/null
+++ b/third-party/physfs/README.txt
@@ -0,0 +1,9 @@
+
+PhysicsFS; a portable, flexible file i/o abstraction.
+
+ https://icculus.org/physfs/
+
+Please see the docs directory for documentation.
+
+Please see LICENSE.txt for licensing information.
+
diff --git a/third-party/physfs/docs/CHANGELOG.txt b/third-party/physfs/docs/CHANGELOG.txt
new file mode 100644
index 0000000..7ff7245
--- /dev/null
+++ b/third-party/physfs/docs/CHANGELOG.txt
@@ -0,0 +1,11 @@
+
+The changelog is no longer maintained by hand. It made sense to have a single
+ timeline when we were using CVS, but modern revision control tools make this
+ redundant, at best.
+
+If you want a list of changes, updated in real time, just point your web
+ browser here:
+
+ https://github.com/icculus/physfs/commits/
+
+
diff --git a/third-party/physfs/docs/CREDITS.txt b/third-party/physfs/docs/CREDITS.txt
new file mode 100644
index 0000000..c88c50b
--- /dev/null
+++ b/third-party/physfs/docs/CREDITS.txt
@@ -0,0 +1,174 @@
+Maintainer and general codemonkey:
+ Ryan C. Gordon
+
+Tons of win32 help:
+ Adam Gates
+
+More win32 hacking:
+ Gregory S. Read
+
+Fixes for missing current working directories,
+PHYSFS_setSaneConfig() improvements,
+other bugfixes:
+ David Hedbor
+
+Darwin support:
+ Patrick Stein
+
+configure fixes,
+RPM specfile:
+ Edward Rudd
+
+GetLastModTime API,
+other stuff:
+ John R. Hall
+
+Various support, fixes and suggestions:
+ Alexander Pipelka
+
+Russian translation,
+QPAK archiver:
+ Ed Sinjiashvili
+
+French translation:
+ Stéphane Peter
+
+Debian package support:
+ Colin Bayer
+
+"abs-file.h" in "extras" dir:
+ Adam D. Moss
+
+WinCE port and other Win32 patches:
+ Corona688
+
+German translation:
+ Michael Renner
+
+Apple Project Builder support,
+Mac OS X improvements:
+ Eric Wing
+
+iPhone support:
+ Christian Gmeiner
+
+WinRT support:
+ Martin Ahrnbom
+
+HOG archiver,
+MVL archiver:
+ Bradley Bell
+
+MIX archiver:
+ Sebastian Steinhauer
+
+Bug fixes:
+ Tolga Dalman
+
+Initial PHYSFS_mount() work:
+ Philip D. Bober
+
+Brazillian Portuguese translation:
+ Danny Angelo Carminati Grein
+
+Spanish translation:
+ Pedro J. Pérez
+
+MacOS Classic fixes,
+MPW support,
+bug fixes:
+ Chris Taylor
+
+Mingw support,
+General bug fixes:
+ Matze Braun
+
+Haiku support:
+ scott mc
+
+Bug fixes:
+ Jörg Walter
+
+Bug fixes:
+ Olivier Boudeville
+
+Bug fixes:
+ Henk Boom
+
+Build system fixes:
+ Marc Kleine-Budde
+
+Windows .rc file,
+7zip/lzma archiver:
+ Dennis Schridde
+
+OS/2 updates:
+ Dave Yeo
+
+Bug fixes:
+ Patrice Mandin
+
+PHYSFS_stat() API:
+ Christoph Nelles
+ Indy Sams
+
+ISO9660 archiver:
+ Christoph Nelles
+
+Bug fixes:
+ Steven Fuller
+
+Bug fixes:
+ Tolga Dalman
+
+Bug fixes:
+ Frank Becker
+
+Bug fixes:
+ Norfanin
+
+Bug fixes:
+ Evgeny Podjachev
+
+Haiku fixes:
+ Chris Roberts
+
+SLB archiver:
+ Aleksi Nurmi
+
+Bug fixes:
+ Dmitry Marakasov
+
+Bug fixes:
+ Andreas Karlsson
+
+Bug fixes:
+ Michael Bacon
+
+Bug fixes:
+ Xian Nox
+
+Bug fixes:
+ Reto Schneider
+
+pkg-config support:
+ Jonas Kulla
+
+Bug fixes,
+VDF archiver:
+ Francesco Bertolaccini
+
+CMake fixes:
+ Tobias Markus
+
+Bug fixes,
+ Rémi Verschelde
+
+Bug fixes:
+ Rob Loach
+
+Other stuff:
+ Your name here! Patches go to icculus@icculus.org ...
+
+/* end of CREDITS.txt ... */
+
diff --git a/third-party/physfs/docs/Doxyfile b/third-party/physfs/docs/Doxyfile
new file mode 100644
index 0000000..23202cf
--- /dev/null
+++ b/third-party/physfs/docs/Doxyfile
@@ -0,0 +1,1081 @@
+# Doxyfile 1.3.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = physfs
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+# (CMake will set this properly at build time. --ryan.)
+PROJECT_NUMBER = "unknown version (build with 'make docs' next time!)"
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = docs
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch,
+# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en
+# (Japanese with English messages), Korean, Norwegian, Polish, Portuguese,
+# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# This tag can be used to specify the encoding used in the generated output.
+# The encoding is not always determined by the language that is chosen,
+# but also whether or not the output is meant for Windows or non-Windows users.
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
+# forces the Windows encoding (this is the default for the Windows binary),
+# whereas setting the tag to NO uses a Unix-style encoding (the default for
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited
+# members of a class in the documentation of that class as if those members were
+# ordinary class members. Constructors, destructors and assignment operators of
+# the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. It is allowed to use relative paths in the argument list.
+
+STRIP_FROM_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like the Qt-style comments (thus requiring an
+# explict @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# reimplements.
+
+INHERIT_DOCS = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources
+# only. Doxygen will then generate output that is more tailored for Java.
+# For instance, namespaces will be presented as packages, qualified scopes
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = src/physfs.h
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp
+# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories
+# that are symbolic links (a Unix filesystem feature) are excluded from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+
+EXCLUDE_PATTERNS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command , where
+# is the value of the INPUT_FILTER tag, and is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+
+INPUT_FILTER =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output dir.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimised for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assigments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF = YES
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed.
+
+PREDEFINED = DOXYGEN_SHOULD_IGNORE_THIS=1 \
+ PHYSFS_DECL= \
+ PHYSFS_DEPRECATED=
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse the
+# parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::addtions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or
+# super classes. Setting the tag to NO turns the diagrams off. Note that this
+# option is superceded by the HAVE_DOT option below. This is only a fallback. It is
+# recommended to install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = NO
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = NO
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = NO
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similiar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will
+# generate a call dependency graph for every global function or class method.
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found on the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_WIDTH = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
+# this value, doxygen will try to truncate the graph, so that it fits within
+# the specified constraint. Beware that most browsers cannot cope with very
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes that
+# lay further from the root node will be omitted. Note that setting this option to
+# 1 or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that a graph may be further truncated if the graph's image dimensions are
+# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT).
+# If 0 is used for the depth value (the default), the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::addtions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
diff --git a/third-party/physfs/docs/INSTALL.txt b/third-party/physfs/docs/INSTALL.txt
new file mode 100644
index 0000000..39db64c
--- /dev/null
+++ b/third-party/physfs/docs/INSTALL.txt
@@ -0,0 +1,173 @@
+
+The latest PhysicsFS information and releases can be found at:
+ https://icculus.org/physfs/
+
+Building is (ahem) very easy.
+
+
+ALL PLATFORMS:
+
+Please read the text file LICENSE.txt in the root of the source tree.
+ The license is extremely liberal, even to closed-source, commercial
+ applications.
+
+If you've got Doxygen (http://www.doxygen.org/) installed, you can run it
+ without any command line arguments in the root of the source tree to generate
+ the API reference (or build the "docs" target from your build system). This
+ is optional. You can browse the API docs online here:
+
+ https://icculus.org/physfs/docs/
+
+
+
+BUILD IT WITH YOUR OWN PROGRAM:
+
+If you don't care about formal packaging: just add everything in the "src"
+directory to whatever you use to build your program and compile it along with
+everything else, and you're done. It should compile with any reasonable
+ANSI C compiler, should build cleanly even with excessive compiler warnings
+enabled, needs no extra configuration, and allows static linking.
+WinRT and Haiku need C++ compilers for their system APIs, but if you aren't on
+these platforms and don't have a C++ compiler, don't build the .cpp files.
+Likewise: Apple platforms (macOS, iOS, etc) need an Objective-C compiler, but
+if you aren't on these platforms and don't have a Objective-C compiler, don't
+build the .m file. Everything you need is in the .c sources.
+
+If this all worked for your specific project, you can stop reading now.
+
+
+
+Unix:
+
+You will need CMake (https://www.cmake.org/) 2.4 or later installed.
+
+Make a directory, wherever you like. This will be your build directory.
+
+Chdir to your build directory. Run "cmake /where/i/unpacked/physfs" to
+ generate Makefiles. You can then run "ccmake ." and customize the build,
+ but the defaults are probably okay. You can have CMake generate KDevelop
+ or Ninja project files or whatever, if you prefer these.
+
+Run "make". PhysicsFS will now build.
+
+As root, run "make install".
+ If you get sick of the library, run "make uninstall" as root
+ and it will remove all traces of the library from the system paths.
+
+Once you are satisfied, you can delete the build directory.
+
+Primary Unix development is done with GNU/Linux, but PhysicsFS is known to
+ work out of the box with several flavors of Unix. It it doesn't work, patches
+ to get it running can be sent to icculus@icculus.org.
+
+
+Windows:
+
+If building with Cygwin, mingw32, MSYS, or something else that uses the GNU
+ toolchain, follow the Unix instructions, above.
+
+If you want to use Visual Studio, nmake, or the Platform SDK, you will need
+ CMake (https://www.cmake.org/) 2.4 or later installed. Point CMake at the
+ CMakeLists.txt file in the root of the source directory and hit the
+ "Configure" button. After telling it what type of compiler you are targeting
+ (Borland, Visual Studio, etc), CMake will process for while and then give you
+ a list of options you can change (what archivers you want to support, etc).
+ If you aren't sure, the defaults are probably fine. Hit the "Configure"
+ button again, then "OK" once configuration has completed with options that
+ match your liking. Now project files for your favorite programming
+ environment will be generated for you in the directory you specified.
+ Go there and use them to build PhysicsFS.
+
+PhysicsFS will only link directly against system libraries that have existed
+ since Windows NT 3.51. If there's a newer API we want to use, we try to
+ dynamically load it at runtime and fallback to a reasonable behaviour when
+ we can't find it. Note that Windows 98 and later _should_
+ work if you use the Microsoft Layer for Unicode (UNICOWS.DLL) to provide
+ some missing system APIs, but this is no longer tested as of PhysicsFS 2.1.0.
+ PhysicsFS 2.0.x is known to work with Windows 95 without UNICOWS.DLL.
+
+PhysicsFS works on 32-bit and 64-bit Windows. There is no 16-bit Windows
+ support at all. Windows RT is covered below.
+
+
+Windows RT:
+
+Windows RT (Windows Phone, Windows Store, UWP) 8.0 and later are supported.
+Make sure you include both physfs_platform_windows.c _and_
+physfs_platform_winrt.cpp in your build, and that the C++ file has
+"Consume Windows Runtime Extension" set to "Yes" in its Visual Studio
+properties (from the command line, you want to compile this file with the
+"/ZW" compiler switch). CMake can, in theory, generate a project file for
+WinRT if you pick a recent Visual Studio target, choose manual cross-compile
+options, and set the system name to "WindowsPhone" or "WindowsStore" and the
+correct OS version (8.0 or later).
+
+
+PocketPC/WindowsCE:
+
+Support for PocketPC was removed in PhysicsFS 2.1.0. This was known to work
+ in the 1.0 releases, but wasn't tested in 2.0 and later. PhysicsFS should
+ work on modern Windows Phones (see "Windows RT" section).
+
+
+macOS:
+
+You will need CMake (https://www.cmake.org/) 2.4 or later installed.
+
+You can either generate a Unix makefile with CMake, or generate an Xcode
+ project, whichever makes you more comfortable.
+
+PowerPC and Intel Macs should both be supported.
+
+
+MAC OS 8/9 ("Mac OS Classic"):
+
+Classic Mac OS support has been dropped in PhysicsFS 2.0. Apple hasn't updated
+ pre-OSX versions in more than a decade at this point, none of the hardware
+ they've shipped will boot it for almost as many years, and finding
+ developer tools for it is becoming almost impossible. As the switch to Intel
+ hardware has removed the "Classic" emulation environment, it was time to
+ remove support from PhysicsFS. That being said, the PhysicsFS 1.0 branch can
+ still target back to Mac OS 8.5, so you can use that if you need support for
+ this legacy OS. We still very much support modern macOS, though: see above.
+
+
+Emscripten:
+
+Use the "Unix" instructions, above. You can install the Emscripten SDK and use
+ the extras/buildbot-emscripten.sh script to automate this for you.
+
+
+BeOS, Zeta, YellowTab:
+
+BeOS support was dropped in PhysicsFS 2.1.0. Consider installing Haiku, which
+we still support.
+
+
+Haiku:
+
+Use the "Unix" instructions, above.
+
+
+OS/2:
+
+OS/2 is known to work with OpenWatcom and GCC-based compilers. I couldn't get
+an OS/2 port of CMake to generate OpenWatcom project files (although it should
+be able to do that in theory), it should be able to do Unix Makefiles with
+GCC. It might be easier to just compile PhysicsFS along with the rest of
+your project on this platform.
+
+
+
+OTHER PLATFORMS:
+
+Many Unix-like platforms might "just work" with CMake. Some of these platforms
+ are known to have worked at one time, but have not been heavily tested, if
+ tested at all. PhysicsFS is, as far as we know, 64-bit and byteorder clean,
+ and is known to compile on several compilers across many platforms. To
+ implement a new platform or archiver, please read the heavily-commented
+ physfs_internal.h and look at the physfs_platform_* and physfs_archiver_*
+ source files for examples.
+
+--ryan. (icculus@icculus.org)
+
diff --git a/third-party/physfs/docs/README-API-documentation.txt b/third-party/physfs/docs/README-API-documentation.txt
new file mode 100644
index 0000000..65f9b82
--- /dev/null
+++ b/third-party/physfs/docs/README-API-documentation.txt
@@ -0,0 +1,18 @@
+The API documentation is readable in a few ways:
+
+- Read physfs.h; it's _heavily_ documented and the primary source of reference
+ documentation for the library.
+- Run Doxygen over the header, which produces nicer-to-browse documentation in
+ HTML, LaTeX, manpage, etc formats. This is done for you if Doxygen is
+ installed and you build the "docs" target in whatever project files CMake
+ generated for you.
+- Too much trouble? We generated the HTML reference for you, online here:
+
+ https://icculus.org/physfs/docs/
+
+- We would love well-written tutorials for the latest version of PhysicsFS!
+ If you write one, we would love to list it here. Drop me a line about it:
+ icculus@icculus.org ... Thanks!
+
+--ryan.
+
diff --git a/third-party/physfs/docs/TODO.txt b/third-party/physfs/docs/TODO.txt
new file mode 100644
index 0000000..d768a5c
--- /dev/null
+++ b/third-party/physfs/docs/TODO.txt
@@ -0,0 +1,62 @@
+Stuff that needs to be done and wishlist:
+
+These are in no particular order.
+Some might be dupes, some might be done already, some might be bad ideas.
+
+
+From https://icculus.org/pipermail/physfs/2009-March/000698.html ...
+
+- Write support for various archives. I haven't decided how to do this yet,
+ but I'd like to.
+- Add an API to expose a file's extended attributes to the application?
+- Deprecate PHYSFS_setSaneConfig(). It really should have been in the extras
+ directory.
+- Clean up the sources to match my ever-changing coding style. :)
+
+
+
+From https://icculus.org/pipermail/physfs/2010-January/000826.html ...
+
+- Lua bindings
+
+
+From https://icculus.org/pipermail/physfs/2010-January/000833.html ...
+
+- SWIG bindings
+
+
+
+From old TODO.txt...
+
+- Other archivers: perhaps tar(.gz|.bz2), RPM, ARJ, etc. These are less
+ important, since streaming archives aren't of much value to games (which
+ is why zipfiles are king: random access), but it could have uses for, say,
+ an installer/updater.
+- Do symlinks in zip archiver work when they point to dirs?
+- Enable more warnings?
+- Use __cdecl in physfs.h?
+- Look for FIXMEs (many marked with "!!!" in comments).
+- fscanf and fprintf support in extras dir.
+- Sanity check byte order at runtime.
+- Memory locking?
+- General code audit.
+- Multiple write dirs with mount points?
+
+
+Other stuff I thought of...
+- moar asserts!
+- constify!
+- Does iPhone work?
+- Fix CMake vs Doxygen.
+- Doxygen replacement? (manpages suck.)
+- Fix coding standards to match.
+- See if we can ditch some #include lines...
+- LZMA support in zip archiver?
+- bzip2 support in zip archiver?
+- Reduce the BAIL and GOTO macro use. A lot of these don't add anything.
+- Change the term "search path" to something less confusing.
+
+Probably other stuff. Requests and recommendations are welcome.
+
+// end of TODO.txt ...
+
diff --git a/third-party/physfs/extras/README-CSharp.txt b/third-party/physfs/extras/README-CSharp.txt
new file mode 100644
index 0000000..863c0db
--- /dev/null
+++ b/third-party/physfs/extras/README-CSharp.txt
@@ -0,0 +1,11 @@
+There used to be C# bindings in this directory, but they have been
+unmaintained for many years.
+
+Instead, there is a more modern PhysicsFS wrapper for .NET available.
+
+You can find it at https://github.com/frabert/SharpPhysFS
+
+Thanks to Francesco Bertolaccini for his efforts on that project!
+
+--ryan.
+
diff --git a/third-party/physfs/extras/abs-file.h b/third-party/physfs/extras/abs-file.h
new file mode 100644
index 0000000..c7e7b49
--- /dev/null
+++ b/third-party/physfs/extras/abs-file.h
@@ -0,0 +1,165 @@
+/*
+ * stdio/physfs abstraction layer 2003-04-02
+ *
+ * Adam D. Moss
+ *
+ * These wrapper macros and functions are designed to allow a program
+ * to perform file I/O with identical semantics and syntax regardless
+ * of whether PhysicsFS is being used or not.
+ */
+#ifndef _ABS_FILE_H
+#define _ABS_FILE_H
+/*
+PLEASE NOTE: This license applies to abs-file.h ONLY (to make it clear that
+you may embed this wrapper code within commercial software); PhysicsFS itself
+is (at the time of writing) released under a different license with
+additional restrictions.
+
+Copyright (C) 2002-2003 Adam D. Moss (the "Author"). All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is fur-
+nished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT-
+NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CON-
+NECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of the Author of the
+Software shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in this Software without prior written authorization
+from the Author.
+*/
+
+#include
+#include
+
+/*
+ * API:
+ *
+ * Macro/function use like stdio equivalent...
+ * -------------- ----------------------------
+ * MY_FILETYPE FILE
+ * MY_OPEN_FOR_READ fopen(..., "rb")
+ * MY_READ fread(...)
+ * MY_CLOSE fclose(...)
+ * MY_GETC fgetc(...)
+ * MY_GETS fgets(...)
+ * MY_ATEOF feof(...)
+ * MY_TELL ftell(...)
+ * MY_SEEK fseek(..., SEEK_SET)
+ * MY_REWIND rewind(...)
+ * MY_SETBUFFER (not a standard for stdio, does nothing there)
+ */
+
+/*
+ * Important DEFINEs:
+ * It is important to define these consistantly across the various
+ * compilation modules of your program if you wish to exchange file
+ * handles between them.
+ *
+ * USE_PHYSFS: Define USE_PHYSFS if PhysicsFS is being used; note that if
+ * you do intend to use PhysicsFS then you will still need to initialize
+ * PhysicsFS yourself and set up its search-paths.
+ *
+ * Optional DEFINEs:
+ *
+ * PHYSFS_DEFAULT_READ_BUFFER : If set then abs-file.h sets the
+ * PhysicsFS buffer size to this value whenever you open a file. You
+ * may over-ride this on a per-filehandle basis by using the
+ * MY_SETBUFFER() macro (which simply does nothing when not using
+ * PhysicsFS). If you have not defined this value explicitly then
+ * abs-file.h will default to the same default buffer size as used by
+ * stdio if it can be determined, or 8192 bytes otherwise.
+ */
+#ifndef PHYSFS_DEFAULT_READ_BUFFER
+#ifdef BUFSIZ
+#define PHYSFS_DEFAULT_READ_BUFFER BUFSIZ
+#else
+#define PHYSFS_DEFAULT_READ_BUFFER 8192
+#endif
+#endif
+
+#ifdef USE_PHYSFS
+
+#include
+#define MY_FILETYPE PHYSFS_File
+#define MY_SETBUFFER(fp,size) PHYSFS_setBuffer(fp,size)
+#define MY_READ(p,s,n,fp) PHYSFS_read(fp,p,s,n)
+#if PHYSFS_DEFAULT_READ_BUFFER
+static MY_FILETYPE* MY_OPEN_FOR_READ(const char *const filename)
+{
+ MY_FILETYPE *const file = PHYSFS_openRead(filename);
+ if (file) {
+ MY_SETBUFFER(file, PHYSFS_DEFAULT_READ_BUFFER);
+ }
+ return file;
+}
+#else
+#define MY_OPEN_FOR_READ(fn) PHYSFS_openRead(fn)
+#endif
+static int MY_GETC(MY_FILETYPE *const fp) {
+ unsigned char c;
+ /*if (PHYSFS_eof(fp)) {
+ return EOF;
+ }
+ MY_READ(&c, 1, 1, fp);*/
+ if (MY_READ(&c, 1, 1, fp) != 1) {
+ return EOF;
+ }
+ return c;
+}
+static char * MY_GETS(char * const str, const int size,
+ MY_FILETYPE *const fp) {
+ int i = 0;
+ int c;
+ do {
+ if (i == size-1) {
+ break;
+ }
+ c = MY_GETC(fp);
+ if (c == EOF) {
+ break;
+ }
+ str[i++] = c;
+ } while (c != '\0' &&
+ c != -1 &&
+ c != '\n');
+ str[i] = '\0';
+ if (i == 0) {
+ return NULL;
+ }
+ return str;
+}
+#define MY_CLOSE(fp) PHYSFS_close(fp)
+#define MY_ATEOF(fp) PHYSFS_eof(fp)
+#define MY_TELL(fp) PHYSFS_tell(fp)
+#define MY_SEEK(fp,o) PHYSFS_seek(fp,o)
+#define MY_REWIND(fp) MY_SEEK(fp,0)
+
+#else
+
+#define MY_FILETYPE FILE
+#define MY_READ(p,s,n,fp) fread(p,s,n,fp)
+#define MY_OPEN_FOR_READ(n) fopen(n, "rb")
+#define MY_GETC(fp) fgetc(fp)
+#define MY_GETS(str,size,fp) fgets(str,size,fp)
+#define MY_CLOSE(fp) fclose(fp)
+#define MY_ATEOF(fp) feof(fp)
+#define MY_TELL(fp) ftell(fp)
+#define MY_SEEK(fp,o) fseek(fp,o, SEEK_SET)
+#define MY_REWIND(fp) rewind(fp)
+/*static void MY_SETBUFFER(const MY_FILETYPE *const file, const int num) { }*/
+#define MY_SETBUFFER(fp,size)
+#endif
+
+#endif
diff --git a/third-party/physfs/extras/buildbot-checker.sh b/third-party/physfs/extras/buildbot-checker.sh
new file mode 100644
index 0000000..3a37a5e
--- /dev/null
+++ b/third-party/physfs/extras/buildbot-checker.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+# This is a script used by some Buildbot workers to push the project
+# through Clang's static analyzer and prepare the output to be uploaded
+# back to the buildmaster. You might find it useful too.
+
+# Install Clang (you already have it on Mac OS X, apt-get install clang
+# on Ubuntu, etc), Make sure "scan-build" is in your $PATH.
+
+FINALDIR="$1"
+
+set -x
+set -e
+
+cd `dirname "$0"`
+cd ..
+
+rm -rf checker-buildbot analysis
+if [ ! -z "$FINALDIR" ]; then
+ rm -rf "$FINALDIR"
+fi
+
+mkdir checker-buildbot
+cd checker-buildbot
+
+# We turn off deprecated declarations, because we don't care about these warnings during static analysis.
+# The -Wno-liblto is new since our checker-279 upgrade, I think; checker otherwise warns "libLTO.dylib relative to clang installed dir not found"
+
+# You might want to do this for CMake-backed builds instead...
+scan-build -o analysis cmake -G Ninja -Wno-dev -DPHYSFS_BUILD_SHARED=False -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-Wno-deprecated-declarations" -DCMAKE_EXE_LINKER_FLAGS="-Wno-liblto" ..
+
+rm -rf analysis
+scan-build -o analysis cmake --build . --config Debug
+
+if [ `ls -A analysis |wc -l` == 0 ] ; then
+ mkdir analysis/zarro
+ echo 'Zarro boogsStatic analysis: no issues to report.' >analysis/zarro/index.html
+fi
+
+mv analysis/* ../analysis
+rmdir analysis # Make sure this is empty.
+cd ..
+chmod -R a+r analysis
+chmod -R go-w analysis
+find analysis -type d -exec chmod a+x {} \;
+if [ -x /usr/bin/xattr ]; then find analysis -exec /usr/bin/xattr -d com.apple.quarantine {} \; 2>/dev/null ; fi
+
+if [ ! -z "$FINALDIR" ]; then
+ mv analysis "$FINALDIR"
+else
+ FINALDIR=analysis
+fi
+
+rm -rf checker-buildbot
+
+echo "Done. Final output is in '$FINALDIR' ..."
+
+# end of checker-buildbot.sh ...
+
diff --git a/third-party/physfs/extras/buildbot-emscripten.sh b/third-party/physfs/extras/buildbot-emscripten.sh
new file mode 100644
index 0000000..b71a073
--- /dev/null
+++ b/third-party/physfs/extras/buildbot-emscripten.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+if [ -z "$SDKDIR" ]; then
+ SDKDIR="/emsdk"
+fi
+
+ENVSCRIPT="$SDKDIR/emsdk_env.sh"
+if [ ! -f "$ENVSCRIPT" ]; then
+ echo "ERROR: This script expects the Emscripten SDK to be in '$SDKDIR'." 1>&2
+ echo "ERROR: Set the \$SDKDIR environment variable to override this." 1>&2
+ exit 1
+fi
+
+TARBALL="$1"
+if [ -z $1 ]; then
+ TARBALL=physfs-emscripten.tar.xz
+fi
+
+cd `dirname "$0"`
+cd ..
+PHYSFSBASE=`pwd`
+
+echo "Setting up Emscripten SDK environment..."
+source "$ENVSCRIPT"
+
+echo "Setting up..."
+cd "$PHYSFSBASE"
+rm -rf buildbot
+mkdir buildbot
+cd buildbot
+
+echo "Configuring..."
+emcmake cmake -G "Ninja" -DPHYSFS_BUILD_SHARED=False -DCMAKE_BUILD_TYPE=MinSizeRel .. || exit $?
+
+echo "Building..."
+emmake cmake --build . --config MinSizeRel || exit $?
+
+set -e
+rm -rf "$TARBALL" physfs-emscripten
+mkdir -p physfs-emscripten
+echo "Archiving to '$TARBALL' ..."
+cp ../src/physfs.h libphysfs.a physfs-emscripten
+chmod -R a+r physfs-emscripten
+chmod a+x physfs-emscripten
+chmod -R go-w physfs-emscripten
+tar -cJvvf "$TARBALL" physfs-emscripten
+echo "Done."
+
+exit 0
+
+# end of emscripten-buildbot.sh ...
+
diff --git a/third-party/physfs/extras/buildbot-os2.sh b/third-party/physfs/extras/buildbot-os2.sh
new file mode 100644
index 0000000..4119c87
--- /dev/null
+++ b/third-party/physfs/extras/buildbot-os2.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+# This is used by the buildbot to cross-compile for OS/2 from Linux, using
+# OpenWatcom. In an ideal world, we wouldn't need this, but we need a few
+# things to do this "properly" ...
+#
+# - OS/2 running as a VMware guest on the build machine
+# - Buildbot-worker running on that OS/2 guest
+# - CMake for OS/2 that...
+# - ... has Open Watcom compiler support and...
+# - ... a Watcom WMake project generator.
+#
+# Some of these things are doable (there is a CMake port for OS/2, you can
+# use GCC with it), but since OpenWatcom will just target OS/2 on Linux just
+# like it could on OS/2, it's easier and more efficient to just have a
+# buildbot script compile it here.
+#
+# Note that we just blast all the C files through the wcc386 compiler and
+# skip CMake entirely. We should fix this at some point.
+
+set -e
+
+ZIPFILE="$1"
+if [ -z $ZIPFILE ]; then
+ ZIPFILE=physfs-os2.zip
+fi
+
+export WATCOM="/usr/local/share/watcom"
+export PATH="$PATH:$WATCOM/binl"
+
+CFLAGS="-i=\"$WATCOM/h;$WATCOM/h/os2;../src\" -wx -d0 -otexan -6r -zq -bt=os2 -fo=.obj -mf"
+WLIBFLAGS="-b -c -n -q -p=512"
+
+cd `dirname "$0"`
+cd ..
+mkdir -p buildbot
+cd buildbot
+
+rm -f test_physfs.obj
+
+OKAY="1"
+for src in ../src/*.c ; do
+ echo wcc386 $src $CFLAGS
+ wcc386 $src $CFLAGS || OKAY="0"
+done
+
+if [ "$OKAY" == "1" ]; then
+ echo wlib $WLIBFLAGS physfs.lib *.obj
+ wlib $WLIBFLAGS physfs.lib *.obj || OKAY="0"
+fi
+
+echo wcc386 ../test/test_physfs.c $CFLAGS
+wcc386 ../test/test_physfs.c $CFLAGS || OKAY="0"
+
+if [ "$OKAY" == "1" ]; then
+ LDFLAGS="name test_physfs d all sys os2v2 op m libr physfs op q op symf FIL test_physfs.obj"
+ echo wlink $LDFLAGS
+ wlink $LDFLAGS || OKAY="0"
+fi
+
+if [ "$OKAY" == "1" ]; then
+ F=`file test_physfs.exe`
+ echo "$F"
+ if [ "$F" != 'test_physfs.exe: MS-DOS executable, LX for OS/2 (console) i80386' ]; then
+ echo 1>&2 "ERROR: final binary doesn't appear to be OS/2 executable."
+ OKAY=0
+ fi
+fi
+
+if [ "$OKAY" == "1" ]; then
+ echo 1>&2 "Build succeeded."
+ set -e
+ rm -rf "$ZIPFILE" physfs-os2
+ mkdir -p physfs-os2
+ echo "Zipping to '$ZIPFILE' ..."
+ cp ../src/physfs.h physfs.lib physfs-os2
+ chmod -R a+r physfs-os2
+ chmod a+x physfs-os2
+ chmod -R go-w physfs-os2
+ zip -9r "$ZIPFILE" physfs-os2
+ echo "Done."
+ exit 0
+else
+ echo 1>&2 "Build failed."
+ exit 1
+fi
+
diff --git a/third-party/physfs/extras/buildbot-raspberrypi.sh b/third-party/physfs/extras/buildbot-raspberrypi.sh
new file mode 100644
index 0000000..5968894
--- /dev/null
+++ b/third-party/physfs/extras/buildbot-raspberrypi.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+# This is the script physfs-buildbot.icculus.org uses to cross-compile
+# PhysicsFS from x86 Linux to Raspberry Pi. This script was originally from
+# Simple Directmedia Layer ( https://www.libsdl.org/ ).
+
+# The final tarball can be unpacked in the root directory of a RPi,
+# so the PhysicsFS install lands in /usr/local. Run ldconfig, and then
+# you should be able to build and run PhysicsFS-based software on your
+# Pi. Standard configure scripts should be able to find PhysicsFS and
+# build against it.
+
+TARBALL="$1"
+if [ -z $1 ]; then
+ TARBALL=physfs-raspberrypi.tar.xz
+fi
+
+BUILDBOTDIR="buildbot"
+PARENTDIR="$PWD"
+
+set -e
+set -x
+rm -f $TARBALL
+rm -rf $BUILDBOTDIR
+mkdir -p $BUILDBOTDIR
+pushd $BUILDBOTDIR
+
+# the '-G "Ninja"' can be '-G "Unix Makefiles"' if you prefer to use GNU Make.
+SYSROOT="/opt/rpi-sysroot"
+cmake -G "Ninja" \
+ -DCMAKE_C_COMPILER="/opt/rpi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin/arm-linux-gnueabihf-gcc" \
+ -DCMAKE_BUILD_TYPE=MinSizeRel \
+ -DCMAKE_SYSROOT="$SYSROOT" \
+ -DCMAKE_FIND_ROOT_PATH="$SYSROOT" \
+ -DCMAKE_SYSTEM_NAME="Linux" \
+ -DCMAKE_SYSTEM_VERSION=1 \
+ -DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER \
+ -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY \
+ -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY \
+ ..
+
+cmake --build . --config MinSizeRel
+
+rm -rf "$TARBALL" physfs-raspberrypi
+mkdir -p physfs-raspberrypi
+echo "Archiving to '$TARBALL' ..."
+cp -a ../src/physfs.h libphysfs.a libphysfs.so* physfs-raspberrypi
+chmod -R a+r physfs-raspberrypi
+chmod a+x physfs-raspberrypi physfs-raspberrypi/*.so*
+chmod -R go-w physfs-raspberrypi
+tar -cJvvf "$TARBALL" physfs-raspberrypi
+
+set +x
+echo "Done."
+
+
diff --git a/third-party/physfs/extras/casefolding.txt b/third-party/physfs/extras/casefolding.txt
new file mode 100644
index 0000000..0197a6c
--- /dev/null
+++ b/third-party/physfs/extras/casefolding.txt
@@ -0,0 +1,1414 @@
+# CaseFolding-8.0.0.txt
+# Date: 2015-01-13, 18:16:36 GMT [MD]
+#
+# Unicode Character Database
+# Copyright (c) 1991-2015 Unicode, Inc.
+# For terms of use, see http://www.unicode.org/terms_of_use.html
+# For documentation, see http://www.unicode.org/reports/tr44/
+#
+# Case Folding Properties
+#
+# This file is a supplement to the UnicodeData file.
+# It provides a case folding mapping generated from the Unicode Character Database.
+# If all characters are mapped according to the full mapping below, then
+# case differences (according to UnicodeData.txt and SpecialCasing.txt)
+# are eliminated.
+#
+# The data supports both implementations that require simple case foldings
+# (where string lengths don't change), and implementations that allow full case folding
+# (where string lengths may grow). Note that where they can be supported, the
+# full case foldings are superior: for example, they allow "MASSE" and "Maße" to match.
+#
+# All code points not listed in this file map to themselves.
+#
+# NOTE: case folding does not preserve normalization formats!
+#
+# For information on case folding, including how to have case folding
+# preserve normalization formats, see Section 3.13 Default Case Algorithms in
+# The Unicode Standard.
+#
+# ================================================================================
+# Format
+# ================================================================================
+# The entries in this file are in the following machine-readable format:
+#
+# ; ; ; #
+#
+# The status field is:
+# C: common case folding, common mappings shared by both simple and full mappings.
+# F: full case folding, mappings that cause strings to grow in length. Multiple characters are separated by spaces.
+# S: simple case folding, mappings to single characters where different from F.
+# T: special case for uppercase I and dotted uppercase I
+# - For non-Turkic languages, this mapping is normally not used.
+# - For Turkic languages (tr, az), this mapping can be used instead of the normal mapping for these characters.
+# Note that the Turkic mappings do not maintain canonical equivalence without additional processing.
+# See the discussions of case mapping in the Unicode Standard for more information.
+#
+# Usage:
+# A. To do a simple case folding, use the mappings with status C + S.
+# B. To do a full case folding, use the mappings with status C + F.
+#
+# The mappings with status T can be used or omitted depending on the desired case-folding
+# behavior. (The default option is to exclude them.)
+#
+# =================================================================
+
+# Property: Case_Folding
+
+# All code points not explicitly listed for Case_Folding
+# have the value C for the status field, and the code point itself for the mapping field.
+
+# =================================================================
+0041; C; 0061; # LATIN CAPITAL LETTER A
+0042; C; 0062; # LATIN CAPITAL LETTER B
+0043; C; 0063; # LATIN CAPITAL LETTER C
+0044; C; 0064; # LATIN CAPITAL LETTER D
+0045; C; 0065; # LATIN CAPITAL LETTER E
+0046; C; 0066; # LATIN CAPITAL LETTER F
+0047; C; 0067; # LATIN CAPITAL LETTER G
+0048; C; 0068; # LATIN CAPITAL LETTER H
+0049; C; 0069; # LATIN CAPITAL LETTER I
+0049; T; 0131; # LATIN CAPITAL LETTER I
+004A; C; 006A; # LATIN CAPITAL LETTER J
+004B; C; 006B; # LATIN CAPITAL LETTER K
+004C; C; 006C; # LATIN CAPITAL LETTER L
+004D; C; 006D; # LATIN CAPITAL LETTER M
+004E; C; 006E; # LATIN CAPITAL LETTER N
+004F; C; 006F; # LATIN CAPITAL LETTER O
+0050; C; 0070; # LATIN CAPITAL LETTER P
+0051; C; 0071; # LATIN CAPITAL LETTER Q
+0052; C; 0072; # LATIN CAPITAL LETTER R
+0053; C; 0073; # LATIN CAPITAL LETTER S
+0054; C; 0074; # LATIN CAPITAL LETTER T
+0055; C; 0075; # LATIN CAPITAL LETTER U
+0056; C; 0076; # LATIN CAPITAL LETTER V
+0057; C; 0077; # LATIN CAPITAL LETTER W
+0058; C; 0078; # LATIN CAPITAL LETTER X
+0059; C; 0079; # LATIN CAPITAL LETTER Y
+005A; C; 007A; # LATIN CAPITAL LETTER Z
+00B5; C; 03BC; # MICRO SIGN
+00C0; C; 00E0; # LATIN CAPITAL LETTER A WITH GRAVE
+00C1; C; 00E1; # LATIN CAPITAL LETTER A WITH ACUTE
+00C2; C; 00E2; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+00C3; C; 00E3; # LATIN CAPITAL LETTER A WITH TILDE
+00C4; C; 00E4; # LATIN CAPITAL LETTER A WITH DIAERESIS
+00C5; C; 00E5; # LATIN CAPITAL LETTER A WITH RING ABOVE
+00C6; C; 00E6; # LATIN CAPITAL LETTER AE
+00C7; C; 00E7; # LATIN CAPITAL LETTER C WITH CEDILLA
+00C8; C; 00E8; # LATIN CAPITAL LETTER E WITH GRAVE
+00C9; C; 00E9; # LATIN CAPITAL LETTER E WITH ACUTE
+00CA; C; 00EA; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+00CB; C; 00EB; # LATIN CAPITAL LETTER E WITH DIAERESIS
+00CC; C; 00EC; # LATIN CAPITAL LETTER I WITH GRAVE
+00CD; C; 00ED; # LATIN CAPITAL LETTER I WITH ACUTE
+00CE; C; 00EE; # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+00CF; C; 00EF; # LATIN CAPITAL LETTER I WITH DIAERESIS
+00D0; C; 00F0; # LATIN CAPITAL LETTER ETH
+00D1; C; 00F1; # LATIN CAPITAL LETTER N WITH TILDE
+00D2; C; 00F2; # LATIN CAPITAL LETTER O WITH GRAVE
+00D3; C; 00F3; # LATIN CAPITAL LETTER O WITH ACUTE
+00D4; C; 00F4; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+00D5; C; 00F5; # LATIN CAPITAL LETTER O WITH TILDE
+00D6; C; 00F6; # LATIN CAPITAL LETTER O WITH DIAERESIS
+00D8; C; 00F8; # LATIN CAPITAL LETTER O WITH STROKE
+00D9; C; 00F9; # LATIN CAPITAL LETTER U WITH GRAVE
+00DA; C; 00FA; # LATIN CAPITAL LETTER U WITH ACUTE
+00DB; C; 00FB; # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+00DC; C; 00FC; # LATIN CAPITAL LETTER U WITH DIAERESIS
+00DD; C; 00FD; # LATIN CAPITAL LETTER Y WITH ACUTE
+00DE; C; 00FE; # LATIN CAPITAL LETTER THORN
+00DF; F; 0073 0073; # LATIN SMALL LETTER SHARP S
+0100; C; 0101; # LATIN CAPITAL LETTER A WITH MACRON
+0102; C; 0103; # LATIN CAPITAL LETTER A WITH BREVE
+0104; C; 0105; # LATIN CAPITAL LETTER A WITH OGONEK
+0106; C; 0107; # LATIN CAPITAL LETTER C WITH ACUTE
+0108; C; 0109; # LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+010A; C; 010B; # LATIN CAPITAL LETTER C WITH DOT ABOVE
+010C; C; 010D; # LATIN CAPITAL LETTER C WITH CARON
+010E; C; 010F; # LATIN CAPITAL LETTER D WITH CARON
+0110; C; 0111; # LATIN CAPITAL LETTER D WITH STROKE
+0112; C; 0113; # LATIN CAPITAL LETTER E WITH MACRON
+0114; C; 0115; # LATIN CAPITAL LETTER E WITH BREVE
+0116; C; 0117; # LATIN CAPITAL LETTER E WITH DOT ABOVE
+0118; C; 0119; # LATIN CAPITAL LETTER E WITH OGONEK
+011A; C; 011B; # LATIN CAPITAL LETTER E WITH CARON
+011C; C; 011D; # LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+011E; C; 011F; # LATIN CAPITAL LETTER G WITH BREVE
+0120; C; 0121; # LATIN CAPITAL LETTER G WITH DOT ABOVE
+0122; C; 0123; # LATIN CAPITAL LETTER G WITH CEDILLA
+0124; C; 0125; # LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+0126; C; 0127; # LATIN CAPITAL LETTER H WITH STROKE
+0128; C; 0129; # LATIN CAPITAL LETTER I WITH TILDE
+012A; C; 012B; # LATIN CAPITAL LETTER I WITH MACRON
+012C; C; 012D; # LATIN CAPITAL LETTER I WITH BREVE
+012E; C; 012F; # LATIN CAPITAL LETTER I WITH OGONEK
+0130; F; 0069 0307; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0130; T; 0069; # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0132; C; 0133; # LATIN CAPITAL LIGATURE IJ
+0134; C; 0135; # LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+0136; C; 0137; # LATIN CAPITAL LETTER K WITH CEDILLA
+0139; C; 013A; # LATIN CAPITAL LETTER L WITH ACUTE
+013B; C; 013C; # LATIN CAPITAL LETTER L WITH CEDILLA
+013D; C; 013E; # LATIN CAPITAL LETTER L WITH CARON
+013F; C; 0140; # LATIN CAPITAL LETTER L WITH MIDDLE DOT
+0141; C; 0142; # LATIN CAPITAL LETTER L WITH STROKE
+0143; C; 0144; # LATIN CAPITAL LETTER N WITH ACUTE
+0145; C; 0146; # LATIN CAPITAL LETTER N WITH CEDILLA
+0147; C; 0148; # LATIN CAPITAL LETTER N WITH CARON
+0149; F; 02BC 006E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
+014A; C; 014B; # LATIN CAPITAL LETTER ENG
+014C; C; 014D; # LATIN CAPITAL LETTER O WITH MACRON
+014E; C; 014F; # LATIN CAPITAL LETTER O WITH BREVE
+0150; C; 0151; # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0152; C; 0153; # LATIN CAPITAL LIGATURE OE
+0154; C; 0155; # LATIN CAPITAL LETTER R WITH ACUTE
+0156; C; 0157; # LATIN CAPITAL LETTER R WITH CEDILLA
+0158; C; 0159; # LATIN CAPITAL LETTER R WITH CARON
+015A; C; 015B; # LATIN CAPITAL LETTER S WITH ACUTE
+015C; C; 015D; # LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+015E; C; 015F; # LATIN CAPITAL LETTER S WITH CEDILLA
+0160; C; 0161; # LATIN CAPITAL LETTER S WITH CARON
+0162; C; 0163; # LATIN CAPITAL LETTER T WITH CEDILLA
+0164; C; 0165; # LATIN CAPITAL LETTER T WITH CARON
+0166; C; 0167; # LATIN CAPITAL LETTER T WITH STROKE
+0168; C; 0169; # LATIN CAPITAL LETTER U WITH TILDE
+016A; C; 016B; # LATIN CAPITAL LETTER U WITH MACRON
+016C; C; 016D; # LATIN CAPITAL LETTER U WITH BREVE
+016E; C; 016F; # LATIN CAPITAL LETTER U WITH RING ABOVE
+0170; C; 0171; # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0172; C; 0173; # LATIN CAPITAL LETTER U WITH OGONEK
+0174; C; 0175; # LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+0176; C; 0177; # LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+0178; C; 00FF; # LATIN CAPITAL LETTER Y WITH DIAERESIS
+0179; C; 017A; # LATIN CAPITAL LETTER Z WITH ACUTE
+017B; C; 017C; # LATIN CAPITAL LETTER Z WITH DOT ABOVE
+017D; C; 017E; # LATIN CAPITAL LETTER Z WITH CARON
+017F; C; 0073; # LATIN SMALL LETTER LONG S
+0181; C; 0253; # LATIN CAPITAL LETTER B WITH HOOK
+0182; C; 0183; # LATIN CAPITAL LETTER B WITH TOPBAR
+0184; C; 0185; # LATIN CAPITAL LETTER TONE SIX
+0186; C; 0254; # LATIN CAPITAL LETTER OPEN O
+0187; C; 0188; # LATIN CAPITAL LETTER C WITH HOOK
+0189; C; 0256; # LATIN CAPITAL LETTER AFRICAN D
+018A; C; 0257; # LATIN CAPITAL LETTER D WITH HOOK
+018B; C; 018C; # LATIN CAPITAL LETTER D WITH TOPBAR
+018E; C; 01DD; # LATIN CAPITAL LETTER REVERSED E
+018F; C; 0259; # LATIN CAPITAL LETTER SCHWA
+0190; C; 025B; # LATIN CAPITAL LETTER OPEN E
+0191; C; 0192; # LATIN CAPITAL LETTER F WITH HOOK
+0193; C; 0260; # LATIN CAPITAL LETTER G WITH HOOK
+0194; C; 0263; # LATIN CAPITAL LETTER GAMMA
+0196; C; 0269; # LATIN CAPITAL LETTER IOTA
+0197; C; 0268; # LATIN CAPITAL LETTER I WITH STROKE
+0198; C; 0199; # LATIN CAPITAL LETTER K WITH HOOK
+019C; C; 026F; # LATIN CAPITAL LETTER TURNED M
+019D; C; 0272; # LATIN CAPITAL LETTER N WITH LEFT HOOK
+019F; C; 0275; # LATIN CAPITAL LETTER O WITH MIDDLE TILDE
+01A0; C; 01A1; # LATIN CAPITAL LETTER O WITH HORN
+01A2; C; 01A3; # LATIN CAPITAL LETTER OI
+01A4; C; 01A5; # LATIN CAPITAL LETTER P WITH HOOK
+01A6; C; 0280; # LATIN LETTER YR
+01A7; C; 01A8; # LATIN CAPITAL LETTER TONE TWO
+01A9; C; 0283; # LATIN CAPITAL LETTER ESH
+01AC; C; 01AD; # LATIN CAPITAL LETTER T WITH HOOK
+01AE; C; 0288; # LATIN CAPITAL LETTER T WITH RETROFLEX HOOK
+01AF; C; 01B0; # LATIN CAPITAL LETTER U WITH HORN
+01B1; C; 028A; # LATIN CAPITAL LETTER UPSILON
+01B2; C; 028B; # LATIN CAPITAL LETTER V WITH HOOK
+01B3; C; 01B4; # LATIN CAPITAL LETTER Y WITH HOOK
+01B5; C; 01B6; # LATIN CAPITAL LETTER Z WITH STROKE
+01B7; C; 0292; # LATIN CAPITAL LETTER EZH
+01B8; C; 01B9; # LATIN CAPITAL LETTER EZH REVERSED
+01BC; C; 01BD; # LATIN CAPITAL LETTER TONE FIVE
+01C4; C; 01C6; # LATIN CAPITAL LETTER DZ WITH CARON
+01C5; C; 01C6; # LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
+01C7; C; 01C9; # LATIN CAPITAL LETTER LJ
+01C8; C; 01C9; # LATIN CAPITAL LETTER L WITH SMALL LETTER J
+01CA; C; 01CC; # LATIN CAPITAL LETTER NJ
+01CB; C; 01CC; # LATIN CAPITAL LETTER N WITH SMALL LETTER J
+01CD; C; 01CE; # LATIN CAPITAL LETTER A WITH CARON
+01CF; C; 01D0; # LATIN CAPITAL LETTER I WITH CARON
+01D1; C; 01D2; # LATIN CAPITAL LETTER O WITH CARON
+01D3; C; 01D4; # LATIN CAPITAL LETTER U WITH CARON
+01D5; C; 01D6; # LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON
+01D7; C; 01D8; # LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE
+01D9; C; 01DA; # LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON
+01DB; C; 01DC; # LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE
+01DE; C; 01DF; # LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
+01E0; C; 01E1; # LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
+01E2; C; 01E3; # LATIN CAPITAL LETTER AE WITH MACRON
+01E4; C; 01E5; # LATIN CAPITAL LETTER G WITH STROKE
+01E6; C; 01E7; # LATIN CAPITAL LETTER G WITH CARON
+01E8; C; 01E9; # LATIN CAPITAL LETTER K WITH CARON
+01EA; C; 01EB; # LATIN CAPITAL LETTER O WITH OGONEK
+01EC; C; 01ED; # LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
+01EE; C; 01EF; # LATIN CAPITAL LETTER EZH WITH CARON
+01F0; F; 006A 030C; # LATIN SMALL LETTER J WITH CARON
+01F1; C; 01F3; # LATIN CAPITAL LETTER DZ
+01F2; C; 01F3; # LATIN CAPITAL LETTER D WITH SMALL LETTER Z
+01F4; C; 01F5; # LATIN CAPITAL LETTER G WITH ACUTE
+01F6; C; 0195; # LATIN CAPITAL LETTER HWAIR
+01F7; C; 01BF; # LATIN CAPITAL LETTER WYNN
+01F8; C; 01F9; # LATIN CAPITAL LETTER N WITH GRAVE
+01FA; C; 01FB; # LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
+01FC; C; 01FD; # LATIN CAPITAL LETTER AE WITH ACUTE
+01FE; C; 01FF; # LATIN CAPITAL LETTER O WITH STROKE AND ACUTE
+0200; C; 0201; # LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
+0202; C; 0203; # LATIN CAPITAL LETTER A WITH INVERTED BREVE
+0204; C; 0205; # LATIN CAPITAL LETTER E WITH DOUBLE GRAVE
+0206; C; 0207; # LATIN CAPITAL LETTER E WITH INVERTED BREVE
+0208; C; 0209; # LATIN CAPITAL LETTER I WITH DOUBLE GRAVE
+020A; C; 020B; # LATIN CAPITAL LETTER I WITH INVERTED BREVE
+020C; C; 020D; # LATIN CAPITAL LETTER O WITH DOUBLE GRAVE
+020E; C; 020F; # LATIN CAPITAL LETTER O WITH INVERTED BREVE
+0210; C; 0211; # LATIN CAPITAL LETTER R WITH DOUBLE GRAVE
+0212; C; 0213; # LATIN CAPITAL LETTER R WITH INVERTED BREVE
+0214; C; 0215; # LATIN CAPITAL LETTER U WITH DOUBLE GRAVE
+0216; C; 0217; # LATIN CAPITAL LETTER U WITH INVERTED BREVE
+0218; C; 0219; # LATIN CAPITAL LETTER S WITH COMMA BELOW
+021A; C; 021B; # LATIN CAPITAL LETTER T WITH COMMA BELOW
+021C; C; 021D; # LATIN CAPITAL LETTER YOGH
+021E; C; 021F; # LATIN CAPITAL LETTER H WITH CARON
+0220; C; 019E; # LATIN CAPITAL LETTER N WITH LONG RIGHT LEG
+0222; C; 0223; # LATIN CAPITAL LETTER OU
+0224; C; 0225; # LATIN CAPITAL LETTER Z WITH HOOK
+0226; C; 0227; # LATIN CAPITAL LETTER A WITH DOT ABOVE
+0228; C; 0229; # LATIN CAPITAL LETTER E WITH CEDILLA
+022A; C; 022B; # LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON
+022C; C; 022D; # LATIN CAPITAL LETTER O WITH TILDE AND MACRON
+022E; C; 022F; # LATIN CAPITAL LETTER O WITH DOT ABOVE
+0230; C; 0231; # LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON
+0232; C; 0233; # LATIN CAPITAL LETTER Y WITH MACRON
+023A; C; 2C65; # LATIN CAPITAL LETTER A WITH STROKE
+023B; C; 023C; # LATIN CAPITAL LETTER C WITH STROKE
+023D; C; 019A; # LATIN CAPITAL LETTER L WITH BAR
+023E; C; 2C66; # LATIN CAPITAL LETTER T WITH DIAGONAL STROKE
+0241; C; 0242; # LATIN CAPITAL LETTER GLOTTAL STOP
+0243; C; 0180; # LATIN CAPITAL LETTER B WITH STROKE
+0244; C; 0289; # LATIN CAPITAL LETTER U BAR
+0245; C; 028C; # LATIN CAPITAL LETTER TURNED V
+0246; C; 0247; # LATIN CAPITAL LETTER E WITH STROKE
+0248; C; 0249; # LATIN CAPITAL LETTER J WITH STROKE
+024A; C; 024B; # LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL
+024C; C; 024D; # LATIN CAPITAL LETTER R WITH STROKE
+024E; C; 024F; # LATIN CAPITAL LETTER Y WITH STROKE
+0345; C; 03B9; # COMBINING GREEK YPOGEGRAMMENI
+0370; C; 0371; # GREEK CAPITAL LETTER HETA
+0372; C; 0373; # GREEK CAPITAL LETTER ARCHAIC SAMPI
+0376; C; 0377; # GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA
+037F; C; 03F3; # GREEK CAPITAL LETTER YOT
+0386; C; 03AC; # GREEK CAPITAL LETTER ALPHA WITH TONOS
+0388; C; 03AD; # GREEK CAPITAL LETTER EPSILON WITH TONOS
+0389; C; 03AE; # GREEK CAPITAL LETTER ETA WITH TONOS
+038A; C; 03AF; # GREEK CAPITAL LETTER IOTA WITH TONOS
+038C; C; 03CC; # GREEK CAPITAL LETTER OMICRON WITH TONOS
+038E; C; 03CD; # GREEK CAPITAL LETTER UPSILON WITH TONOS
+038F; C; 03CE; # GREEK CAPITAL LETTER OMEGA WITH TONOS
+0390; F; 03B9 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+0391; C; 03B1; # GREEK CAPITAL LETTER ALPHA
+0392; C; 03B2; # GREEK CAPITAL LETTER BETA
+0393; C; 03B3; # GREEK CAPITAL LETTER GAMMA
+0394; C; 03B4; # GREEK CAPITAL LETTER DELTA
+0395; C; 03B5; # GREEK CAPITAL LETTER EPSILON
+0396; C; 03B6; # GREEK CAPITAL LETTER ZETA
+0397; C; 03B7; # GREEK CAPITAL LETTER ETA
+0398; C; 03B8; # GREEK CAPITAL LETTER THETA
+0399; C; 03B9; # GREEK CAPITAL LETTER IOTA
+039A; C; 03BA; # GREEK CAPITAL LETTER KAPPA
+039B; C; 03BB; # GREEK CAPITAL LETTER LAMDA
+039C; C; 03BC; # GREEK CAPITAL LETTER MU
+039D; C; 03BD; # GREEK CAPITAL LETTER NU
+039E; C; 03BE; # GREEK CAPITAL LETTER XI
+039F; C; 03BF; # GREEK CAPITAL LETTER OMICRON
+03A0; C; 03C0; # GREEK CAPITAL LETTER PI
+03A1; C; 03C1; # GREEK CAPITAL LETTER RHO
+03A3; C; 03C3; # GREEK CAPITAL LETTER SIGMA
+03A4; C; 03C4; # GREEK CAPITAL LETTER TAU
+03A5; C; 03C5; # GREEK CAPITAL LETTER UPSILON
+03A6; C; 03C6; # GREEK CAPITAL LETTER PHI
+03A7; C; 03C7; # GREEK CAPITAL LETTER CHI
+03A8; C; 03C8; # GREEK CAPITAL LETTER PSI
+03A9; C; 03C9; # GREEK CAPITAL LETTER OMEGA
+03AA; C; 03CA; # GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+03AB; C; 03CB; # GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+03B0; F; 03C5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+03C2; C; 03C3; # GREEK SMALL LETTER FINAL SIGMA
+03CF; C; 03D7; # GREEK CAPITAL KAI SYMBOL
+03D0; C; 03B2; # GREEK BETA SYMBOL
+03D1; C; 03B8; # GREEK THETA SYMBOL
+03D5; C; 03C6; # GREEK PHI SYMBOL
+03D6; C; 03C0; # GREEK PI SYMBOL
+03D8; C; 03D9; # GREEK LETTER ARCHAIC KOPPA
+03DA; C; 03DB; # GREEK LETTER STIGMA
+03DC; C; 03DD; # GREEK LETTER DIGAMMA
+03DE; C; 03DF; # GREEK LETTER KOPPA
+03E0; C; 03E1; # GREEK LETTER SAMPI
+03E2; C; 03E3; # COPTIC CAPITAL LETTER SHEI
+03E4; C; 03E5; # COPTIC CAPITAL LETTER FEI
+03E6; C; 03E7; # COPTIC CAPITAL LETTER KHEI
+03E8; C; 03E9; # COPTIC CAPITAL LETTER HORI
+03EA; C; 03EB; # COPTIC CAPITAL LETTER GANGIA
+03EC; C; 03ED; # COPTIC CAPITAL LETTER SHIMA
+03EE; C; 03EF; # COPTIC CAPITAL LETTER DEI
+03F0; C; 03BA; # GREEK KAPPA SYMBOL
+03F1; C; 03C1; # GREEK RHO SYMBOL
+03F4; C; 03B8; # GREEK CAPITAL THETA SYMBOL
+03F5; C; 03B5; # GREEK LUNATE EPSILON SYMBOL
+03F7; C; 03F8; # GREEK CAPITAL LETTER SHO
+03F9; C; 03F2; # GREEK CAPITAL LUNATE SIGMA SYMBOL
+03FA; C; 03FB; # GREEK CAPITAL LETTER SAN
+03FD; C; 037B; # GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL
+03FE; C; 037C; # GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL
+03FF; C; 037D; # GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL
+0400; C; 0450; # CYRILLIC CAPITAL LETTER IE WITH GRAVE
+0401; C; 0451; # CYRILLIC CAPITAL LETTER IO
+0402; C; 0452; # CYRILLIC CAPITAL LETTER DJE
+0403; C; 0453; # CYRILLIC CAPITAL LETTER GJE
+0404; C; 0454; # CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0405; C; 0455; # CYRILLIC CAPITAL LETTER DZE
+0406; C; 0456; # CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0407; C; 0457; # CYRILLIC CAPITAL LETTER YI
+0408; C; 0458; # CYRILLIC CAPITAL LETTER JE
+0409; C; 0459; # CYRILLIC CAPITAL LETTER LJE
+040A; C; 045A; # CYRILLIC CAPITAL LETTER NJE
+040B; C; 045B; # CYRILLIC CAPITAL LETTER TSHE
+040C; C; 045C; # CYRILLIC CAPITAL LETTER KJE
+040D; C; 045D; # CYRILLIC CAPITAL LETTER I WITH GRAVE
+040E; C; 045E; # CYRILLIC CAPITAL LETTER SHORT U
+040F; C; 045F; # CYRILLIC CAPITAL LETTER DZHE
+0410; C; 0430; # CYRILLIC CAPITAL LETTER A
+0411; C; 0431; # CYRILLIC CAPITAL LETTER BE
+0412; C; 0432; # CYRILLIC CAPITAL LETTER VE
+0413; C; 0433; # CYRILLIC CAPITAL LETTER GHE
+0414; C; 0434; # CYRILLIC CAPITAL LETTER DE
+0415; C; 0435; # CYRILLIC CAPITAL LETTER IE
+0416; C; 0436; # CYRILLIC CAPITAL LETTER ZHE
+0417; C; 0437; # CYRILLIC CAPITAL LETTER ZE
+0418; C; 0438; # CYRILLIC CAPITAL LETTER I
+0419; C; 0439; # CYRILLIC CAPITAL LETTER SHORT I
+041A; C; 043A; # CYRILLIC CAPITAL LETTER KA
+041B; C; 043B; # CYRILLIC CAPITAL LETTER EL
+041C; C; 043C; # CYRILLIC CAPITAL LETTER EM
+041D; C; 043D; # CYRILLIC CAPITAL LETTER EN
+041E; C; 043E; # CYRILLIC CAPITAL LETTER O
+041F; C; 043F; # CYRILLIC CAPITAL LETTER PE
+0420; C; 0440; # CYRILLIC CAPITAL LETTER ER
+0421; C; 0441; # CYRILLIC CAPITAL LETTER ES
+0422; C; 0442; # CYRILLIC CAPITAL LETTER TE
+0423; C; 0443; # CYRILLIC CAPITAL LETTER U
+0424; C; 0444; # CYRILLIC CAPITAL LETTER EF
+0425; C; 0445; # CYRILLIC CAPITAL LETTER HA
+0426; C; 0446; # CYRILLIC CAPITAL LETTER TSE
+0427; C; 0447; # CYRILLIC CAPITAL LETTER CHE
+0428; C; 0448; # CYRILLIC CAPITAL LETTER SHA
+0429; C; 0449; # CYRILLIC CAPITAL LETTER SHCHA
+042A; C; 044A; # CYRILLIC CAPITAL LETTER HARD SIGN
+042B; C; 044B; # CYRILLIC CAPITAL LETTER YERU
+042C; C; 044C; # CYRILLIC CAPITAL LETTER SOFT SIGN
+042D; C; 044D; # CYRILLIC CAPITAL LETTER E
+042E; C; 044E; # CYRILLIC CAPITAL LETTER YU
+042F; C; 044F; # CYRILLIC CAPITAL LETTER YA
+0460; C; 0461; # CYRILLIC CAPITAL LETTER OMEGA
+0462; C; 0463; # CYRILLIC CAPITAL LETTER YAT
+0464; C; 0465; # CYRILLIC CAPITAL LETTER IOTIFIED E
+0466; C; 0467; # CYRILLIC CAPITAL LETTER LITTLE YUS
+0468; C; 0469; # CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS
+046A; C; 046B; # CYRILLIC CAPITAL LETTER BIG YUS
+046C; C; 046D; # CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS
+046E; C; 046F; # CYRILLIC CAPITAL LETTER KSI
+0470; C; 0471; # CYRILLIC CAPITAL LETTER PSI
+0472; C; 0473; # CYRILLIC CAPITAL LETTER FITA
+0474; C; 0475; # CYRILLIC CAPITAL LETTER IZHITSA
+0476; C; 0477; # CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
+0478; C; 0479; # CYRILLIC CAPITAL LETTER UK
+047A; C; 047B; # CYRILLIC CAPITAL LETTER ROUND OMEGA
+047C; C; 047D; # CYRILLIC CAPITAL LETTER OMEGA WITH TITLO
+047E; C; 047F; # CYRILLIC CAPITAL LETTER OT
+0480; C; 0481; # CYRILLIC CAPITAL LETTER KOPPA
+048A; C; 048B; # CYRILLIC CAPITAL LETTER SHORT I WITH TAIL
+048C; C; 048D; # CYRILLIC CAPITAL LETTER SEMISOFT SIGN
+048E; C; 048F; # CYRILLIC CAPITAL LETTER ER WITH TICK
+0490; C; 0491; # CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0492; C; 0493; # CYRILLIC CAPITAL LETTER GHE WITH STROKE
+0494; C; 0495; # CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK
+0496; C; 0497; # CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER
+0498; C; 0499; # CYRILLIC CAPITAL LETTER ZE WITH DESCENDER
+049A; C; 049B; # CYRILLIC CAPITAL LETTER KA WITH DESCENDER
+049C; C; 049D; # CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE
+049E; C; 049F; # CYRILLIC CAPITAL LETTER KA WITH STROKE
+04A0; C; 04A1; # CYRILLIC CAPITAL LETTER BASHKIR KA
+04A2; C; 04A3; # CYRILLIC CAPITAL LETTER EN WITH DESCENDER
+04A4; C; 04A5; # CYRILLIC CAPITAL LIGATURE EN GHE
+04A6; C; 04A7; # CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK
+04A8; C; 04A9; # CYRILLIC CAPITAL LETTER ABKHASIAN HA
+04AA; C; 04AB; # CYRILLIC CAPITAL LETTER ES WITH DESCENDER
+04AC; C; 04AD; # CYRILLIC CAPITAL LETTER TE WITH DESCENDER
+04AE; C; 04AF; # CYRILLIC CAPITAL LETTER STRAIGHT U
+04B0; C; 04B1; # CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE
+04B2; C; 04B3; # CYRILLIC CAPITAL LETTER HA WITH DESCENDER
+04B4; C; 04B5; # CYRILLIC CAPITAL LIGATURE TE TSE
+04B6; C; 04B7; # CYRILLIC CAPITAL LETTER CHE WITH DESCENDER
+04B8; C; 04B9; # CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE
+04BA; C; 04BB; # CYRILLIC CAPITAL LETTER SHHA
+04BC; C; 04BD; # CYRILLIC CAPITAL LETTER ABKHASIAN CHE
+04BE; C; 04BF; # CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER
+04C0; C; 04CF; # CYRILLIC LETTER PALOCHKA
+04C1; C; 04C2; # CYRILLIC CAPITAL LETTER ZHE WITH BREVE
+04C3; C; 04C4; # CYRILLIC CAPITAL LETTER KA WITH HOOK
+04C5; C; 04C6; # CYRILLIC CAPITAL LETTER EL WITH TAIL
+04C7; C; 04C8; # CYRILLIC CAPITAL LETTER EN WITH HOOK
+04C9; C; 04CA; # CYRILLIC CAPITAL LETTER EN WITH TAIL
+04CB; C; 04CC; # CYRILLIC CAPITAL LETTER KHAKASSIAN CHE
+04CD; C; 04CE; # CYRILLIC CAPITAL LETTER EM WITH TAIL
+04D0; C; 04D1; # CYRILLIC CAPITAL LETTER A WITH BREVE
+04D2; C; 04D3; # CYRILLIC CAPITAL LETTER A WITH DIAERESIS
+04D4; C; 04D5; # CYRILLIC CAPITAL LIGATURE A IE
+04D6; C; 04D7; # CYRILLIC CAPITAL LETTER IE WITH BREVE
+04D8; C; 04D9; # CYRILLIC CAPITAL LETTER SCHWA
+04DA; C; 04DB; # CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS
+04DC; C; 04DD; # CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS
+04DE; C; 04DF; # CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS
+04E0; C; 04E1; # CYRILLIC CAPITAL LETTER ABKHASIAN DZE
+04E2; C; 04E3; # CYRILLIC CAPITAL LETTER I WITH MACRON
+04E4; C; 04E5; # CYRILLIC CAPITAL LETTER I WITH DIAERESIS
+04E6; C; 04E7; # CYRILLIC CAPITAL LETTER O WITH DIAERESIS
+04E8; C; 04E9; # CYRILLIC CAPITAL LETTER BARRED O
+04EA; C; 04EB; # CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS
+04EC; C; 04ED; # CYRILLIC CAPITAL LETTER E WITH DIAERESIS
+04EE; C; 04EF; # CYRILLIC CAPITAL LETTER U WITH MACRON
+04F0; C; 04F1; # CYRILLIC CAPITAL LETTER U WITH DIAERESIS
+04F2; C; 04F3; # CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE
+04F4; C; 04F5; # CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS
+04F6; C; 04F7; # CYRILLIC CAPITAL LETTER GHE WITH DESCENDER
+04F8; C; 04F9; # CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS
+04FA; C; 04FB; # CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK
+04FC; C; 04FD; # CYRILLIC CAPITAL LETTER HA WITH HOOK
+04FE; C; 04FF; # CYRILLIC CAPITAL LETTER HA WITH STROKE
+0500; C; 0501; # CYRILLIC CAPITAL LETTER KOMI DE
+0502; C; 0503; # CYRILLIC CAPITAL LETTER KOMI DJE
+0504; C; 0505; # CYRILLIC CAPITAL LETTER KOMI ZJE
+0506; C; 0507; # CYRILLIC CAPITAL LETTER KOMI DZJE
+0508; C; 0509; # CYRILLIC CAPITAL LETTER KOMI LJE
+050A; C; 050B; # CYRILLIC CAPITAL LETTER KOMI NJE
+050C; C; 050D; # CYRILLIC CAPITAL LETTER KOMI SJE
+050E; C; 050F; # CYRILLIC CAPITAL LETTER KOMI TJE
+0510; C; 0511; # CYRILLIC CAPITAL LETTER REVERSED ZE
+0512; C; 0513; # CYRILLIC CAPITAL LETTER EL WITH HOOK
+0514; C; 0515; # CYRILLIC CAPITAL LETTER LHA
+0516; C; 0517; # CYRILLIC CAPITAL LETTER RHA
+0518; C; 0519; # CYRILLIC CAPITAL LETTER YAE
+051A; C; 051B; # CYRILLIC CAPITAL LETTER QA
+051C; C; 051D; # CYRILLIC CAPITAL LETTER WE
+051E; C; 051F; # CYRILLIC CAPITAL LETTER ALEUT KA
+0520; C; 0521; # CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK
+0522; C; 0523; # CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK
+0524; C; 0525; # CYRILLIC CAPITAL LETTER PE WITH DESCENDER
+0526; C; 0527; # CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER
+0528; C; 0529; # CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK
+052A; C; 052B; # CYRILLIC CAPITAL LETTER DZZHE
+052C; C; 052D; # CYRILLIC CAPITAL LETTER DCHE
+052E; C; 052F; # CYRILLIC CAPITAL LETTER EL WITH DESCENDER
+0531; C; 0561; # ARMENIAN CAPITAL LETTER AYB
+0532; C; 0562; # ARMENIAN CAPITAL LETTER BEN
+0533; C; 0563; # ARMENIAN CAPITAL LETTER GIM
+0534; C; 0564; # ARMENIAN CAPITAL LETTER DA
+0535; C; 0565; # ARMENIAN CAPITAL LETTER ECH
+0536; C; 0566; # ARMENIAN CAPITAL LETTER ZA
+0537; C; 0567; # ARMENIAN CAPITAL LETTER EH
+0538; C; 0568; # ARMENIAN CAPITAL LETTER ET
+0539; C; 0569; # ARMENIAN CAPITAL LETTER TO
+053A; C; 056A; # ARMENIAN CAPITAL LETTER ZHE
+053B; C; 056B; # ARMENIAN CAPITAL LETTER INI
+053C; C; 056C; # ARMENIAN CAPITAL LETTER LIWN
+053D; C; 056D; # ARMENIAN CAPITAL LETTER XEH
+053E; C; 056E; # ARMENIAN CAPITAL LETTER CA
+053F; C; 056F; # ARMENIAN CAPITAL LETTER KEN
+0540; C; 0570; # ARMENIAN CAPITAL LETTER HO
+0541; C; 0571; # ARMENIAN CAPITAL LETTER JA
+0542; C; 0572; # ARMENIAN CAPITAL LETTER GHAD
+0543; C; 0573; # ARMENIAN CAPITAL LETTER CHEH
+0544; C; 0574; # ARMENIAN CAPITAL LETTER MEN
+0545; C; 0575; # ARMENIAN CAPITAL LETTER YI
+0546; C; 0576; # ARMENIAN CAPITAL LETTER NOW
+0547; C; 0577; # ARMENIAN CAPITAL LETTER SHA
+0548; C; 0578; # ARMENIAN CAPITAL LETTER VO
+0549; C; 0579; # ARMENIAN CAPITAL LETTER CHA
+054A; C; 057A; # ARMENIAN CAPITAL LETTER PEH
+054B; C; 057B; # ARMENIAN CAPITAL LETTER JHEH
+054C; C; 057C; # ARMENIAN CAPITAL LETTER RA
+054D; C; 057D; # ARMENIAN CAPITAL LETTER SEH
+054E; C; 057E; # ARMENIAN CAPITAL LETTER VEW
+054F; C; 057F; # ARMENIAN CAPITAL LETTER TIWN
+0550; C; 0580; # ARMENIAN CAPITAL LETTER REH
+0551; C; 0581; # ARMENIAN CAPITAL LETTER CO
+0552; C; 0582; # ARMENIAN CAPITAL LETTER YIWN
+0553; C; 0583; # ARMENIAN CAPITAL LETTER PIWR
+0554; C; 0584; # ARMENIAN CAPITAL LETTER KEH
+0555; C; 0585; # ARMENIAN CAPITAL LETTER OH
+0556; C; 0586; # ARMENIAN CAPITAL LETTER FEH
+0587; F; 0565 0582; # ARMENIAN SMALL LIGATURE ECH YIWN
+10A0; C; 2D00; # GEORGIAN CAPITAL LETTER AN
+10A1; C; 2D01; # GEORGIAN CAPITAL LETTER BAN
+10A2; C; 2D02; # GEORGIAN CAPITAL LETTER GAN
+10A3; C; 2D03; # GEORGIAN CAPITAL LETTER DON
+10A4; C; 2D04; # GEORGIAN CAPITAL LETTER EN
+10A5; C; 2D05; # GEORGIAN CAPITAL LETTER VIN
+10A6; C; 2D06; # GEORGIAN CAPITAL LETTER ZEN
+10A7; C; 2D07; # GEORGIAN CAPITAL LETTER TAN
+10A8; C; 2D08; # GEORGIAN CAPITAL LETTER IN
+10A9; C; 2D09; # GEORGIAN CAPITAL LETTER KAN
+10AA; C; 2D0A; # GEORGIAN CAPITAL LETTER LAS
+10AB; C; 2D0B; # GEORGIAN CAPITAL LETTER MAN
+10AC; C; 2D0C; # GEORGIAN CAPITAL LETTER NAR
+10AD; C; 2D0D; # GEORGIAN CAPITAL LETTER ON
+10AE; C; 2D0E; # GEORGIAN CAPITAL LETTER PAR
+10AF; C; 2D0F; # GEORGIAN CAPITAL LETTER ZHAR
+10B0; C; 2D10; # GEORGIAN CAPITAL LETTER RAE
+10B1; C; 2D11; # GEORGIAN CAPITAL LETTER SAN
+10B2; C; 2D12; # GEORGIAN CAPITAL LETTER TAR
+10B3; C; 2D13; # GEORGIAN CAPITAL LETTER UN
+10B4; C; 2D14; # GEORGIAN CAPITAL LETTER PHAR
+10B5; C; 2D15; # GEORGIAN CAPITAL LETTER KHAR
+10B6; C; 2D16; # GEORGIAN CAPITAL LETTER GHAN
+10B7; C; 2D17; # GEORGIAN CAPITAL LETTER QAR
+10B8; C; 2D18; # GEORGIAN CAPITAL LETTER SHIN
+10B9; C; 2D19; # GEORGIAN CAPITAL LETTER CHIN
+10BA; C; 2D1A; # GEORGIAN CAPITAL LETTER CAN
+10BB; C; 2D1B; # GEORGIAN CAPITAL LETTER JIL
+10BC; C; 2D1C; # GEORGIAN CAPITAL LETTER CIL
+10BD; C; 2D1D; # GEORGIAN CAPITAL LETTER CHAR
+10BE; C; 2D1E; # GEORGIAN CAPITAL LETTER XAN
+10BF; C; 2D1F; # GEORGIAN CAPITAL LETTER JHAN
+10C0; C; 2D20; # GEORGIAN CAPITAL LETTER HAE
+10C1; C; 2D21; # GEORGIAN CAPITAL LETTER HE
+10C2; C; 2D22; # GEORGIAN CAPITAL LETTER HIE
+10C3; C; 2D23; # GEORGIAN CAPITAL LETTER WE
+10C4; C; 2D24; # GEORGIAN CAPITAL LETTER HAR
+10C5; C; 2D25; # GEORGIAN CAPITAL LETTER HOE
+10C7; C; 2D27; # GEORGIAN CAPITAL LETTER YN
+10CD; C; 2D2D; # GEORGIAN CAPITAL LETTER AEN
+13F8; C; 13F0; # CHEROKEE SMALL LETTER YE
+13F9; C; 13F1; # CHEROKEE SMALL LETTER YI
+13FA; C; 13F2; # CHEROKEE SMALL LETTER YO
+13FB; C; 13F3; # CHEROKEE SMALL LETTER YU
+13FC; C; 13F4; # CHEROKEE SMALL LETTER YV
+13FD; C; 13F5; # CHEROKEE SMALL LETTER MV
+1E00; C; 1E01; # LATIN CAPITAL LETTER A WITH RING BELOW
+1E02; C; 1E03; # LATIN CAPITAL LETTER B WITH DOT ABOVE
+1E04; C; 1E05; # LATIN CAPITAL LETTER B WITH DOT BELOW
+1E06; C; 1E07; # LATIN CAPITAL LETTER B WITH LINE BELOW
+1E08; C; 1E09; # LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE
+1E0A; C; 1E0B; # LATIN CAPITAL LETTER D WITH DOT ABOVE
+1E0C; C; 1E0D; # LATIN CAPITAL LETTER D WITH DOT BELOW
+1E0E; C; 1E0F; # LATIN CAPITAL LETTER D WITH LINE BELOW
+1E10; C; 1E11; # LATIN CAPITAL LETTER D WITH CEDILLA
+1E12; C; 1E13; # LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW
+1E14; C; 1E15; # LATIN CAPITAL LETTER E WITH MACRON AND GRAVE
+1E16; C; 1E17; # LATIN CAPITAL LETTER E WITH MACRON AND ACUTE
+1E18; C; 1E19; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW
+1E1A; C; 1E1B; # LATIN CAPITAL LETTER E WITH TILDE BELOW
+1E1C; C; 1E1D; # LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE
+1E1E; C; 1E1F; # LATIN CAPITAL LETTER F WITH DOT ABOVE
+1E20; C; 1E21; # LATIN CAPITAL LETTER G WITH MACRON
+1E22; C; 1E23; # LATIN CAPITAL LETTER H WITH DOT ABOVE
+1E24; C; 1E25; # LATIN CAPITAL LETTER H WITH DOT BELOW
+1E26; C; 1E27; # LATIN CAPITAL LETTER H WITH DIAERESIS
+1E28; C; 1E29; # LATIN CAPITAL LETTER H WITH CEDILLA
+1E2A; C; 1E2B; # LATIN CAPITAL LETTER H WITH BREVE BELOW
+1E2C; C; 1E2D; # LATIN CAPITAL LETTER I WITH TILDE BELOW
+1E2E; C; 1E2F; # LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE
+1E30; C; 1E31; # LATIN CAPITAL LETTER K WITH ACUTE
+1E32; C; 1E33; # LATIN CAPITAL LETTER K WITH DOT BELOW
+1E34; C; 1E35; # LATIN CAPITAL LETTER K WITH LINE BELOW
+1E36; C; 1E37; # LATIN CAPITAL LETTER L WITH DOT BELOW
+1E38; C; 1E39; # LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON
+1E3A; C; 1E3B; # LATIN CAPITAL LETTER L WITH LINE BELOW
+1E3C; C; 1E3D; # LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW
+1E3E; C; 1E3F; # LATIN CAPITAL LETTER M WITH ACUTE
+1E40; C; 1E41; # LATIN CAPITAL LETTER M WITH DOT ABOVE
+1E42; C; 1E43; # LATIN CAPITAL LETTER M WITH DOT BELOW
+1E44; C; 1E45; # LATIN CAPITAL LETTER N WITH DOT ABOVE
+1E46; C; 1E47; # LATIN CAPITAL LETTER N WITH DOT BELOW
+1E48; C; 1E49; # LATIN CAPITAL LETTER N WITH LINE BELOW
+1E4A; C; 1E4B; # LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW
+1E4C; C; 1E4D; # LATIN CAPITAL LETTER O WITH TILDE AND ACUTE
+1E4E; C; 1E4F; # LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS
+1E50; C; 1E51; # LATIN CAPITAL LETTER O WITH MACRON AND GRAVE
+1E52; C; 1E53; # LATIN CAPITAL LETTER O WITH MACRON AND ACUTE
+1E54; C; 1E55; # LATIN CAPITAL LETTER P WITH ACUTE
+1E56; C; 1E57; # LATIN CAPITAL LETTER P WITH DOT ABOVE
+1E58; C; 1E59; # LATIN CAPITAL LETTER R WITH DOT ABOVE
+1E5A; C; 1E5B; # LATIN CAPITAL LETTER R WITH DOT BELOW
+1E5C; C; 1E5D; # LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON
+1E5E; C; 1E5F; # LATIN CAPITAL LETTER R WITH LINE BELOW
+1E60; C; 1E61; # LATIN CAPITAL LETTER S WITH DOT ABOVE
+1E62; C; 1E63; # LATIN CAPITAL LETTER S WITH DOT BELOW
+1E64; C; 1E65; # LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE
+1E66; C; 1E67; # LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE
+1E68; C; 1E69; # LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE
+1E6A; C; 1E6B; # LATIN CAPITAL LETTER T WITH DOT ABOVE
+1E6C; C; 1E6D; # LATIN CAPITAL LETTER T WITH DOT BELOW
+1E6E; C; 1E6F; # LATIN CAPITAL LETTER T WITH LINE BELOW
+1E70; C; 1E71; # LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW
+1E72; C; 1E73; # LATIN CAPITAL LETTER U WITH DIAERESIS BELOW
+1E74; C; 1E75; # LATIN CAPITAL LETTER U WITH TILDE BELOW
+1E76; C; 1E77; # LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW
+1E78; C; 1E79; # LATIN CAPITAL LETTER U WITH TILDE AND ACUTE
+1E7A; C; 1E7B; # LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS
+1E7C; C; 1E7D; # LATIN CAPITAL LETTER V WITH TILDE
+1E7E; C; 1E7F; # LATIN CAPITAL LETTER V WITH DOT BELOW
+1E80; C; 1E81; # LATIN CAPITAL LETTER W WITH GRAVE
+1E82; C; 1E83; # LATIN CAPITAL LETTER W WITH ACUTE
+1E84; C; 1E85; # LATIN CAPITAL LETTER W WITH DIAERESIS
+1E86; C; 1E87; # LATIN CAPITAL LETTER W WITH DOT ABOVE
+1E88; C; 1E89; # LATIN CAPITAL LETTER W WITH DOT BELOW
+1E8A; C; 1E8B; # LATIN CAPITAL LETTER X WITH DOT ABOVE
+1E8C; C; 1E8D; # LATIN CAPITAL LETTER X WITH DIAERESIS
+1E8E; C; 1E8F; # LATIN CAPITAL LETTER Y WITH DOT ABOVE
+1E90; C; 1E91; # LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
+1E92; C; 1E93; # LATIN CAPITAL LETTER Z WITH DOT BELOW
+1E94; C; 1E95; # LATIN CAPITAL LETTER Z WITH LINE BELOW
+1E96; F; 0068 0331; # LATIN SMALL LETTER H WITH LINE BELOW
+1E97; F; 0074 0308; # LATIN SMALL LETTER T WITH DIAERESIS
+1E98; F; 0077 030A; # LATIN SMALL LETTER W WITH RING ABOVE
+1E99; F; 0079 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
+1E9A; F; 0061 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
+1E9B; C; 1E61; # LATIN SMALL LETTER LONG S WITH DOT ABOVE
+1E9E; F; 0073 0073; # LATIN CAPITAL LETTER SHARP S
+1E9E; S; 00DF; # LATIN CAPITAL LETTER SHARP S
+1EA0; C; 1EA1; # LATIN CAPITAL LETTER A WITH DOT BELOW
+1EA2; C; 1EA3; # LATIN CAPITAL LETTER A WITH HOOK ABOVE
+1EA4; C; 1EA5; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE
+1EA6; C; 1EA7; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE
+1EA8; C; 1EA9; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
+1EAA; C; 1EAB; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE
+1EAC; C; 1EAD; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW
+1EAE; C; 1EAF; # LATIN CAPITAL LETTER A WITH BREVE AND ACUTE
+1EB0; C; 1EB1; # LATIN CAPITAL LETTER A WITH BREVE AND GRAVE
+1EB2; C; 1EB3; # LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE
+1EB4; C; 1EB5; # LATIN CAPITAL LETTER A WITH BREVE AND TILDE
+1EB6; C; 1EB7; # LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW
+1EB8; C; 1EB9; # LATIN CAPITAL LETTER E WITH DOT BELOW
+1EBA; C; 1EBB; # LATIN CAPITAL LETTER E WITH HOOK ABOVE
+1EBC; C; 1EBD; # LATIN CAPITAL LETTER E WITH TILDE
+1EBE; C; 1EBF; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE
+1EC0; C; 1EC1; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE
+1EC2; C; 1EC3; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
+1EC4; C; 1EC5; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE
+1EC6; C; 1EC7; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW
+1EC8; C; 1EC9; # LATIN CAPITAL LETTER I WITH HOOK ABOVE
+1ECA; C; 1ECB; # LATIN CAPITAL LETTER I WITH DOT BELOW
+1ECC; C; 1ECD; # LATIN CAPITAL LETTER O WITH DOT BELOW
+1ECE; C; 1ECF; # LATIN CAPITAL LETTER O WITH HOOK ABOVE
+1ED0; C; 1ED1; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE
+1ED2; C; 1ED3; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE
+1ED4; C; 1ED5; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
+1ED6; C; 1ED7; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE
+1ED8; C; 1ED9; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW
+1EDA; C; 1EDB; # LATIN CAPITAL LETTER O WITH HORN AND ACUTE
+1EDC; C; 1EDD; # LATIN CAPITAL LETTER O WITH HORN AND GRAVE
+1EDE; C; 1EDF; # LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE
+1EE0; C; 1EE1; # LATIN CAPITAL LETTER O WITH HORN AND TILDE
+1EE2; C; 1EE3; # LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW
+1EE4; C; 1EE5; # LATIN CAPITAL LETTER U WITH DOT BELOW
+1EE6; C; 1EE7; # LATIN CAPITAL LETTER U WITH HOOK ABOVE
+1EE8; C; 1EE9; # LATIN CAPITAL LETTER U WITH HORN AND ACUTE
+1EEA; C; 1EEB; # LATIN CAPITAL LETTER U WITH HORN AND GRAVE
+1EEC; C; 1EED; # LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE
+1EEE; C; 1EEF; # LATIN CAPITAL LETTER U WITH HORN AND TILDE
+1EF0; C; 1EF1; # LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW
+1EF2; C; 1EF3; # LATIN CAPITAL LETTER Y WITH GRAVE
+1EF4; C; 1EF5; # LATIN CAPITAL LETTER Y WITH DOT BELOW
+1EF6; C; 1EF7; # LATIN CAPITAL LETTER Y WITH HOOK ABOVE
+1EF8; C; 1EF9; # LATIN CAPITAL LETTER Y WITH TILDE
+1EFA; C; 1EFB; # LATIN CAPITAL LETTER MIDDLE-WELSH LL
+1EFC; C; 1EFD; # LATIN CAPITAL LETTER MIDDLE-WELSH V
+1EFE; C; 1EFF; # LATIN CAPITAL LETTER Y WITH LOOP
+1F08; C; 1F00; # GREEK CAPITAL LETTER ALPHA WITH PSILI
+1F09; C; 1F01; # GREEK CAPITAL LETTER ALPHA WITH DASIA
+1F0A; C; 1F02; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA
+1F0B; C; 1F03; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA
+1F0C; C; 1F04; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA
+1F0D; C; 1F05; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA
+1F0E; C; 1F06; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI
+1F0F; C; 1F07; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI
+1F18; C; 1F10; # GREEK CAPITAL LETTER EPSILON WITH PSILI
+1F19; C; 1F11; # GREEK CAPITAL LETTER EPSILON WITH DASIA
+1F1A; C; 1F12; # GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA
+1F1B; C; 1F13; # GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA
+1F1C; C; 1F14; # GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA
+1F1D; C; 1F15; # GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
+1F28; C; 1F20; # GREEK CAPITAL LETTER ETA WITH PSILI
+1F29; C; 1F21; # GREEK CAPITAL LETTER ETA WITH DASIA
+1F2A; C; 1F22; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA
+1F2B; C; 1F23; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA
+1F2C; C; 1F24; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA
+1F2D; C; 1F25; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA
+1F2E; C; 1F26; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI
+1F2F; C; 1F27; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI
+1F38; C; 1F30; # GREEK CAPITAL LETTER IOTA WITH PSILI
+1F39; C; 1F31; # GREEK CAPITAL LETTER IOTA WITH DASIA
+1F3A; C; 1F32; # GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA
+1F3B; C; 1F33; # GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA
+1F3C; C; 1F34; # GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA
+1F3D; C; 1F35; # GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA
+1F3E; C; 1F36; # GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI
+1F3F; C; 1F37; # GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI
+1F48; C; 1F40; # GREEK CAPITAL LETTER OMICRON WITH PSILI
+1F49; C; 1F41; # GREEK CAPITAL LETTER OMICRON WITH DASIA
+1F4A; C; 1F42; # GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA
+1F4B; C; 1F43; # GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA
+1F4C; C; 1F44; # GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA
+1F4D; C; 1F45; # GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
+1F50; F; 03C5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
+1F52; F; 03C5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
+1F54; F; 03C5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
+1F56; F; 03C5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
+1F59; C; 1F51; # GREEK CAPITAL LETTER UPSILON WITH DASIA
+1F5B; C; 1F53; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
+1F5D; C; 1F55; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
+1F5F; C; 1F57; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI
+1F68; C; 1F60; # GREEK CAPITAL LETTER OMEGA WITH PSILI
+1F69; C; 1F61; # GREEK CAPITAL LETTER OMEGA WITH DASIA
+1F6A; C; 1F62; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA
+1F6B; C; 1F63; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA
+1F6C; C; 1F64; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA
+1F6D; C; 1F65; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA
+1F6E; C; 1F66; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI
+1F6F; C; 1F67; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI
+1F80; F; 1F00 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
+1F81; F; 1F01 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
+1F82; F; 1F02 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F83; F; 1F03 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F84; F; 1F04 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F85; F; 1F05 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F86; F; 1F06 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F87; F; 1F07 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F88; F; 1F00 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F88; S; 1F80; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
+1F89; F; 1F01 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F89; S; 1F81; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
+1F8A; F; 1F02 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8A; S; 1F82; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F8B; F; 1F03 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8B; S; 1F83; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F8C; F; 1F04 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8C; S; 1F84; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F8D; F; 1F05 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8D; S; 1F85; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F8E; F; 1F06 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8E; S; 1F86; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F; F; 1F07 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F8F; S; 1F87; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F90; F; 1F20 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
+1F91; F; 1F21 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
+1F92; F; 1F22 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1F93; F; 1F23 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1F94; F; 1F24 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1F95; F; 1F25 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1F96; F; 1F26 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1F97; F; 1F27 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1F98; F; 1F20 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F98; S; 1F90; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
+1F99; F; 1F21 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F99; S; 1F91; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
+1F9A; F; 1F22 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9A; S; 1F92; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1F9B; F; 1F23 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9B; S; 1F93; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1F9C; F; 1F24 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9C; S; 1F94; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1F9D; F; 1F25 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9D; S; 1F95; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1F9E; F; 1F26 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9E; S; 1F96; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F; F; 1F27 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1F9F; S; 1F97; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FA0; F; 1F60 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
+1FA1; F; 1F61 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
+1FA2; F; 1F62 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
+1FA3; F; 1F63 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
+1FA4; F; 1F64 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
+1FA5; F; 1F65 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
+1FA6; F; 1F66 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
+1FA7; F; 1F67 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
+1FA8; F; 1F60 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA8; S; 1FA0; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
+1FA9; F; 1F61 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FA9; S; 1FA1; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
+1FAA; F; 1F62 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAA; S; 1FA2; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
+1FAB; F; 1F63 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAB; S; 1FA3; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
+1FAC; F; 1F64 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAC; S; 1FA4; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
+1FAD; F; 1F65 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAD; S; 1FA5; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
+1FAE; F; 1F66 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAE; S; 1FA6; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF; F; 1F67 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FAF; S; 1FA7; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
+1FB2; F; 1F70 03B9; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
+1FB3; F; 03B1 03B9; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
+1FB4; F; 03AC 03B9; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
+1FB6; F; 03B1 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
+1FB7; F; 03B1 0342 03B9; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FB8; C; 1FB0; # GREEK CAPITAL LETTER ALPHA WITH VRACHY
+1FB9; C; 1FB1; # GREEK CAPITAL LETTER ALPHA WITH MACRON
+1FBA; C; 1F70; # GREEK CAPITAL LETTER ALPHA WITH VARIA
+1FBB; C; 1F71; # GREEK CAPITAL LETTER ALPHA WITH OXIA
+1FBC; F; 03B1 03B9; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBC; S; 1FB3; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
+1FBE; C; 03B9; # GREEK PROSGEGRAMMENI
+1FC2; F; 1F74 03B9; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
+1FC3; F; 03B7 03B9; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
+1FC4; F; 03AE 03B9; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
+1FC6; F; 03B7 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
+1FC7; F; 03B7 0342 03B9; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FC8; C; 1F72; # GREEK CAPITAL LETTER EPSILON WITH VARIA
+1FC9; C; 1F73; # GREEK CAPITAL LETTER EPSILON WITH OXIA
+1FCA; C; 1F74; # GREEK CAPITAL LETTER ETA WITH VARIA
+1FCB; C; 1F75; # GREEK CAPITAL LETTER ETA WITH OXIA
+1FCC; F; 03B7 03B9; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FCC; S; 1FC3; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
+1FD2; F; 03B9 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
+1FD3; F; 03B9 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
+1FD6; F; 03B9 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
+1FD7; F; 03B9 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
+1FD8; C; 1FD0; # GREEK CAPITAL LETTER IOTA WITH VRACHY
+1FD9; C; 1FD1; # GREEK CAPITAL LETTER IOTA WITH MACRON
+1FDA; C; 1F76; # GREEK CAPITAL LETTER IOTA WITH VARIA
+1FDB; C; 1F77; # GREEK CAPITAL LETTER IOTA WITH OXIA
+1FE2; F; 03C5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
+1FE3; F; 03C5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
+1FE4; F; 03C1 0313; # GREEK SMALL LETTER RHO WITH PSILI
+1FE6; F; 03C5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
+1FE7; F; 03C5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
+1FE8; C; 1FE0; # GREEK CAPITAL LETTER UPSILON WITH VRACHY
+1FE9; C; 1FE1; # GREEK CAPITAL LETTER UPSILON WITH MACRON
+1FEA; C; 1F7A; # GREEK CAPITAL LETTER UPSILON WITH VARIA
+1FEB; C; 1F7B; # GREEK CAPITAL LETTER UPSILON WITH OXIA
+1FEC; C; 1FE5; # GREEK CAPITAL LETTER RHO WITH DASIA
+1FF2; F; 1F7C 03B9; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
+1FF3; F; 03C9 03B9; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
+1FF4; F; 03CE 03B9; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
+1FF6; F; 03C9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
+1FF7; F; 03C9 0342 03B9; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
+1FF8; C; 1F78; # GREEK CAPITAL LETTER OMICRON WITH VARIA
+1FF9; C; 1F79; # GREEK CAPITAL LETTER OMICRON WITH OXIA
+1FFA; C; 1F7C; # GREEK CAPITAL LETTER OMEGA WITH VARIA
+1FFB; C; 1F7D; # GREEK CAPITAL LETTER OMEGA WITH OXIA
+1FFC; F; 03C9 03B9; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+1FFC; S; 1FF3; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
+2126; C; 03C9; # OHM SIGN
+212A; C; 006B; # KELVIN SIGN
+212B; C; 00E5; # ANGSTROM SIGN
+2132; C; 214E; # TURNED CAPITAL F
+2160; C; 2170; # ROMAN NUMERAL ONE
+2161; C; 2171; # ROMAN NUMERAL TWO
+2162; C; 2172; # ROMAN NUMERAL THREE
+2163; C; 2173; # ROMAN NUMERAL FOUR
+2164; C; 2174; # ROMAN NUMERAL FIVE
+2165; C; 2175; # ROMAN NUMERAL SIX
+2166; C; 2176; # ROMAN NUMERAL SEVEN
+2167; C; 2177; # ROMAN NUMERAL EIGHT
+2168; C; 2178; # ROMAN NUMERAL NINE
+2169; C; 2179; # ROMAN NUMERAL TEN
+216A; C; 217A; # ROMAN NUMERAL ELEVEN
+216B; C; 217B; # ROMAN NUMERAL TWELVE
+216C; C; 217C; # ROMAN NUMERAL FIFTY
+216D; C; 217D; # ROMAN NUMERAL ONE HUNDRED
+216E; C; 217E; # ROMAN NUMERAL FIVE HUNDRED
+216F; C; 217F; # ROMAN NUMERAL ONE THOUSAND
+2183; C; 2184; # ROMAN NUMERAL REVERSED ONE HUNDRED
+24B6; C; 24D0; # CIRCLED LATIN CAPITAL LETTER A
+24B7; C; 24D1; # CIRCLED LATIN CAPITAL LETTER B
+24B8; C; 24D2; # CIRCLED LATIN CAPITAL LETTER C
+24B9; C; 24D3; # CIRCLED LATIN CAPITAL LETTER D
+24BA; C; 24D4; # CIRCLED LATIN CAPITAL LETTER E
+24BB; C; 24D5; # CIRCLED LATIN CAPITAL LETTER F
+24BC; C; 24D6; # CIRCLED LATIN CAPITAL LETTER G
+24BD; C; 24D7; # CIRCLED LATIN CAPITAL LETTER H
+24BE; C; 24D8; # CIRCLED LATIN CAPITAL LETTER I
+24BF; C; 24D9; # CIRCLED LATIN CAPITAL LETTER J
+24C0; C; 24DA; # CIRCLED LATIN CAPITAL LETTER K
+24C1; C; 24DB; # CIRCLED LATIN CAPITAL LETTER L
+24C2; C; 24DC; # CIRCLED LATIN CAPITAL LETTER M
+24C3; C; 24DD; # CIRCLED LATIN CAPITAL LETTER N
+24C4; C; 24DE; # CIRCLED LATIN CAPITAL LETTER O
+24C5; C; 24DF; # CIRCLED LATIN CAPITAL LETTER P
+24C6; C; 24E0; # CIRCLED LATIN CAPITAL LETTER Q
+24C7; C; 24E1; # CIRCLED LATIN CAPITAL LETTER R
+24C8; C; 24E2; # CIRCLED LATIN CAPITAL LETTER S
+24C9; C; 24E3; # CIRCLED LATIN CAPITAL LETTER T
+24CA; C; 24E4; # CIRCLED LATIN CAPITAL LETTER U
+24CB; C; 24E5; # CIRCLED LATIN CAPITAL LETTER V
+24CC; C; 24E6; # CIRCLED LATIN CAPITAL LETTER W
+24CD; C; 24E7; # CIRCLED LATIN CAPITAL LETTER X
+24CE; C; 24E8; # CIRCLED LATIN CAPITAL LETTER Y
+24CF; C; 24E9; # CIRCLED LATIN CAPITAL LETTER Z
+2C00; C; 2C30; # GLAGOLITIC CAPITAL LETTER AZU
+2C01; C; 2C31; # GLAGOLITIC CAPITAL LETTER BUKY
+2C02; C; 2C32; # GLAGOLITIC CAPITAL LETTER VEDE
+2C03; C; 2C33; # GLAGOLITIC CAPITAL LETTER GLAGOLI
+2C04; C; 2C34; # GLAGOLITIC CAPITAL LETTER DOBRO
+2C05; C; 2C35; # GLAGOLITIC CAPITAL LETTER YESTU
+2C06; C; 2C36; # GLAGOLITIC CAPITAL LETTER ZHIVETE
+2C07; C; 2C37; # GLAGOLITIC CAPITAL LETTER DZELO
+2C08; C; 2C38; # GLAGOLITIC CAPITAL LETTER ZEMLJA
+2C09; C; 2C39; # GLAGOLITIC CAPITAL LETTER IZHE
+2C0A; C; 2C3A; # GLAGOLITIC CAPITAL LETTER INITIAL IZHE
+2C0B; C; 2C3B; # GLAGOLITIC CAPITAL LETTER I
+2C0C; C; 2C3C; # GLAGOLITIC CAPITAL LETTER DJERVI
+2C0D; C; 2C3D; # GLAGOLITIC CAPITAL LETTER KAKO
+2C0E; C; 2C3E; # GLAGOLITIC CAPITAL LETTER LJUDIJE
+2C0F; C; 2C3F; # GLAGOLITIC CAPITAL LETTER MYSLITE
+2C10; C; 2C40; # GLAGOLITIC CAPITAL LETTER NASHI
+2C11; C; 2C41; # GLAGOLITIC CAPITAL LETTER ONU
+2C12; C; 2C42; # GLAGOLITIC CAPITAL LETTER POKOJI
+2C13; C; 2C43; # GLAGOLITIC CAPITAL LETTER RITSI
+2C14; C; 2C44; # GLAGOLITIC CAPITAL LETTER SLOVO
+2C15; C; 2C45; # GLAGOLITIC CAPITAL LETTER TVRIDO
+2C16; C; 2C46; # GLAGOLITIC CAPITAL LETTER UKU
+2C17; C; 2C47; # GLAGOLITIC CAPITAL LETTER FRITU
+2C18; C; 2C48; # GLAGOLITIC CAPITAL LETTER HERU
+2C19; C; 2C49; # GLAGOLITIC CAPITAL LETTER OTU
+2C1A; C; 2C4A; # GLAGOLITIC CAPITAL LETTER PE
+2C1B; C; 2C4B; # GLAGOLITIC CAPITAL LETTER SHTA
+2C1C; C; 2C4C; # GLAGOLITIC CAPITAL LETTER TSI
+2C1D; C; 2C4D; # GLAGOLITIC CAPITAL LETTER CHRIVI
+2C1E; C; 2C4E; # GLAGOLITIC CAPITAL LETTER SHA
+2C1F; C; 2C4F; # GLAGOLITIC CAPITAL LETTER YERU
+2C20; C; 2C50; # GLAGOLITIC CAPITAL LETTER YERI
+2C21; C; 2C51; # GLAGOLITIC CAPITAL LETTER YATI
+2C22; C; 2C52; # GLAGOLITIC CAPITAL LETTER SPIDERY HA
+2C23; C; 2C53; # GLAGOLITIC CAPITAL LETTER YU
+2C24; C; 2C54; # GLAGOLITIC CAPITAL LETTER SMALL YUS
+2C25; C; 2C55; # GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL
+2C26; C; 2C56; # GLAGOLITIC CAPITAL LETTER YO
+2C27; C; 2C57; # GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS
+2C28; C; 2C58; # GLAGOLITIC CAPITAL LETTER BIG YUS
+2C29; C; 2C59; # GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS
+2C2A; C; 2C5A; # GLAGOLITIC CAPITAL LETTER FITA
+2C2B; C; 2C5B; # GLAGOLITIC CAPITAL LETTER IZHITSA
+2C2C; C; 2C5C; # GLAGOLITIC CAPITAL LETTER SHTAPIC
+2C2D; C; 2C5D; # GLAGOLITIC CAPITAL LETTER TROKUTASTI A
+2C2E; C; 2C5E; # GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE
+2C60; C; 2C61; # LATIN CAPITAL LETTER L WITH DOUBLE BAR
+2C62; C; 026B; # LATIN CAPITAL LETTER L WITH MIDDLE TILDE
+2C63; C; 1D7D; # LATIN CAPITAL LETTER P WITH STROKE
+2C64; C; 027D; # LATIN CAPITAL LETTER R WITH TAIL
+2C67; C; 2C68; # LATIN CAPITAL LETTER H WITH DESCENDER
+2C69; C; 2C6A; # LATIN CAPITAL LETTER K WITH DESCENDER
+2C6B; C; 2C6C; # LATIN CAPITAL LETTER Z WITH DESCENDER
+2C6D; C; 0251; # LATIN CAPITAL LETTER ALPHA
+2C6E; C; 0271; # LATIN CAPITAL LETTER M WITH HOOK
+2C6F; C; 0250; # LATIN CAPITAL LETTER TURNED A
+2C70; C; 0252; # LATIN CAPITAL LETTER TURNED ALPHA
+2C72; C; 2C73; # LATIN CAPITAL LETTER W WITH HOOK
+2C75; C; 2C76; # LATIN CAPITAL LETTER HALF H
+2C7E; C; 023F; # LATIN CAPITAL LETTER S WITH SWASH TAIL
+2C7F; C; 0240; # LATIN CAPITAL LETTER Z WITH SWASH TAIL
+2C80; C; 2C81; # COPTIC CAPITAL LETTER ALFA
+2C82; C; 2C83; # COPTIC CAPITAL LETTER VIDA
+2C84; C; 2C85; # COPTIC CAPITAL LETTER GAMMA
+2C86; C; 2C87; # COPTIC CAPITAL LETTER DALDA
+2C88; C; 2C89; # COPTIC CAPITAL LETTER EIE
+2C8A; C; 2C8B; # COPTIC CAPITAL LETTER SOU
+2C8C; C; 2C8D; # COPTIC CAPITAL LETTER ZATA
+2C8E; C; 2C8F; # COPTIC CAPITAL LETTER HATE
+2C90; C; 2C91; # COPTIC CAPITAL LETTER THETHE
+2C92; C; 2C93; # COPTIC CAPITAL LETTER IAUDA
+2C94; C; 2C95; # COPTIC CAPITAL LETTER KAPA
+2C96; C; 2C97; # COPTIC CAPITAL LETTER LAULA
+2C98; C; 2C99; # COPTIC CAPITAL LETTER MI
+2C9A; C; 2C9B; # COPTIC CAPITAL LETTER NI
+2C9C; C; 2C9D; # COPTIC CAPITAL LETTER KSI
+2C9E; C; 2C9F; # COPTIC CAPITAL LETTER O
+2CA0; C; 2CA1; # COPTIC CAPITAL LETTER PI
+2CA2; C; 2CA3; # COPTIC CAPITAL LETTER RO
+2CA4; C; 2CA5; # COPTIC CAPITAL LETTER SIMA
+2CA6; C; 2CA7; # COPTIC CAPITAL LETTER TAU
+2CA8; C; 2CA9; # COPTIC CAPITAL LETTER UA
+2CAA; C; 2CAB; # COPTIC CAPITAL LETTER FI
+2CAC; C; 2CAD; # COPTIC CAPITAL LETTER KHI
+2CAE; C; 2CAF; # COPTIC CAPITAL LETTER PSI
+2CB0; C; 2CB1; # COPTIC CAPITAL LETTER OOU
+2CB2; C; 2CB3; # COPTIC CAPITAL LETTER DIALECT-P ALEF
+2CB4; C; 2CB5; # COPTIC CAPITAL LETTER OLD COPTIC AIN
+2CB6; C; 2CB7; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE
+2CB8; C; 2CB9; # COPTIC CAPITAL LETTER DIALECT-P KAPA
+2CBA; C; 2CBB; # COPTIC CAPITAL LETTER DIALECT-P NI
+2CBC; C; 2CBD; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI
+2CBE; C; 2CBF; # COPTIC CAPITAL LETTER OLD COPTIC OOU
+2CC0; C; 2CC1; # COPTIC CAPITAL LETTER SAMPI
+2CC2; C; 2CC3; # COPTIC CAPITAL LETTER CROSSED SHEI
+2CC4; C; 2CC5; # COPTIC CAPITAL LETTER OLD COPTIC SHEI
+2CC6; C; 2CC7; # COPTIC CAPITAL LETTER OLD COPTIC ESH
+2CC8; C; 2CC9; # COPTIC CAPITAL LETTER AKHMIMIC KHEI
+2CCA; C; 2CCB; # COPTIC CAPITAL LETTER DIALECT-P HORI
+2CCC; C; 2CCD; # COPTIC CAPITAL LETTER OLD COPTIC HORI
+2CCE; C; 2CCF; # COPTIC CAPITAL LETTER OLD COPTIC HA
+2CD0; C; 2CD1; # COPTIC CAPITAL LETTER L-SHAPED HA
+2CD2; C; 2CD3; # COPTIC CAPITAL LETTER OLD COPTIC HEI
+2CD4; C; 2CD5; # COPTIC CAPITAL LETTER OLD COPTIC HAT
+2CD6; C; 2CD7; # COPTIC CAPITAL LETTER OLD COPTIC GANGIA
+2CD8; C; 2CD9; # COPTIC CAPITAL LETTER OLD COPTIC DJA
+2CDA; C; 2CDB; # COPTIC CAPITAL LETTER OLD COPTIC SHIMA
+2CDC; C; 2CDD; # COPTIC CAPITAL LETTER OLD NUBIAN SHIMA
+2CDE; C; 2CDF; # COPTIC CAPITAL LETTER OLD NUBIAN NGI
+2CE0; C; 2CE1; # COPTIC CAPITAL LETTER OLD NUBIAN NYI
+2CE2; C; 2CE3; # COPTIC CAPITAL LETTER OLD NUBIAN WAU
+2CEB; C; 2CEC; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI
+2CED; C; 2CEE; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA
+2CF2; C; 2CF3; # COPTIC CAPITAL LETTER BOHAIRIC KHEI
+A640; C; A641; # CYRILLIC CAPITAL LETTER ZEMLYA
+A642; C; A643; # CYRILLIC CAPITAL LETTER DZELO
+A644; C; A645; # CYRILLIC CAPITAL LETTER REVERSED DZE
+A646; C; A647; # CYRILLIC CAPITAL LETTER IOTA
+A648; C; A649; # CYRILLIC CAPITAL LETTER DJERV
+A64A; C; A64B; # CYRILLIC CAPITAL LETTER MONOGRAPH UK
+A64C; C; A64D; # CYRILLIC CAPITAL LETTER BROAD OMEGA
+A64E; C; A64F; # CYRILLIC CAPITAL LETTER NEUTRAL YER
+A650; C; A651; # CYRILLIC CAPITAL LETTER YERU WITH BACK YER
+A652; C; A653; # CYRILLIC CAPITAL LETTER IOTIFIED YAT
+A654; C; A655; # CYRILLIC CAPITAL LETTER REVERSED YU
+A656; C; A657; # CYRILLIC CAPITAL LETTER IOTIFIED A
+A658; C; A659; # CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS
+A65A; C; A65B; # CYRILLIC CAPITAL LETTER BLENDED YUS
+A65C; C; A65D; # CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS
+A65E; C; A65F; # CYRILLIC CAPITAL LETTER YN
+A660; C; A661; # CYRILLIC CAPITAL LETTER REVERSED TSE
+A662; C; A663; # CYRILLIC CAPITAL LETTER SOFT DE
+A664; C; A665; # CYRILLIC CAPITAL LETTER SOFT EL
+A666; C; A667; # CYRILLIC CAPITAL LETTER SOFT EM
+A668; C; A669; # CYRILLIC CAPITAL LETTER MONOCULAR O
+A66A; C; A66B; # CYRILLIC CAPITAL LETTER BINOCULAR O
+A66C; C; A66D; # CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O
+A680; C; A681; # CYRILLIC CAPITAL LETTER DWE
+A682; C; A683; # CYRILLIC CAPITAL LETTER DZWE
+A684; C; A685; # CYRILLIC CAPITAL LETTER ZHWE
+A686; C; A687; # CYRILLIC CAPITAL LETTER CCHE
+A688; C; A689; # CYRILLIC CAPITAL LETTER DZZE
+A68A; C; A68B; # CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK
+A68C; C; A68D; # CYRILLIC CAPITAL LETTER TWE
+A68E; C; A68F; # CYRILLIC CAPITAL LETTER TSWE
+A690; C; A691; # CYRILLIC CAPITAL LETTER TSSE
+A692; C; A693; # CYRILLIC CAPITAL LETTER TCHE
+A694; C; A695; # CYRILLIC CAPITAL LETTER HWE
+A696; C; A697; # CYRILLIC CAPITAL LETTER SHWE
+A698; C; A699; # CYRILLIC CAPITAL LETTER DOUBLE O
+A69A; C; A69B; # CYRILLIC CAPITAL LETTER CROSSED O
+A722; C; A723; # LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF
+A724; C; A725; # LATIN CAPITAL LETTER EGYPTOLOGICAL AIN
+A726; C; A727; # LATIN CAPITAL LETTER HENG
+A728; C; A729; # LATIN CAPITAL LETTER TZ
+A72A; C; A72B; # LATIN CAPITAL LETTER TRESILLO
+A72C; C; A72D; # LATIN CAPITAL LETTER CUATRILLO
+A72E; C; A72F; # LATIN CAPITAL LETTER CUATRILLO WITH COMMA
+A732; C; A733; # LATIN CAPITAL LETTER AA
+A734; C; A735; # LATIN CAPITAL LETTER AO
+A736; C; A737; # LATIN CAPITAL LETTER AU
+A738; C; A739; # LATIN CAPITAL LETTER AV
+A73A; C; A73B; # LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR
+A73C; C; A73D; # LATIN CAPITAL LETTER AY
+A73E; C; A73F; # LATIN CAPITAL LETTER REVERSED C WITH DOT
+A740; C; A741; # LATIN CAPITAL LETTER K WITH STROKE
+A742; C; A743; # LATIN CAPITAL LETTER K WITH DIAGONAL STROKE
+A744; C; A745; # LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE
+A746; C; A747; # LATIN CAPITAL LETTER BROKEN L
+A748; C; A749; # LATIN CAPITAL LETTER L WITH HIGH STROKE
+A74A; C; A74B; # LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY
+A74C; C; A74D; # LATIN CAPITAL LETTER O WITH LOOP
+A74E; C; A74F; # LATIN CAPITAL LETTER OO
+A750; C; A751; # LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER
+A752; C; A753; # LATIN CAPITAL LETTER P WITH FLOURISH
+A754; C; A755; # LATIN CAPITAL LETTER P WITH SQUIRREL TAIL
+A756; C; A757; # LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER
+A758; C; A759; # LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE
+A75A; C; A75B; # LATIN CAPITAL LETTER R ROTUNDA
+A75C; C; A75D; # LATIN CAPITAL LETTER RUM ROTUNDA
+A75E; C; A75F; # LATIN CAPITAL LETTER V WITH DIAGONAL STROKE
+A760; C; A761; # LATIN CAPITAL LETTER VY
+A762; C; A763; # LATIN CAPITAL LETTER VISIGOTHIC Z
+A764; C; A765; # LATIN CAPITAL LETTER THORN WITH STROKE
+A766; C; A767; # LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER
+A768; C; A769; # LATIN CAPITAL LETTER VEND
+A76A; C; A76B; # LATIN CAPITAL LETTER ET
+A76C; C; A76D; # LATIN CAPITAL LETTER IS
+A76E; C; A76F; # LATIN CAPITAL LETTER CON
+A779; C; A77A; # LATIN CAPITAL LETTER INSULAR D
+A77B; C; A77C; # LATIN CAPITAL LETTER INSULAR F
+A77D; C; 1D79; # LATIN CAPITAL LETTER INSULAR G
+A77E; C; A77F; # LATIN CAPITAL LETTER TURNED INSULAR G
+A780; C; A781; # LATIN CAPITAL LETTER TURNED L
+A782; C; A783; # LATIN CAPITAL LETTER INSULAR R
+A784; C; A785; # LATIN CAPITAL LETTER INSULAR S
+A786; C; A787; # LATIN CAPITAL LETTER INSULAR T
+A78B; C; A78C; # LATIN CAPITAL LETTER SALTILLO
+A78D; C; 0265; # LATIN CAPITAL LETTER TURNED H
+A790; C; A791; # LATIN CAPITAL LETTER N WITH DESCENDER
+A792; C; A793; # LATIN CAPITAL LETTER C WITH BAR
+A796; C; A797; # LATIN CAPITAL LETTER B WITH FLOURISH
+A798; C; A799; # LATIN CAPITAL LETTER F WITH STROKE
+A79A; C; A79B; # LATIN CAPITAL LETTER VOLAPUK AE
+A79C; C; A79D; # LATIN CAPITAL LETTER VOLAPUK OE
+A79E; C; A79F; # LATIN CAPITAL LETTER VOLAPUK UE
+A7A0; C; A7A1; # LATIN CAPITAL LETTER G WITH OBLIQUE STROKE
+A7A2; C; A7A3; # LATIN CAPITAL LETTER K WITH OBLIQUE STROKE
+A7A4; C; A7A5; # LATIN CAPITAL LETTER N WITH OBLIQUE STROKE
+A7A6; C; A7A7; # LATIN CAPITAL LETTER R WITH OBLIQUE STROKE
+A7A8; C; A7A9; # LATIN CAPITAL LETTER S WITH OBLIQUE STROKE
+A7AA; C; 0266; # LATIN CAPITAL LETTER H WITH HOOK
+A7AB; C; 025C; # LATIN CAPITAL LETTER REVERSED OPEN E
+A7AC; C; 0261; # LATIN CAPITAL LETTER SCRIPT G
+A7AD; C; 026C; # LATIN CAPITAL LETTER L WITH BELT
+A7B0; C; 029E; # LATIN CAPITAL LETTER TURNED K
+A7B1; C; 0287; # LATIN CAPITAL LETTER TURNED T
+A7B2; C; 029D; # LATIN CAPITAL LETTER J WITH CROSSED-TAIL
+A7B3; C; AB53; # LATIN CAPITAL LETTER CHI
+A7B4; C; A7B5; # LATIN CAPITAL LETTER BETA
+A7B6; C; A7B7; # LATIN CAPITAL LETTER OMEGA
+AB70; C; 13A0; # CHEROKEE SMALL LETTER A
+AB71; C; 13A1; # CHEROKEE SMALL LETTER E
+AB72; C; 13A2; # CHEROKEE SMALL LETTER I
+AB73; C; 13A3; # CHEROKEE SMALL LETTER O
+AB74; C; 13A4; # CHEROKEE SMALL LETTER U
+AB75; C; 13A5; # CHEROKEE SMALL LETTER V
+AB76; C; 13A6; # CHEROKEE SMALL LETTER GA
+AB77; C; 13A7; # CHEROKEE SMALL LETTER KA
+AB78; C; 13A8; # CHEROKEE SMALL LETTER GE
+AB79; C; 13A9; # CHEROKEE SMALL LETTER GI
+AB7A; C; 13AA; # CHEROKEE SMALL LETTER GO
+AB7B; C; 13AB; # CHEROKEE SMALL LETTER GU
+AB7C; C; 13AC; # CHEROKEE SMALL LETTER GV
+AB7D; C; 13AD; # CHEROKEE SMALL LETTER HA
+AB7E; C; 13AE; # CHEROKEE SMALL LETTER HE
+AB7F; C; 13AF; # CHEROKEE SMALL LETTER HI
+AB80; C; 13B0; # CHEROKEE SMALL LETTER HO
+AB81; C; 13B1; # CHEROKEE SMALL LETTER HU
+AB82; C; 13B2; # CHEROKEE SMALL LETTER HV
+AB83; C; 13B3; # CHEROKEE SMALL LETTER LA
+AB84; C; 13B4; # CHEROKEE SMALL LETTER LE
+AB85; C; 13B5; # CHEROKEE SMALL LETTER LI
+AB86; C; 13B6; # CHEROKEE SMALL LETTER LO
+AB87; C; 13B7; # CHEROKEE SMALL LETTER LU
+AB88; C; 13B8; # CHEROKEE SMALL LETTER LV
+AB89; C; 13B9; # CHEROKEE SMALL LETTER MA
+AB8A; C; 13BA; # CHEROKEE SMALL LETTER ME
+AB8B; C; 13BB; # CHEROKEE SMALL LETTER MI
+AB8C; C; 13BC; # CHEROKEE SMALL LETTER MO
+AB8D; C; 13BD; # CHEROKEE SMALL LETTER MU
+AB8E; C; 13BE; # CHEROKEE SMALL LETTER NA
+AB8F; C; 13BF; # CHEROKEE SMALL LETTER HNA
+AB90; C; 13C0; # CHEROKEE SMALL LETTER NAH
+AB91; C; 13C1; # CHEROKEE SMALL LETTER NE
+AB92; C; 13C2; # CHEROKEE SMALL LETTER NI
+AB93; C; 13C3; # CHEROKEE SMALL LETTER NO
+AB94; C; 13C4; # CHEROKEE SMALL LETTER NU
+AB95; C; 13C5; # CHEROKEE SMALL LETTER NV
+AB96; C; 13C6; # CHEROKEE SMALL LETTER QUA
+AB97; C; 13C7; # CHEROKEE SMALL LETTER QUE
+AB98; C; 13C8; # CHEROKEE SMALL LETTER QUI
+AB99; C; 13C9; # CHEROKEE SMALL LETTER QUO
+AB9A; C; 13CA; # CHEROKEE SMALL LETTER QUU
+AB9B; C; 13CB; # CHEROKEE SMALL LETTER QUV
+AB9C; C; 13CC; # CHEROKEE SMALL LETTER SA
+AB9D; C; 13CD; # CHEROKEE SMALL LETTER S
+AB9E; C; 13CE; # CHEROKEE SMALL LETTER SE
+AB9F; C; 13CF; # CHEROKEE SMALL LETTER SI
+ABA0; C; 13D0; # CHEROKEE SMALL LETTER SO
+ABA1; C; 13D1; # CHEROKEE SMALL LETTER SU
+ABA2; C; 13D2; # CHEROKEE SMALL LETTER SV
+ABA3; C; 13D3; # CHEROKEE SMALL LETTER DA
+ABA4; C; 13D4; # CHEROKEE SMALL LETTER TA
+ABA5; C; 13D5; # CHEROKEE SMALL LETTER DE
+ABA6; C; 13D6; # CHEROKEE SMALL LETTER TE
+ABA7; C; 13D7; # CHEROKEE SMALL LETTER DI
+ABA8; C; 13D8; # CHEROKEE SMALL LETTER TI
+ABA9; C; 13D9; # CHEROKEE SMALL LETTER DO
+ABAA; C; 13DA; # CHEROKEE SMALL LETTER DU
+ABAB; C; 13DB; # CHEROKEE SMALL LETTER DV
+ABAC; C; 13DC; # CHEROKEE SMALL LETTER DLA
+ABAD; C; 13DD; # CHEROKEE SMALL LETTER TLA
+ABAE; C; 13DE; # CHEROKEE SMALL LETTER TLE
+ABAF; C; 13DF; # CHEROKEE SMALL LETTER TLI
+ABB0; C; 13E0; # CHEROKEE SMALL LETTER TLO
+ABB1; C; 13E1; # CHEROKEE SMALL LETTER TLU
+ABB2; C; 13E2; # CHEROKEE SMALL LETTER TLV
+ABB3; C; 13E3; # CHEROKEE SMALL LETTER TSA
+ABB4; C; 13E4; # CHEROKEE SMALL LETTER TSE
+ABB5; C; 13E5; # CHEROKEE SMALL LETTER TSI
+ABB6; C; 13E6; # CHEROKEE SMALL LETTER TSO
+ABB7; C; 13E7; # CHEROKEE SMALL LETTER TSU
+ABB8; C; 13E8; # CHEROKEE SMALL LETTER TSV
+ABB9; C; 13E9; # CHEROKEE SMALL LETTER WA
+ABBA; C; 13EA; # CHEROKEE SMALL LETTER WE
+ABBB; C; 13EB; # CHEROKEE SMALL LETTER WI
+ABBC; C; 13EC; # CHEROKEE SMALL LETTER WO
+ABBD; C; 13ED; # CHEROKEE SMALL LETTER WU
+ABBE; C; 13EE; # CHEROKEE SMALL LETTER WV
+ABBF; C; 13EF; # CHEROKEE SMALL LETTER YA
+FB00; F; 0066 0066; # LATIN SMALL LIGATURE FF
+FB01; F; 0066 0069; # LATIN SMALL LIGATURE FI
+FB02; F; 0066 006C; # LATIN SMALL LIGATURE FL
+FB03; F; 0066 0066 0069; # LATIN SMALL LIGATURE FFI
+FB04; F; 0066 0066 006C; # LATIN SMALL LIGATURE FFL
+FB05; F; 0073 0074; # LATIN SMALL LIGATURE LONG S T
+FB06; F; 0073 0074; # LATIN SMALL LIGATURE ST
+FB13; F; 0574 0576; # ARMENIAN SMALL LIGATURE MEN NOW
+FB14; F; 0574 0565; # ARMENIAN SMALL LIGATURE MEN ECH
+FB15; F; 0574 056B; # ARMENIAN SMALL LIGATURE MEN INI
+FB16; F; 057E 0576; # ARMENIAN SMALL LIGATURE VEW NOW
+FB17; F; 0574 056D; # ARMENIAN SMALL LIGATURE MEN XEH
+FF21; C; FF41; # FULLWIDTH LATIN CAPITAL LETTER A
+FF22; C; FF42; # FULLWIDTH LATIN CAPITAL LETTER B
+FF23; C; FF43; # FULLWIDTH LATIN CAPITAL LETTER C
+FF24; C; FF44; # FULLWIDTH LATIN CAPITAL LETTER D
+FF25; C; FF45; # FULLWIDTH LATIN CAPITAL LETTER E
+FF26; C; FF46; # FULLWIDTH LATIN CAPITAL LETTER F
+FF27; C; FF47; # FULLWIDTH LATIN CAPITAL LETTER G
+FF28; C; FF48; # FULLWIDTH LATIN CAPITAL LETTER H
+FF29; C; FF49; # FULLWIDTH LATIN CAPITAL LETTER I
+FF2A; C; FF4A; # FULLWIDTH LATIN CAPITAL LETTER J
+FF2B; C; FF4B; # FULLWIDTH LATIN CAPITAL LETTER K
+FF2C; C; FF4C; # FULLWIDTH LATIN CAPITAL LETTER L
+FF2D; C; FF4D; # FULLWIDTH LATIN CAPITAL LETTER M
+FF2E; C; FF4E; # FULLWIDTH LATIN CAPITAL LETTER N
+FF2F; C; FF4F; # FULLWIDTH LATIN CAPITAL LETTER O
+FF30; C; FF50; # FULLWIDTH LATIN CAPITAL LETTER P
+FF31; C; FF51; # FULLWIDTH LATIN CAPITAL LETTER Q
+FF32; C; FF52; # FULLWIDTH LATIN CAPITAL LETTER R
+FF33; C; FF53; # FULLWIDTH LATIN CAPITAL LETTER S
+FF34; C; FF54; # FULLWIDTH LATIN CAPITAL LETTER T
+FF35; C; FF55; # FULLWIDTH LATIN CAPITAL LETTER U
+FF36; C; FF56; # FULLWIDTH LATIN CAPITAL LETTER V
+FF37; C; FF57; # FULLWIDTH LATIN CAPITAL LETTER W
+FF38; C; FF58; # FULLWIDTH LATIN CAPITAL LETTER X
+FF39; C; FF59; # FULLWIDTH LATIN CAPITAL LETTER Y
+FF3A; C; FF5A; # FULLWIDTH LATIN CAPITAL LETTER Z
+10400; C; 10428; # DESERET CAPITAL LETTER LONG I
+10401; C; 10429; # DESERET CAPITAL LETTER LONG E
+10402; C; 1042A; # DESERET CAPITAL LETTER LONG A
+10403; C; 1042B; # DESERET CAPITAL LETTER LONG AH
+10404; C; 1042C; # DESERET CAPITAL LETTER LONG O
+10405; C; 1042D; # DESERET CAPITAL LETTER LONG OO
+10406; C; 1042E; # DESERET CAPITAL LETTER SHORT I
+10407; C; 1042F; # DESERET CAPITAL LETTER SHORT E
+10408; C; 10430; # DESERET CAPITAL LETTER SHORT A
+10409; C; 10431; # DESERET CAPITAL LETTER SHORT AH
+1040A; C; 10432; # DESERET CAPITAL LETTER SHORT O
+1040B; C; 10433; # DESERET CAPITAL LETTER SHORT OO
+1040C; C; 10434; # DESERET CAPITAL LETTER AY
+1040D; C; 10435; # DESERET CAPITAL LETTER OW
+1040E; C; 10436; # DESERET CAPITAL LETTER WU
+1040F; C; 10437; # DESERET CAPITAL LETTER YEE
+10410; C; 10438; # DESERET CAPITAL LETTER H
+10411; C; 10439; # DESERET CAPITAL LETTER PEE
+10412; C; 1043A; # DESERET CAPITAL LETTER BEE
+10413; C; 1043B; # DESERET CAPITAL LETTER TEE
+10414; C; 1043C; # DESERET CAPITAL LETTER DEE
+10415; C; 1043D; # DESERET CAPITAL LETTER CHEE
+10416; C; 1043E; # DESERET CAPITAL LETTER JEE
+10417; C; 1043F; # DESERET CAPITAL LETTER KAY
+10418; C; 10440; # DESERET CAPITAL LETTER GAY
+10419; C; 10441; # DESERET CAPITAL LETTER EF
+1041A; C; 10442; # DESERET CAPITAL LETTER VEE
+1041B; C; 10443; # DESERET CAPITAL LETTER ETH
+1041C; C; 10444; # DESERET CAPITAL LETTER THEE
+1041D; C; 10445; # DESERET CAPITAL LETTER ES
+1041E; C; 10446; # DESERET CAPITAL LETTER ZEE
+1041F; C; 10447; # DESERET CAPITAL LETTER ESH
+10420; C; 10448; # DESERET CAPITAL LETTER ZHEE
+10421; C; 10449; # DESERET CAPITAL LETTER ER
+10422; C; 1044A; # DESERET CAPITAL LETTER EL
+10423; C; 1044B; # DESERET CAPITAL LETTER EM
+10424; C; 1044C; # DESERET CAPITAL LETTER EN
+10425; C; 1044D; # DESERET CAPITAL LETTER ENG
+10426; C; 1044E; # DESERET CAPITAL LETTER OI
+10427; C; 1044F; # DESERET CAPITAL LETTER EW
+10C80; C; 10CC0; # OLD HUNGARIAN CAPITAL LETTER A
+10C81; C; 10CC1; # OLD HUNGARIAN CAPITAL LETTER AA
+10C82; C; 10CC2; # OLD HUNGARIAN CAPITAL LETTER EB
+10C83; C; 10CC3; # OLD HUNGARIAN CAPITAL LETTER AMB
+10C84; C; 10CC4; # OLD HUNGARIAN CAPITAL LETTER EC
+10C85; C; 10CC5; # OLD HUNGARIAN CAPITAL LETTER ENC
+10C86; C; 10CC6; # OLD HUNGARIAN CAPITAL LETTER ECS
+10C87; C; 10CC7; # OLD HUNGARIAN CAPITAL LETTER ED
+10C88; C; 10CC8; # OLD HUNGARIAN CAPITAL LETTER AND
+10C89; C; 10CC9; # OLD HUNGARIAN CAPITAL LETTER E
+10C8A; C; 10CCA; # OLD HUNGARIAN CAPITAL LETTER CLOSE E
+10C8B; C; 10CCB; # OLD HUNGARIAN CAPITAL LETTER EE
+10C8C; C; 10CCC; # OLD HUNGARIAN CAPITAL LETTER EF
+10C8D; C; 10CCD; # OLD HUNGARIAN CAPITAL LETTER EG
+10C8E; C; 10CCE; # OLD HUNGARIAN CAPITAL LETTER EGY
+10C8F; C; 10CCF; # OLD HUNGARIAN CAPITAL LETTER EH
+10C90; C; 10CD0; # OLD HUNGARIAN CAPITAL LETTER I
+10C91; C; 10CD1; # OLD HUNGARIAN CAPITAL LETTER II
+10C92; C; 10CD2; # OLD HUNGARIAN CAPITAL LETTER EJ
+10C93; C; 10CD3; # OLD HUNGARIAN CAPITAL LETTER EK
+10C94; C; 10CD4; # OLD HUNGARIAN CAPITAL LETTER AK
+10C95; C; 10CD5; # OLD HUNGARIAN CAPITAL LETTER UNK
+10C96; C; 10CD6; # OLD HUNGARIAN CAPITAL LETTER EL
+10C97; C; 10CD7; # OLD HUNGARIAN CAPITAL LETTER ELY
+10C98; C; 10CD8; # OLD HUNGARIAN CAPITAL LETTER EM
+10C99; C; 10CD9; # OLD HUNGARIAN CAPITAL LETTER EN
+10C9A; C; 10CDA; # OLD HUNGARIAN CAPITAL LETTER ENY
+10C9B; C; 10CDB; # OLD HUNGARIAN CAPITAL LETTER O
+10C9C; C; 10CDC; # OLD HUNGARIAN CAPITAL LETTER OO
+10C9D; C; 10CDD; # OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG OE
+10C9E; C; 10CDE; # OLD HUNGARIAN CAPITAL LETTER RUDIMENTA OE
+10C9F; C; 10CDF; # OLD HUNGARIAN CAPITAL LETTER OEE
+10CA0; C; 10CE0; # OLD HUNGARIAN CAPITAL LETTER EP
+10CA1; C; 10CE1; # OLD HUNGARIAN CAPITAL LETTER EMP
+10CA2; C; 10CE2; # OLD HUNGARIAN CAPITAL LETTER ER
+10CA3; C; 10CE3; # OLD HUNGARIAN CAPITAL LETTER SHORT ER
+10CA4; C; 10CE4; # OLD HUNGARIAN CAPITAL LETTER ES
+10CA5; C; 10CE5; # OLD HUNGARIAN CAPITAL LETTER ESZ
+10CA6; C; 10CE6; # OLD HUNGARIAN CAPITAL LETTER ET
+10CA7; C; 10CE7; # OLD HUNGARIAN CAPITAL LETTER ENT
+10CA8; C; 10CE8; # OLD HUNGARIAN CAPITAL LETTER ETY
+10CA9; C; 10CE9; # OLD HUNGARIAN CAPITAL LETTER ECH
+10CAA; C; 10CEA; # OLD HUNGARIAN CAPITAL LETTER U
+10CAB; C; 10CEB; # OLD HUNGARIAN CAPITAL LETTER UU
+10CAC; C; 10CEC; # OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG UE
+10CAD; C; 10CED; # OLD HUNGARIAN CAPITAL LETTER RUDIMENTA UE
+10CAE; C; 10CEE; # OLD HUNGARIAN CAPITAL LETTER EV
+10CAF; C; 10CEF; # OLD HUNGARIAN CAPITAL LETTER EZ
+10CB0; C; 10CF0; # OLD HUNGARIAN CAPITAL LETTER EZS
+10CB1; C; 10CF1; # OLD HUNGARIAN CAPITAL LETTER ENT-SHAPED SIGN
+10CB2; C; 10CF2; # OLD HUNGARIAN CAPITAL LETTER US
+118A0; C; 118C0; # WARANG CITI CAPITAL LETTER NGAA
+118A1; C; 118C1; # WARANG CITI CAPITAL LETTER A
+118A2; C; 118C2; # WARANG CITI CAPITAL LETTER WI
+118A3; C; 118C3; # WARANG CITI CAPITAL LETTER YU
+118A4; C; 118C4; # WARANG CITI CAPITAL LETTER YA
+118A5; C; 118C5; # WARANG CITI CAPITAL LETTER YO
+118A6; C; 118C6; # WARANG CITI CAPITAL LETTER II
+118A7; C; 118C7; # WARANG CITI CAPITAL LETTER UU
+118A8; C; 118C8; # WARANG CITI CAPITAL LETTER E
+118A9; C; 118C9; # WARANG CITI CAPITAL LETTER O
+118AA; C; 118CA; # WARANG CITI CAPITAL LETTER ANG
+118AB; C; 118CB; # WARANG CITI CAPITAL LETTER GA
+118AC; C; 118CC; # WARANG CITI CAPITAL LETTER KO
+118AD; C; 118CD; # WARANG CITI CAPITAL LETTER ENY
+118AE; C; 118CE; # WARANG CITI CAPITAL LETTER YUJ
+118AF; C; 118CF; # WARANG CITI CAPITAL LETTER UC
+118B0; C; 118D0; # WARANG CITI CAPITAL LETTER ENN
+118B1; C; 118D1; # WARANG CITI CAPITAL LETTER ODD
+118B2; C; 118D2; # WARANG CITI CAPITAL LETTER TTE
+118B3; C; 118D3; # WARANG CITI CAPITAL LETTER NUNG
+118B4; C; 118D4; # WARANG CITI CAPITAL LETTER DA
+118B5; C; 118D5; # WARANG CITI CAPITAL LETTER AT
+118B6; C; 118D6; # WARANG CITI CAPITAL LETTER AM
+118B7; C; 118D7; # WARANG CITI CAPITAL LETTER BU
+118B8; C; 118D8; # WARANG CITI CAPITAL LETTER PU
+118B9; C; 118D9; # WARANG CITI CAPITAL LETTER HIYO
+118BA; C; 118DA; # WARANG CITI CAPITAL LETTER HOLO
+118BB; C; 118DB; # WARANG CITI CAPITAL LETTER HORR
+118BC; C; 118DC; # WARANG CITI CAPITAL LETTER HAR
+118BD; C; 118DD; # WARANG CITI CAPITAL LETTER SSUU
+118BE; C; 118DE; # WARANG CITI CAPITAL LETTER SII
+118BF; C; 118DF; # WARANG CITI CAPITAL LETTER VIYO
+#
+# EOF
diff --git a/third-party/physfs/extras/globbing.c b/third-party/physfs/extras/globbing.c
new file mode 100644
index 0000000..b639551
--- /dev/null
+++ b/third-party/physfs/extras/globbing.c
@@ -0,0 +1,239 @@
+/** \file globbing.c */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "globbing.h"
+
+/**
+ * Please see globbing.h for details.
+ *
+ * License: this code is public domain. I make no warranty that it is useful,
+ * correct, harmless, or environmentally safe.
+ *
+ * This particular file may be used however you like, including copying it
+ * verbatim into a closed-source project, exploiting it commercially, and
+ * removing any trace of my name from the source (although I hope you won't
+ * do that). I welcome enhancements and corrections to this file, but I do
+ * not require you to send me patches if you make changes. This code has
+ * NO WARRANTY.
+ *
+ * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * \author Ryan C. Gordon.
+ */
+
+
+static int matchesPattern(const char *fname, const char *wildcard,
+ int caseSensitive)
+{
+ char x, y;
+ const char *fnameptr = fname;
+ const char *wildptr = wildcard;
+
+ while ((*wildptr) && (*fnameptr))
+ {
+ y = *wildptr;
+ if (y == '*')
+ {
+ do
+ {
+ wildptr++; /* skip multiple '*' in a row... */
+ } while (*wildptr == '*');
+
+ y = (caseSensitive) ? *wildptr : (char) tolower(*wildptr);
+
+ while (1)
+ {
+ x = (caseSensitive) ? *fnameptr : (char) tolower(*fnameptr);
+ if ((!x) || (x == y))
+ break;
+ else
+ fnameptr++;
+ } /* while */
+ } /* if */
+
+ else if (y == '?')
+ {
+ wildptr++;
+ fnameptr++;
+ } /* else if */
+
+ else
+ {
+ if (caseSensitive)
+ x = *fnameptr;
+ else
+ {
+ x = tolower(*fnameptr);
+ y = tolower(y);
+ } /* if */
+
+ wildptr++;
+ fnameptr++;
+
+ if (x != y)
+ return 0;
+ } /* else */
+ } /* while */
+
+ while (*wildptr == '*')
+ wildptr++;
+
+ return (*fnameptr == *wildptr);
+} /* matchesPattern */
+
+typedef struct
+{
+ const PHYSFS_Allocator *allocator;
+ const char *wildcard;
+ int caseSensitive;
+ PHYSFS_EnumFilesCallback callback;
+ void *origData;
+} WildcardCallbackData;
+
+
+/*
+ * This callback sits between the enumerator and the enduser callback,
+ * filtering out files that don't match the wildcard pattern.
+ */
+static void wildcardCallback(void *_d, const char *origdir, const char *fname)
+{
+ const WildcardCallbackData *data = (const WildcardCallbackData *) _d;
+ if (matchesPattern(fname, data->wildcard, data->caseSensitive))
+ data->callback(data->origData, origdir, fname);
+} /* wildcardCallback */
+
+
+void PHYSFSEXT_enumerateFilesCallbackWildcard(const char *dir,
+ const char *wildcard,
+ int caseSensitive,
+ PHYSFS_EnumFilesCallback c,
+ void *d)
+{
+ WildcardCallbackData data;
+ data.allocator = PHYSFS_getAllocator();
+ data.wildcard = wildcard;
+ data.caseSensitive = caseSensitive;
+ data.callback = c;
+ data.origData = d;
+ PHYSFS_enumerateFilesCallback(dir, wildcardCallback, &data);
+} /* PHYSFSEXT_enumerateFilesCallbackWildcard */
+
+
+void PHYSFSEXT_freeEnumeration(char **list)
+{
+ const PHYSFS_Allocator *allocator = PHYSFS_getAllocator();
+ int i;
+ if (list != NULL)
+ {
+ for (i = 0; list[i] != NULL; i++)
+ allocator->Free(list[i]);
+ allocator->Free(list);
+ } /* if */
+} /* PHYSFSEXT_freeEnumeration */
+
+
+char **PHYSFSEXT_enumerateFilesWildcard(const char *dir, const char *wildcard,
+ int caseSensitive)
+{
+ const PHYSFS_Allocator *allocator = PHYSFS_getAllocator();
+ char **list = PHYSFS_enumerateFiles(dir);
+ char **retval = NULL;
+ int totalmatches = 0;
+ int matches = 0;
+ char **i;
+
+ for (i = list; *i != NULL; i++)
+ {
+ #if 0
+ printf("matchesPattern: '%s' vs '%s' (%s) ... %s\n", *i, wildcard,
+ caseSensitive ? "case" : "nocase",
+ matchesPattern(*i, wildcard, caseSensitive) ? "true" : "false");
+ #endif
+ if (matchesPattern(*i, wildcard, caseSensitive))
+ totalmatches++;
+ } /* for */
+
+ retval = (char **) allocator->Malloc(sizeof (char *) * (totalmatches+1));
+ if (retval != NULL)
+ {
+ for (i = list; ((matches < totalmatches) && (*i != NULL)); i++)
+ {
+ if (matchesPattern(*i, wildcard, caseSensitive))
+ {
+ retval[matches] = (char *) allocator->Malloc(strlen(*i) + 1);
+ if (retval[matches] == NULL)
+ {
+ while (matches--)
+ allocator->Free(retval[matches]);
+ allocator->Free(retval);
+ retval = NULL;
+ break;
+ } /* if */
+ strcpy(retval[matches], *i);
+ matches++;
+ } /* if */
+ } /* for */
+
+ if (retval != NULL)
+ {
+ assert(totalmatches == matches);
+ retval[matches] = NULL;
+ } /* if */
+ } /* if */
+
+ PHYSFS_freeList(list);
+ return retval;
+} /* PHYSFSEXT_enumerateFilesWildcard */
+
+
+#ifdef TEST_PHYSFSEXT_ENUMERATEFILESWILDCARD
+int main(int argc, char **argv)
+{
+ int rc;
+ char **flist;
+ char **i;
+
+ if (argc != 3)
+ {
+ printf("USAGE: %s \n"
+ " where is 1 or 0.\n", argv[0]);
+ return 1;
+ } /* if */
+
+ if (!PHYSFS_init(argv[0]))
+ {
+ fprintf(stderr, "PHYSFS_init(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return 1;
+ } /* if */
+
+ if (!PHYSFS_addToSearchPath(".", 1))
+ {
+ fprintf(stderr, "PHYSFS_addToSearchPath(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ PHYSFS_deinit();
+ return 1;
+ } /* if */
+
+ flist = PHYSFSEXT_enumerateFilesWildcard("/", argv[1], atoi(argv[2]));
+ rc = 0;
+ for (i = flist; *i; i++)
+ {
+ printf("%s\n", *i);
+ rc++;
+ } /* for */
+ printf("\n total %d files.\n\n", rc);
+
+ PHYSFSEXT_freeEnumeration(flist);
+ PHYSFS_deinit();
+
+ return 0;
+} /* main */
+#endif
+
+/* end of globbing.c ... */
+
diff --git a/third-party/physfs/extras/globbing.h b/third-party/physfs/extras/globbing.h
new file mode 100644
index 0000000..4cfe25d
--- /dev/null
+++ b/third-party/physfs/extras/globbing.h
@@ -0,0 +1,165 @@
+#ifndef INCL_PHYSFSEXT_GLOBBING_H
+#define INCL_PHYSFSEXT_GLOBBING_H
+
+/** \file globbing.h */
+
+#include "physfs.h"
+
+/**
+ * \mainpage PhysicsFS globbing
+ *
+ * This is an extension to PhysicsFS to let you search for files with basic
+ * wildcard matching, regardless of what sort of filesystem or archive they
+ * reside in. It does this by enumerating directories as needed and manually
+ * locating matching entries.
+ *
+ * Usage: Set up PhysicsFS as you normally would, then use
+ * PHYSFSEXT_enumerateFilesWildcard() when enumerating files. This is just
+ * like PHYSFS_enumerateFiles(), but it returns a subset that matches your
+ * wildcard pattern. You must call PHYSFSEXT_freeEnumeration() on the results,
+ * just PHYSFS_enumerateFiles() would do with PHYSFS_freeList().
+ *
+ * License: this code is public domain. I make no warranty that it is useful,
+ * correct, harmless, or environmentally safe.
+ *
+ * This particular file may be used however you like, including copying it
+ * verbatim into a closed-source project, exploiting it commercially, and
+ * removing any trace of my name from the source (although I hope you won't
+ * do that). I welcome enhancements and corrections to this file, but I do
+ * not require you to send me patches if you make changes. This code has
+ * NO WARRANTY.
+ *
+ * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
+ * Please see LICENSE.txt in the source's "docs" directory.
+ *
+ * \author Ryan C. Gordon.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \fn char **PHYSFS_enumerateFilesWildcard(const char *dir, const char *wildcard, int caseSensitive)
+ * \brief Get a file listing of a search path's directory, filtered with a wildcard pattern.
+ *
+ * Matching directories are interpolated. That is, if "C:\mydir" is in the
+ * search path and contains a directory "savegames" that contains "x.sav",
+ * "y.Sav", and "z.txt", and there is also a "C:\userdir" in the search path
+ * that has a "savegames" subdirectory with "w.sav", then the following code:
+ *
+ * \code
+ * char **rc = PHYSFS_enumerateFilesWildcard("savegames", "*.sav", 0);
+ * char **i;
+ *
+ * for (i = rc; *i != NULL; i++)
+ * printf(" * We've got [%s].\n", *i);
+ *
+ * PHYSFS_freeList(rc);
+ * \endcode
+ *
+ * ...will print:
+ *
+ * \verbatim
+ * We've got [x.sav].
+ * We've got [y.Sav].
+ * We've got [w.sav].\endverbatim
+ *
+ * Feel free to sort the list however you like. We only promise there will
+ * be no duplicates, but not what order the final list will come back in.
+ *
+ * Wildcard strings can use the '*' and '?' characters, currently.
+ * Matches can be case-insensitive if you pass a zero for argument 3.
+ *
+ * Don't forget to call PHYSFSEXT_freeEnumerator() with the return value from
+ * this function when you are done with it. As we use PhysicsFS's allocator
+ * for this list, you must free it before calling PHYSFS_deinit().
+ * Do not use PHYSFS_freeList() on the returned value!
+ *
+ * \param dir directory in platform-independent notation to enumerate.
+ * \param wildcard Wildcard pattern to use for filtering.
+ * \param caseSensitive Zero for case-insensitive matching,
+ * non-zero for case-sensitive.
+ * \return Null-terminated array of null-terminated strings.
+ *
+ * \sa PHYSFSEXT_freeEnumeration
+ */
+PHYSFS_DECL char **PHYSFSEXT_enumerateFilesWildcard(const char *dir,
+ const char *wildcard,
+ int caseSensitive);
+
+/**
+ * \fn void PHYSFSEXT_freeEnumeration(char **list)
+ * \brief Free data returned by PHYSFSEXT_enumerateFilesWildcard
+ *
+ * Conceptually, this works like PHYSFS_freeList(), but is used with data
+ * returned by PHYSFSEXT_enumerateFilesWildcard() only. Be sure to call this
+ * on any returned data from that function before
+ *
+ * \param list Pointer previously returned by
+ * PHYSFSEXT_enumerateFilesWildcard(). It is safe to pass a
+ * NULL here.
+ *
+ * \sa PHYSFSEXT_enumerateFilesWildcard
+ */
+PHYSFS_DECL void PHYSFSEXT_freeEnumeration(char **list);
+
+
+/**
+ * \fn void PHYSFSEXT_enumerateFilesCallbackWildcard(const char *dir, const char *wildcard, int caseSensitive, PHYSFS_EnumFilesCallback c, void *d);
+ * \brief Get a file listing of a search path's directory, filtered with a wildcard pattern, using an application-defined callback.
+ *
+ * This function is equivalent to PHYSFSEXT_enumerateFilesWildcard(). It
+ * reports file listings, filtered by a wildcard pattern.
+ *
+ * Unlike PHYSFS_enumerateFiles(), this function does not return an array.
+ * Rather, it calls a function specified by the application once per
+ * element of the search path:
+ *
+ * \code
+ *
+ * static void printDir(void *data, const char *origdir, const char *fname)
+ * {
+ * printf(" * We've got [%s] in [%s].\n", fname, origdir);
+ * }
+ *
+ * // ...
+ * PHYSFS_enumerateFilesCallbackWildcard("savegames","*.sav",0,printDir,NULL);
+ * \endcode
+ *
+ * Items sent to the callback are not guaranteed to be in any order whatsoever.
+ * There is no sorting done at this level, and if you need that, you should
+ * probably use PHYSFS_enumerateFilesWildcard() instead, which guarantees
+ * alphabetical sorting. This form reports whatever is discovered in each
+ * archive before moving on to the next. Even within one archive, we can't
+ * guarantee what order it will discover data. Any sorting you find in
+ * these callbacks is just pure luck. Do not rely on it. As this walks
+ * the entire list of archives, you may receive duplicate filenames.
+ *
+ * Wildcard strings can use the '*' and '?' characters, currently.
+ * Matches can be case-insensitive if you pass a zero for argument 3.
+ *
+ * \param dir Directory, in platform-independent notation, to enumerate.
+ * \param wildcard Wildcard pattern to use for filtering.
+ * \param caseSensitive Zero for case-insensitive matching,
+ * non-zero for case-sensitive.
+ * \param c Callback function to notify about search path elements.
+ * \param d Application-defined data passed to callback. Can be NULL.
+ *
+ * \sa PHYSFS_EnumFilesCallback
+ * \sa PHYSFS_enumerateFiles
+ */
+PHYSFS_DECL void PHYSFSEXT_enumerateFilesCallbackWildcard(const char *dir,
+ const char *wildcard,
+ int caseSensitive,
+ PHYSFS_EnumFilesCallback c,
+ void *d);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* include-once blocker. */
+
+/* end of globbing.h ... */
+
diff --git a/third-party/physfs/extras/ignorecase.c b/third-party/physfs/extras/ignorecase.c
new file mode 100644
index 0000000..5f0f35b
--- /dev/null
+++ b/third-party/physfs/extras/ignorecase.c
@@ -0,0 +1,203 @@
+/** \file ignorecase.c */
+
+#include
+#include
+#include
+#include
+
+#include "physfs.h"
+#include "ignorecase.h"
+
+/**
+ * Please see ignorecase.h for details.
+ *
+ * License: this code is public domain. I make no warranty that it is useful,
+ * correct, harmless, or environmentally safe.
+ *
+ * This particular file may be used however you like, including copying it
+ * verbatim into a closed-source project, exploiting it commercially, and
+ * removing any trace of my name from the source (although I hope you won't
+ * do that). I welcome enhancements and corrections to this file, but I do
+ * not require you to send me patches if you make changes. This code has
+ * NO WARRANTY.
+ *
+ * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
+ * Please see LICENSE.txt in the root of the source tree.
+ *
+ * \author Ryan C. Gordon.
+ */
+
+static int locateOneElement(char *buf)
+{
+ char *ptr;
+ char **rc;
+ char **i;
+
+ if (PHYSFS_exists(buf))
+ return 1; /* quick rejection: exists in current case. */
+
+ ptr = strrchr(buf, '/'); /* find entry at end of path. */
+ if (ptr == NULL)
+ {
+ rc = PHYSFS_enumerateFiles("/");
+ ptr = buf;
+ } /* if */
+ else
+ {
+ *ptr = '\0';
+ rc = PHYSFS_enumerateFiles(buf);
+ *ptr = '/';
+ ptr++; /* point past dirsep to entry itself. */
+ } /* else */
+
+ if (rc != NULL)
+ {
+ for (i = rc; *i != NULL; i++)
+ {
+ if (PHYSFS_utf8stricmp(*i, ptr) == 0)
+ {
+ strcpy(ptr, *i); /* found a match. Overwrite with this case. */
+ PHYSFS_freeList(rc);
+ return 1;
+ } /* if */
+ } /* for */
+
+ PHYSFS_freeList(rc);
+ } /* if */
+
+ /* no match at all... */
+ return 0;
+} /* locateOneElement */
+
+
+int PHYSFSEXT_locateCorrectCase(char *buf)
+{
+ int rc;
+ char *ptr;
+
+ while (*buf == '/') /* skip any '/' at start of string... */
+ buf++;
+
+ ptr = buf;
+ if (*ptr == '\0')
+ return 0; /* Uh...I guess that's success. */
+
+ while ( (ptr = strchr(ptr + 1, '/')) != NULL )
+ {
+ *ptr = '\0'; /* block this path section off */
+ rc = locateOneElement(buf);
+ *ptr = '/'; /* restore path separator */
+ if (!rc)
+ return -2; /* missing element in path. */
+ } /* while */
+
+ /* check final element... */
+ return locateOneElement(buf) ? 0 : -1;
+} /* PHYSFSEXT_locateCorrectCase */
+
+
+#ifdef TEST_PHYSFSEXT_LOCATECORRECTCASE
+int main(int argc, char **argv)
+{
+ int rc;
+ char buf[128];
+ PHYSFS_File *f;
+
+ if (!PHYSFS_init(argv[0]))
+ {
+ fprintf(stderr, "PHYSFS_init(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return 1;
+ } /* if */
+
+ if (!PHYSFS_addToSearchPath(".", 1))
+ {
+ fprintf(stderr, "PHYSFS_addToSearchPath(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ PHYSFS_deinit();
+ return 1;
+ } /* if */
+
+ if (!PHYSFS_setWriteDir("."))
+ {
+ fprintf(stderr, "PHYSFS_setWriteDir(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ PHYSFS_deinit();
+ return 1;
+ } /* if */
+
+ if (!PHYSFS_mkdir("/a/b/c"))
+ {
+ fprintf(stderr, "PHYSFS_mkdir(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ PHYSFS_deinit();
+ return 1;
+ } /* if */
+
+ if (!PHYSFS_mkdir("/a/b/C"))
+ {
+ fprintf(stderr, "PHYSFS_mkdir(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ PHYSFS_deinit();
+ return 1;
+ } /* if */
+
+ f = PHYSFS_openWrite("/a/b/c/x.txt");
+ PHYSFS_close(f);
+ if (f == NULL)
+ {
+ fprintf(stderr, "PHYSFS_openWrite(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ PHYSFS_deinit();
+ return 1;
+ } /* if */
+
+ f = PHYSFS_openWrite("/a/b/C/X.txt");
+ PHYSFS_close(f);
+ if (f == NULL)
+ {
+ fprintf(stderr, "PHYSFS_openWrite(): %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ PHYSFS_deinit();
+ return 1;
+ } /* if */
+
+ strcpy(buf, "/a/b/c/x.txt");
+ rc = PHYSFSEXT_locateCorrectCase(buf);
+ if ((rc != 0) || (strcmp(buf, "/a/b/c/x.txt") != 0))
+ printf("test 1 failed\n");
+
+ strcpy(buf, "/a/B/c/x.txt");
+ rc = PHYSFSEXT_locateCorrectCase(buf);
+ if ((rc != 0) || (strcmp(buf, "/a/b/c/x.txt") != 0))
+ printf("test 2 failed\n");
+
+ strcpy(buf, "/a/b/C/x.txt");
+ rc = PHYSFSEXT_locateCorrectCase(buf);
+ if ((rc != 0) || (strcmp(buf, "/a/b/C/X.txt") != 0))
+ printf("test 3 failed\n");
+
+ strcpy(buf, "/a/b/c/X.txt");
+ rc = PHYSFSEXT_locateCorrectCase(buf);
+ if ((rc != 0) || (strcmp(buf, "/a/b/c/x.txt") != 0))
+ printf("test 4 failed\n");
+
+ strcpy(buf, "/a/b/c/z.txt");
+ rc = PHYSFSEXT_locateCorrectCase(buf);
+ if ((rc != -1) || (strcmp(buf, "/a/b/c/z.txt") != 0))
+ printf("test 5 failed\n");
+
+ strcpy(buf, "/A/B/Z/z.txt");
+ rc = PHYSFSEXT_locateCorrectCase(buf);
+ if ((rc != -2) || (strcmp(buf, "/a/b/Z/z.txt") != 0))
+ printf("test 6 failed\n");
+
+ printf("Testing completed.\n");
+ printf(" If no errors were reported, you're good to go.\n");
+
+ PHYSFS_delete("/a/b/c/x.txt");
+ PHYSFS_delete("/a/b/C/X.txt");
+ PHYSFS_delete("/a/b/c");
+ PHYSFS_delete("/a/b/C");
+ PHYSFS_delete("/a/b");
+ PHYSFS_delete("/a");
+ PHYSFS_deinit();
+ return 0;
+} /* main */
+#endif
+
+/* end of ignorecase.c ... */
+
diff --git a/third-party/physfs/extras/ignorecase.h b/third-party/physfs/extras/ignorecase.h
new file mode 100644
index 0000000..4e223bb
--- /dev/null
+++ b/third-party/physfs/extras/ignorecase.h
@@ -0,0 +1,86 @@
+#ifndef INCL_PHYSFSEXT_IGNORECASE_H
+#define INCL_PHYSFSEXT_IGNORECASE_H
+
+/** \file ignorecase.h */
+
+/**
+ * \mainpage PhysicsFS ignorecase
+ *
+ * This is an extension to PhysicsFS to let you handle files in a
+ * case-insensitive manner, regardless of what sort of filesystem or
+ * archive they reside in. It does this by enumerating directories as
+ * needed and manually locating matching entries.
+ *
+ * Please note that this brings with it some caveats:
+ * - On filesystems that are case-insensitive to start with, such as those
+ * used on Windows or MacOS, you are adding extra overhead.
+ * - On filesystems that are case-sensitive, you might select the wrong dir
+ * or file (which brings security considerations and potential bugs). This
+ * code favours exact case matches, but you will lose access to otherwise
+ * duplicate filenames, or you might go down a wrong directory tree, etc.
+ * In practice, this is rarely a problem, but you need to be aware of it.
+ * - This doesn't do _anything_ with the write directory; you're on your
+ * own for opening the right files for writing. You can sort of get around
+ * this by adding your write directory to the search path, but then the
+ * interpolated directory tree can screw you up even more.
+ *
+ * This code should be considered an aid for legacy code. New development
+ * shouldn't do things that require this aid in the first place. :)
+ *
+ * Usage: Set up PhysicsFS as you normally would, then use
+ * PHYSFSEXT_locateCorrectCase() to get a "correct" pathname to pass to
+ * functions like PHYSFS_openRead(), etc.
+ *
+ * License: this code is public domain. I make no warranty that it is useful,
+ * correct, harmless, or environmentally safe.
+ *
+ * This particular file may be used however you like, including copying it
+ * verbatim into a closed-source project, exploiting it commercially, and
+ * removing any trace of my name from the source (although I hope you won't
+ * do that). I welcome enhancements and corrections to this file, but I do
+ * not require you to send me patches if you make changes. This code has
+ * NO WARRANTY.
+ *
+ * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
+ * Please see LICENSE.txt in the root of the source tree.
+ *
+ * \author Ryan C. Gordon.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \fn int PHYSFSEXT_locateCorrectCase(char *buf)
+ * \brief Find an existing filename with matching case.
+ *
+ * This function will look for a path/filename that matches the passed in
+ * buffer. Each element of the buffer's path is checked for a
+ * case-insensitive match. The buffer must specify a null-terminated string
+ * in platform-independent notation.
+ *
+ * Please note results may be skewed differently depending on whether symlinks
+ * are enabled or not.
+ *
+ * Each element of the buffer is overwritten with the actual case of an
+ * existing match. If there is no match, the search aborts and reports an
+ * error. Exact matches are favored over case-insensitive matches.
+ *
+ * THIS IS RISKY. Please do not use this function for anything but legacy code.
+ *
+ * \param buf Buffer with null-terminated string of path/file to locate.
+ * This buffer will be modified by this function.
+ * \return zero if match was found, -1 if the final element (the file itself)
+ * is missing, -2 if one of the parent directories is missing.
+ */
+int PHYSFSEXT_locateCorrectCase(char *buf);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* include-once blocker. */
+
+/* end of ignorecase.h ... */
+
diff --git a/third-party/physfs/extras/makecasefoldhashtable.pl b/third-party/physfs/extras/makecasefoldhashtable.pl
new file mode 100644
index 0000000..4e0cd30
--- /dev/null
+++ b/third-party/physfs/extras/makecasefoldhashtable.pl
@@ -0,0 +1,276 @@
+#!/usr/bin/perl -w
+
+use warnings;
+use strict;
+
+my $HASHBUCKETS1_16 = 256;
+my $HASHBUCKETS1_32 = 16;
+my $HASHBUCKETS2_16 = 16;
+my $HASHBUCKETS3_16 = 4;
+
+print <<__EOF__;
+/*
+ * This file is part of PhysicsFS (https://icculus.org/physfs/)
+ *
+ * This data generated by physfs/extras/makecasefoldhashtable.pl ...
+ * Do not manually edit this file!
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ */
+
+#ifndef _INCLUDE_PHYSFS_CASEFOLDING_H_
+#define _INCLUDE_PHYSFS_CASEFOLDING_H_
+
+#ifndef __PHYSICSFS_INTERNAL__
+#error Do not include this header from your applications.
+#endif
+
+/* We build three simple hashmaps here: one that maps Unicode codepoints to
+a one, two, or three lowercase codepoints. To retrieve this info: look at
+case_fold_hashX, where X is 1, 2, or 3. Most foldable codepoints fold to one,
+a few dozen fold to two, and a handful fold to three. If the codepoint isn't
+in any of these hashes, it doesn't fold (no separate upper and lowercase).
+
+Almost all these codepoints fit into 16 bits, so we hash them as such to save
+memory. If a codepoint is > 0xFFFF, we have separate hashes for them,
+since there are (currently) only about 120 of them and (currently) all of them
+map to a single lowercase codepoint. */
+
+typedef struct CaseFoldMapping1_32
+{
+ PHYSFS_uint32 from;
+ PHYSFS_uint32 to0;
+} CaseFoldMapping1_32;
+
+typedef struct CaseFoldMapping1_16
+{
+ PHYSFS_uint16 from;
+ PHYSFS_uint16 to0;
+} CaseFoldMapping1_16;
+
+typedef struct CaseFoldMapping2_16
+{
+ PHYSFS_uint16 from;
+ PHYSFS_uint16 to0;
+ PHYSFS_uint16 to1;
+} CaseFoldMapping2_16;
+
+typedef struct CaseFoldMapping3_16
+{
+ PHYSFS_uint16 from;
+ PHYSFS_uint16 to0;
+ PHYSFS_uint16 to1;
+ PHYSFS_uint16 to2;
+} CaseFoldMapping3_16;
+
+typedef struct CaseFoldHashBucket1_16
+{
+ const CaseFoldMapping1_16 *list;
+ const PHYSFS_uint8 count;
+} CaseFoldHashBucket1_16;
+
+typedef struct CaseFoldHashBucket1_32
+{
+ const CaseFoldMapping1_32 *list;
+ const PHYSFS_uint8 count;
+} CaseFoldHashBucket1_32;
+
+typedef struct CaseFoldHashBucket2_16
+{
+ const CaseFoldMapping2_16 *list;
+ const PHYSFS_uint8 count;
+} CaseFoldHashBucket2_16;
+
+typedef struct CaseFoldHashBucket3_16
+{
+ const CaseFoldMapping3_16 *list;
+ const PHYSFS_uint8 count;
+} CaseFoldHashBucket3_16;
+
+__EOF__
+
+
+my @foldPairs1_16;
+my @foldPairs2_16;
+my @foldPairs3_16;
+my @foldPairs1_32;
+
+for (my $i = 0; $i < $HASHBUCKETS1_16; $i++) {
+ $foldPairs1_16[$i] = '';
+}
+
+for (my $i = 0; $i < $HASHBUCKETS1_32; $i++) {
+ $foldPairs1_32[$i] = '';
+}
+
+for (my $i = 0; $i < $HASHBUCKETS2_16; $i++) {
+ $foldPairs2_16[$i] = '';
+}
+
+for (my $i = 0; $i < $HASHBUCKETS3_16; $i++) {
+ $foldPairs3_16[$i] = '';
+}
+
+open(FH,'<','casefolding.txt') or die("failed to open casefolding.txt: $!\n");
+while () {
+ chomp;
+ # strip comments from textfile...
+ s/\#.*\Z//;
+
+ # strip whitespace...
+ s/\A\s+//;
+ s/\s+\Z//;
+
+ next if not /\A([a-fA-F0-9]+)\;\s*(.)\;\s*(.+)\;/;
+ my ($code, $status, $mapping) = ($1, $2, $3);
+
+ my $hexxed = hex($code);
+ #print("// code '$code' status '$status' mapping '$mapping'\n");
+
+ if (($status eq 'C') or ($status eq 'F')) {
+ my ($map1, $map2, $map3) = (undef, undef, undef);
+ $map1 = $1 if $mapping =~ s/\A([a-fA-F0-9]+)(\s*|\Z)//;
+ $map2 = $1 if $mapping =~ s/\A([a-fA-F0-9]+)(\s*|\Z)//;
+ $map3 = $1 if $mapping =~ s/\A([a-fA-F0-9]+)(\s*|\Z)//;
+ die("mapping space too small for '$code'\n") if ($mapping ne '');
+ die("problem parsing mapping for '$code'\n") if (not defined($map1));
+
+ if ($hexxed < 128) {
+ # Just ignore these, we'll handle the low-ASCII ones ourselves.
+ } elsif ($hexxed > 0xFFFF) {
+ # We just need to add the 32-bit 2 and/or 3 codepoint maps if this die()'s here.
+ die("Uhoh, a codepoint > 0xFFFF that folds to multiple codepoints! Fixme.") if defined($map2);
+ my $hashed = (($hexxed ^ ($hexxed >> 8)) & ($HASHBUCKETS1_32-1));
+ #print("// hexxed '$hexxed' hashed1 '$hashed'\n");
+ $foldPairs1_32[$hashed] .= " { 0x$code, 0x$map1 },\n";
+ } elsif (not defined($map2)) {
+ my $hashed = (($hexxed ^ ($hexxed >> 8)) & ($HASHBUCKETS1_16-1));
+ #print("// hexxed '$hexxed' hashed1 '$hashed'\n");
+ $foldPairs1_16[$hashed] .= " { 0x$code, 0x$map1 },\n";
+ } elsif (not defined($map3)) {
+ my $hashed = (($hexxed ^ ($hexxed >> 8)) & ($HASHBUCKETS2_16-1));
+ #print("// hexxed '$hexxed' hashed2 '$hashed'\n");
+ $foldPairs2_16[$hashed] .= " { 0x$code, 0x$map1, 0x$map2 },\n";
+ } else {
+ my $hashed = (($hexxed ^ ($hexxed >> 8)) & ($HASHBUCKETS3_16-1));
+ #print("// hexxed '$hexxed' hashed3 '$hashed'\n");
+ $foldPairs3_16[$hashed] .= " { 0x$code, 0x$map1, 0x$map2, 0x$map3 },\n";
+ }
+ }
+}
+close(FH);
+
+for (my $i = 0; $i < $HASHBUCKETS1_16; $i++) {
+ $foldPairs1_16[$i] =~ s/,\n\Z//;
+ my $str = $foldPairs1_16[$i];
+ next if $str eq '';
+ my $num = '000' . $i;
+ $num =~ s/\A.*?(\d\d\d)\Z/$1/;
+ my $sym = "case_fold1_16_${num}";
+ print("static const CaseFoldMapping1_16 ${sym}[] = {\n$str\n};\n\n");
+}
+
+for (my $i = 0; $i < $HASHBUCKETS1_32; $i++) {
+ $foldPairs1_32[$i] =~ s/,\n\Z//;
+ my $str = $foldPairs1_32[$i];
+ next if $str eq '';
+ my $num = '000' . $i;
+ $num =~ s/\A.*?(\d\d\d)\Z/$1/;
+ my $sym = "case_fold1_32_${num}";
+ print("static const CaseFoldMapping1_32 ${sym}[] = {\n$str\n};\n\n");
+}
+
+for (my $i = 0; $i < $HASHBUCKETS2_16; $i++) {
+ $foldPairs2_16[$i] =~ s/,\n\Z//;
+ my $str = $foldPairs2_16[$i];
+ next if $str eq '';
+ my $num = '000' . $i;
+ $num =~ s/\A.*?(\d\d\d)\Z/$1/;
+ my $sym = "case_fold2_16_${num}";
+ print("static const CaseFoldMapping2_16 ${sym}[] = {\n$str\n};\n\n");
+}
+
+for (my $i = 0; $i < $HASHBUCKETS3_16; $i++) {
+ $foldPairs3_16[$i] =~ s/,\n\Z//;
+ my $str = $foldPairs3_16[$i];
+ next if $str eq '';
+ my $num = '000' . $i;
+ $num =~ s/\A.*?(\d\d\d)\Z/$1/;
+ my $sym = "case_fold3_16_${num}";
+ print("static const CaseFoldMapping3_16 ${sym}[] = {\n$str\n};\n\n");
+}
+
+print("static const CaseFoldHashBucket1_16 case_fold_hash1_16[] = {\n");
+
+for (my $i = 0; $i < $HASHBUCKETS1_16; $i++) {
+ my $str = $foldPairs1_16[$i];
+ if ($str eq '') {
+ print(" { NULL, 0 },\n");
+ } else {
+ my $num = '000' . $i;
+ $num =~ s/\A.*?(\d\d\d)\Z/$1/;
+ my $sym = "case_fold1_16_${num}";
+ print(" { $sym, __PHYSFS_ARRAYLEN($sym) },\n");
+ }
+}
+print("};\n\n");
+
+
+print("static const CaseFoldHashBucket1_32 case_fold_hash1_32[] = {\n");
+
+for (my $i = 0; $i < $HASHBUCKETS1_32; $i++) {
+ my $str = $foldPairs1_32[$i];
+ if ($str eq '') {
+ print(" { NULL, 0 },\n");
+ } else {
+ my $num = '000' . $i;
+ $num =~ s/\A.*?(\d\d\d)\Z/$1/;
+ my $sym = "case_fold1_32_${num}";
+ print(" { $sym, __PHYSFS_ARRAYLEN($sym) },\n");
+ }
+}
+print("};\n\n");
+
+
+print("static const CaseFoldHashBucket2_16 case_fold_hash2_16[] = {\n");
+
+for (my $i = 0; $i < $HASHBUCKETS2_16; $i++) {
+ my $str = $foldPairs2_16[$i];
+ if ($str eq '') {
+ print(" { NULL, 0 },\n");
+ } else {
+ my $num = '000' . $i;
+ $num =~ s/\A.*?(\d\d\d)\Z/$1/;
+ my $sym = "case_fold2_16_${num}";
+ print(" { $sym, __PHYSFS_ARRAYLEN($sym) },\n");
+ }
+}
+print("};\n\n");
+
+print("static const CaseFoldHashBucket3_16 case_fold_hash3_16[] = {\n");
+
+for (my $i = 0; $i < $HASHBUCKETS3_16; $i++) {
+ my $str = $foldPairs3_16[$i];
+ if ($str eq '') {
+ print(" { NULL, 0 },\n");
+ } else {
+ my $num = '000' . $i;
+ $num =~ s/\A.*?(\d\d\d)\Z/$1/;
+ my $sym = "case_fold3_16_${num}";
+ print(" { $sym, __PHYSFS_ARRAYLEN($sym) },\n");
+ }
+}
+print("};\n\n");
+
+print <<__EOF__;
+
+#endif /* _INCLUDE_PHYSFS_CASEFOLDING_H_ */
+
+/* end of physfs_casefolding.h ... */
+
+__EOF__
+
+exit 0;
+
+# end of makecashfoldhashtable.pl ...
+
diff --git a/third-party/physfs/extras/physfs.pc.in b/third-party/physfs/extras/physfs.pc.in
new file mode 100644
index 0000000..f7e0307
--- /dev/null
+++ b/third-party/physfs/extras/physfs.pc.in
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+
+Name: PhysicsFS
+Description: PhysicsFS is a library to provide abstract access to various archives.
+URL: https://icculus.org/physfs/
+Version: @PHYSFS_VERSION@
+Libs: -L${libdir} -lphysfs
+Cflags: -I${includedir}
diff --git a/third-party/physfs/extras/physfshttpd.c b/third-party/physfs/extras/physfshttpd.c
new file mode 100644
index 0000000..9e0df6b
--- /dev/null
+++ b/third-party/physfs/extras/physfshttpd.c
@@ -0,0 +1,400 @@
+/*
+ * This is a quick and dirty HTTP server that uses PhysicsFS to retrieve
+ * files. It is not robust at all, probably buggy, and definitely poorly
+ * designed. It's just meant to show that it can be done.
+ *
+ * Basically, you compile this code, and run it:
+ * ./physfshttpd archive1.zip archive2.zip /path/to/a/real/dir etc...
+ *
+ * The files are appended in order to the PhysicsFS search path, and when
+ * a client request comes in, it looks for the file in said search path.
+ *
+ * My goal was to make this work in less than 300 lines of C, so again, it's
+ * not to be used for any serious purpose. Patches to make this application
+ * suck less will be readily and gratefully accepted.
+ *
+ * Command line I used to build this on Linux:
+ * gcc -Wall -Werror -g -o bin/physfshttpd extras/physfshttpd.c -lphysfs
+ *
+ * License: this code is public domain. I make no warranty that it is useful,
+ * correct, harmless, or environmentally safe.
+ *
+ * This particular file may be used however you like, including copying it
+ * verbatim into a closed-source project, exploiting it commercially, and
+ * removing any trace of my name from the source (although I hope you won't
+ * do that). I welcome enhancements and corrections to this file, but I do
+ * not require you to send me patches if you make changes. This code has
+ * NO WARRANTY.
+ *
+ * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
+ * Please see LICENSE.txt in the root of the source tree.
+ *
+ * This file was written by Ryan C. Gordon. (icculus@icculus.org).
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef LACKING_SIGNALS
+#include
+#endif
+
+#ifndef LACKING_PROTOENT
+#include
+#endif
+
+#include "physfs.h"
+
+
+#define DEFAULT_PORTNUM 8080
+
+typedef struct
+{
+ int sock;
+ struct sockaddr *addr;
+ socklen_t addrlen;
+} http_args;
+
+
+#define txt404 \
+ "HTTP/1.0 404 Not Found\n" \
+ "Connection: close\n" \
+ "Content-Type: text/html; charset=utf-8\n" \
+ "\n" \
+ "404 Not Found\n" \
+ "Can't find '%s'.\n\n" \
+
+#define txt200 \
+ "HTTP/1.0 200 OK\n" \
+ "Connection: close\n" \
+ "Content-Type: %s\n" \
+ "\n"
+
+static const char *lastError(void)
+{
+ return PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode());
+} /* lastError */
+
+static int writeAll(const char *ipstr, const int sock, void *buf, const size_t len)
+{
+ if (write(sock, buf, len) != len)
+ {
+ printf("%s: Write error to socket.\n", ipstr);
+ return 0;
+ } /* if */
+
+ return 1;
+} /* writeAll */
+
+static int writeString(const char *ipstr, const int sock, const char *fmt, ...)
+{
+ /* none of this is robust against large strings or HTML escaping. */
+ char buffer[1024];
+ int len;
+ va_list ap;
+ va_start(ap, fmt);
+ len = vsnprintf(buffer, sizeof (buffer), fmt, ap);
+ va_end(ap);
+ if (len < 0)
+ {
+ printf("uhoh, vsnprintf() failed!\n");
+ return 0;
+ } /* if */
+
+ return writeAll(ipstr, sock, buffer, len);
+} /* writeString */
+
+
+static void feed_file_http(const char *ipstr, int sock, const char *fname)
+{
+ PHYSFS_File *in = PHYSFS_openRead(fname);
+
+ if (in == NULL)
+ {
+ printf("%s: Can't open [%s]: %s.\n", ipstr, fname, lastError());
+ writeString(ipstr, sock, txt404, fname);
+ return;
+ } /* if */
+
+ /* !!! FIXME: mimetype */
+ if (writeString(ipstr, sock, txt200, "text/plain; charset=utf-8"))
+ {
+ do
+ {
+ char buffer[1024];
+ PHYSFS_sint64 br = PHYSFS_readBytes(in, buffer, sizeof (buffer));
+ if (br == -1)
+ {
+ printf("%s: Read error: %s.\n", ipstr, lastError());
+ break;
+ } /* if */
+
+ else if (!writeAll(ipstr, sock, buffer, (size_t) br))
+ {
+ break;
+ } /* else if */
+ } while (!PHYSFS_eof(in));
+ } /* if */
+
+ PHYSFS_close(in);
+} /* feed_file_http */
+
+
+static void feed_dirlist_http(const char *ipstr, int sock,
+ const char *dname, char **list)
+{
+ int i;
+
+ if (!writeString(ipstr, sock, txt200, "text/html; charset=utf-8"))
+ return;
+
+ else if (!writeString(ipstr, sock,
+ "Directory %s"
+ "Directory %s
\n",
+ dname, dname))
+ return;
+
+ if (strcmp(dname, "/") == 0)
+ dname = "";
+
+ for (i = 0; list[i]; i++)
+ {
+ const char *fname = list[i];
+ if (!writeString(ipstr, sock,
+ "- %s
\n", dname, fname, fname))
+ break;
+ } /* for */
+
+ writeString(ipstr, sock, "
\n");
+} /* feed_dirlist_http */
+
+static void feed_dir_http(const char *ipstr, int sock, const char *dname)
+{
+ char **list = PHYSFS_enumerateFiles(dname);
+ if (list == NULL)
+ {
+ printf("%s: Can't enumerate directory [%s]: %s.\n",
+ ipstr, dname, lastError());
+ writeString(ipstr, sock, txt404, dname);
+ return;
+ } /* if */
+
+ feed_dirlist_http(ipstr, sock, dname, list);
+ PHYSFS_freeList(list);
+} /* feed_dir_http */
+
+static void feed_http_request(const char *ipstr, int sock, const char *fname)
+{
+ PHYSFS_Stat statbuf;
+
+ printf("%s: requested [%s].\n", ipstr, fname);
+
+ if (!PHYSFS_stat(fname, &statbuf))
+ {
+ printf("%s: Can't stat [%s]: %s.\n", ipstr, fname, lastError());
+ writeString(ipstr, sock, txt404, fname);
+ return;
+ } /* if */
+
+ if (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY)
+ feed_dir_http(ipstr, sock, fname);
+ else
+ feed_file_http(ipstr, sock, fname);
+} /* feed_http_request */
+
+
+static void *do_http(void *_args)
+{
+ http_args *args = (http_args *) _args;
+ char ipstr[128];
+ char buffer[512];
+ char *ptr;
+ strncpy(ipstr, inet_ntoa(((struct sockaddr_in *) args->addr)->sin_addr),
+ sizeof (ipstr));
+ ipstr[sizeof (ipstr) - 1] = '\0';
+
+ printf("%s: connected.\n", ipstr);
+ read(args->sock, buffer, sizeof (buffer));
+ buffer[sizeof (buffer) - 1] = '\0';
+ ptr = strchr(buffer, '\n');
+ if (!ptr)
+ printf("%s: potentially bogus request.\n", ipstr);
+ else
+ {
+ *ptr = '\0';
+ ptr = strchr(buffer, '\r');
+ if (ptr != NULL)
+ *ptr = '\0';
+
+ if ((toupper(buffer[0]) == 'G') &&
+ (toupper(buffer[1]) == 'E') &&
+ (toupper(buffer[2]) == 'T') &&
+ (toupper(buffer[3]) == ' ') &&
+ (toupper(buffer[4]) == '/'))
+ {
+ ptr = strchr(buffer + 5, ' ');
+ if (ptr != NULL)
+ *ptr = '\0';
+ feed_http_request(ipstr, args->sock, buffer + 4);
+ } /* if */
+ } /* else */
+
+ /* !!! FIXME: Time the transfer. */
+ printf("%s: closing connection.\n", ipstr);
+ close(args->sock);
+ free(args->addr);
+ free(args);
+ return NULL;
+} /* do_http */
+
+
+static void serve_http_request(int sock, struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ http_args *args = (http_args *) malloc(sizeof (http_args));
+ if (args == NULL)
+ {
+ printf("out of memory.\n");
+ return;
+ } /* if */
+ args->addr = (struct sockaddr *) malloc(addrlen);
+ if (args->addr == NULL)
+ {
+ free(args);
+ printf("out of memory.\n");
+ return;
+ } /* if */
+
+ args->sock = sock;
+ args->addrlen = addrlen;
+ memcpy(args->addr, addr, addrlen);
+
+ /* !!! FIXME: optionally spin a thread... */
+ do_http((void *) args);
+} /* server_http_request */
+
+
+static int create_listen_socket(short portnum)
+{
+ int retval = -1;
+ int protocol = 0; /* pray this is right. */
+
+#ifndef LACKING_PROTOENT
+ struct protoent *prot;
+ setprotoent(0);
+ prot = getprotobyname("tcp");
+ if (prot != NULL)
+ protocol = prot->p_proto;
+#endif
+
+ retval = socket(PF_INET, SOCK_STREAM, protocol);
+ if (retval >= 0)
+ {
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(portnum);
+ addr.sin_addr.s_addr = INADDR_ANY;
+ if ((bind(retval, (struct sockaddr *) &addr, (socklen_t) sizeof (addr)) == -1) ||
+ (listen(retval, 5) == -1))
+ {
+ close(retval);
+ retval = -1;
+ } /* if */
+ } /* if */
+
+ return retval;
+} /* create_listen_socket */
+
+
+static int listensocket = -1;
+
+void at_exit_cleanup(void)
+{
+ /*
+ * !!! FIXME: If thread support, signal threads to terminate and
+ * !!! FIXME: wait for them to clean up.
+ */
+
+ if (listensocket >= 0)
+ close(listensocket);
+
+ if (!PHYSFS_deinit())
+ printf("PHYSFS_deinit() failed: %s\n", lastError());
+} /* at_exit_cleanup */
+
+
+int main(int argc, char **argv)
+{
+ int i;
+ int portnum = DEFAULT_PORTNUM;
+
+ setbuf(stdout, NULL);
+ setbuf(stderr, NULL);
+
+#ifndef LACKING_SIGNALS
+ /* I'm not sure if this qualifies as a cheap trick... */
+ signal(SIGTERM, exit);
+ signal(SIGINT, exit);
+ signal(SIGFPE, exit);
+ signal(SIGSEGV, exit);
+ signal(SIGPIPE, exit);
+ signal(SIGILL, exit);
+#endif
+
+ if (argc == 1)
+ {
+ printf("USAGE: %s [archive2 [... archiveN]]\n", argv[0]);
+ return 42;
+ } /* if */
+
+ if (!PHYSFS_init(argv[0]))
+ {
+ printf("PHYSFS_init() failed: %s\n", lastError());
+ return 42;
+ } /* if */
+
+ /* normally, this is bad practice, but oh well. */
+ atexit(at_exit_cleanup);
+
+ for (i = 1; i < argc; i++)
+ {
+ if (!PHYSFS_mount(argv[i], NULL, 1))
+ printf(" WARNING: failed to add [%s] to search path.\n", argv[i]);
+ } /* else */
+
+ listensocket = create_listen_socket(portnum);
+ if (listensocket < 0)
+ {
+ printf("listen socket failed to create.\n");
+ return 42;
+ } /* if */
+
+ while (1) /* infinite loop for now. */
+ {
+ struct sockaddr addr;
+ socklen_t len;
+ int s = accept(listensocket, &addr, &len);
+ if (s < 0)
+ {
+ printf("accept() failed: %s\n", strerror(errno));
+ close(listensocket);
+ return 42;
+ } /* if */
+
+ serve_http_request(s, &addr, len);
+ } /* while */
+
+ return 0;
+} /* main */
+
+/* end of physfshttpd.c ... */
+
diff --git a/third-party/physfs/extras/physfsrwops.c b/third-party/physfs/extras/physfsrwops.c
new file mode 100644
index 0000000..ad38f25
--- /dev/null
+++ b/third-party/physfs/extras/physfsrwops.c
@@ -0,0 +1,251 @@
+/*
+ * This code provides a glue layer between PhysicsFS and Simple Directmedia
+ * Layer's (SDL) RWops i/o abstraction.
+ *
+ * License: this code is public domain. I make no warranty that it is useful,
+ * correct, harmless, or environmentally safe.
+ *
+ * This particular file may be used however you like, including copying it
+ * verbatim into a closed-source project, exploiting it commercially, and
+ * removing any trace of my name from the source (although I hope you won't
+ * do that). I welcome enhancements and corrections to this file, but I do
+ * not require you to send me patches if you make changes. This code has
+ * NO WARRANTY.
+ *
+ * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
+ * Please see LICENSE.txt in the root of the source tree.
+ *
+ * SDL 1.2 falls under the LGPL license. SDL 1.3+ is zlib, like PhysicsFS.
+ * You can get SDL at https://www.libsdl.org/
+ *
+ * This file was written by Ryan C. Gordon. (icculus@icculus.org).
+ */
+
+#include /* used for SEEK_SET, SEEK_CUR, SEEK_END ... */
+#include "physfsrwops.h"
+
+/* SDL's RWOPS interface changed a little in SDL 2.0... */
+#if defined(SDL_VERSION_ATLEAST)
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+#define TARGET_SDL2 1
+#endif
+#endif
+
+#if !TARGET_SDL2
+#ifndef RW_SEEK_SET
+#define RW_SEEK_SET SEEK_SET
+#endif
+#ifndef RW_SEEK_CUR
+#define RW_SEEK_CUR SEEK_CUR
+#endif
+#ifndef RW_SEEK_END
+#define RW_SEEK_END SEEK_END
+#endif
+#endif
+
+#if TARGET_SDL2
+static Sint64 SDLCALL physfsrwops_size(struct SDL_RWops *rw)
+{
+ PHYSFS_File *handle = (PHYSFS_File *) rw->hidden.unknown.data1;
+ return (Sint64) PHYSFS_fileLength(handle);
+} /* physfsrwops_size */
+#endif
+
+
+#if TARGET_SDL2
+static Sint64 SDLCALL physfsrwops_seek(struct SDL_RWops *rw, Sint64 offset, int whence)
+#else
+static int physfsrwops_seek(SDL_RWops *rw, int offset, int whence)
+#endif
+{
+ PHYSFS_File *handle = (PHYSFS_File *) rw->hidden.unknown.data1;
+ PHYSFS_sint64 pos = 0;
+
+ if (whence == RW_SEEK_SET)
+ pos = (PHYSFS_sint64) offset;
+
+ else if (whence == RW_SEEK_CUR)
+ {
+ const PHYSFS_sint64 current = PHYSFS_tell(handle);
+ if (current == -1)
+ {
+ SDL_SetError("Can't find position in file: %s",
+ PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return -1;
+ } /* if */
+
+ if (offset == 0) /* this is a "tell" call. We're done. */
+ {
+ #if TARGET_SDL2
+ return (Sint64) current;
+ #else
+ return (int) current;
+ #endif
+ } /* if */
+
+ pos = current + ((PHYSFS_sint64) offset);
+ } /* else if */
+
+ else if (whence == RW_SEEK_END)
+ {
+ const PHYSFS_sint64 len = PHYSFS_fileLength(handle);
+ if (len == -1)
+ {
+ SDL_SetError("Can't find end of file: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return -1;
+ } /* if */
+
+ pos = len + ((PHYSFS_sint64) offset);
+ } /* else if */
+
+ else
+ {
+ SDL_SetError("Invalid 'whence' parameter.");
+ return -1;
+ } /* else */
+
+ if ( pos < 0 )
+ {
+ SDL_SetError("Attempt to seek past start of file.");
+ return -1;
+ } /* if */
+
+ if (!PHYSFS_seek(handle, (PHYSFS_uint64) pos))
+ {
+ SDL_SetError("PhysicsFS error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return -1;
+ } /* if */
+
+ #if TARGET_SDL2
+ return (Sint64) pos;
+ #else
+ return (int) pos;
+ #endif
+} /* physfsrwops_seek */
+
+
+#if TARGET_SDL2
+static size_t SDLCALL physfsrwops_read(struct SDL_RWops *rw, void *ptr,
+ size_t size, size_t maxnum)
+#else
+static int physfsrwops_read(SDL_RWops *rw, void *ptr, int size, int maxnum)
+#endif
+{
+ PHYSFS_File *handle = (PHYSFS_File *) rw->hidden.unknown.data1;
+ const PHYSFS_uint64 readlen = (PHYSFS_uint64) (maxnum * size);
+ const PHYSFS_sint64 rc = PHYSFS_readBytes(handle, ptr, readlen);
+ if (rc != ((PHYSFS_sint64) readlen))
+ {
+ if (!PHYSFS_eof(handle)) /* not EOF? Must be an error. */
+ {
+ SDL_SetError("PhysicsFS error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+
+ #if TARGET_SDL2
+ return 0;
+ #else
+ return -1;
+ #endif
+ } /* if */
+ } /* if */
+
+ #if TARGET_SDL2
+ return (size_t) rc / size;
+ #else
+ return (int) rc / size;
+ #endif
+} /* physfsrwops_read */
+
+
+#if TARGET_SDL2
+static size_t SDLCALL physfsrwops_write(struct SDL_RWops *rw, const void *ptr,
+ size_t size, size_t num)
+#else
+static int physfsrwops_write(SDL_RWops *rw, const void *ptr, int size, int num)
+#endif
+{
+ PHYSFS_File *handle = (PHYSFS_File *) rw->hidden.unknown.data1;
+ const PHYSFS_uint64 writelen = (PHYSFS_uint64) (num * size);
+ const PHYSFS_sint64 rc = PHYSFS_writeBytes(handle, ptr, writelen);
+ if (rc != ((PHYSFS_sint64) writelen))
+ SDL_SetError("PhysicsFS error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+
+ #if TARGET_SDL2
+ return (size_t) rc;
+ #else
+ return (int) rc;
+ #endif
+} /* physfsrwops_write */
+
+
+static int physfsrwops_close(SDL_RWops *rw)
+{
+ PHYSFS_File *handle = (PHYSFS_File *) rw->hidden.unknown.data1;
+ if (!PHYSFS_close(handle))
+ {
+ SDL_SetError("PhysicsFS error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return -1;
+ } /* if */
+
+ SDL_FreeRW(rw);
+ return 0;
+} /* physfsrwops_close */
+
+
+static SDL_RWops *create_rwops(PHYSFS_File *handle)
+{
+ SDL_RWops *retval = NULL;
+
+ if (handle == NULL)
+ SDL_SetError("PhysicsFS error: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ else
+ {
+ retval = SDL_AllocRW();
+ if (retval != NULL)
+ {
+ #if TARGET_SDL2
+ retval->size = physfsrwops_size;
+ #endif
+ retval->seek = physfsrwops_seek;
+ retval->read = physfsrwops_read;
+ retval->write = physfsrwops_write;
+ retval->close = physfsrwops_close;
+ retval->hidden.unknown.data1 = handle;
+ } /* if */
+ } /* else */
+
+ return retval;
+} /* create_rwops */
+
+
+SDL_RWops *PHYSFSRWOPS_makeRWops(PHYSFS_File *handle)
+{
+ SDL_RWops *retval = NULL;
+ if (handle == NULL)
+ SDL_SetError("NULL pointer passed to PHYSFSRWOPS_makeRWops().");
+ else
+ retval = create_rwops(handle);
+
+ return retval;
+} /* PHYSFSRWOPS_makeRWops */
+
+
+SDL_RWops *PHYSFSRWOPS_openRead(const char *fname)
+{
+ return create_rwops(PHYSFS_openRead(fname));
+} /* PHYSFSRWOPS_openRead */
+
+
+SDL_RWops *PHYSFSRWOPS_openWrite(const char *fname)
+{
+ return create_rwops(PHYSFS_openWrite(fname));
+} /* PHYSFSRWOPS_openWrite */
+
+
+SDL_RWops *PHYSFSRWOPS_openAppend(const char *fname)
+{
+ return create_rwops(PHYSFS_openAppend(fname));
+} /* PHYSFSRWOPS_openAppend */
+
+
+/* end of physfsrwops.c ... */
+
diff --git a/third-party/physfs/extras/physfsrwops.h b/third-party/physfs/extras/physfsrwops.h
new file mode 100644
index 0000000..54b08a7
--- /dev/null
+++ b/third-party/physfs/extras/physfsrwops.h
@@ -0,0 +1,89 @@
+/*
+ * This code provides a glue layer between PhysicsFS and Simple Directmedia
+ * Layer's (SDL) RWops i/o abstraction.
+ *
+ * License: this code is public domain. I make no warranty that it is useful,
+ * correct, harmless, or environmentally safe.
+ *
+ * This particular file may be used however you like, including copying it
+ * verbatim into a closed-source project, exploiting it commercially, and
+ * removing any trace of my name from the source (although I hope you won't
+ * do that). I welcome enhancements and corrections to this file, but I do
+ * not require you to send me patches if you make changes. This code has
+ * NO WARRANTY.
+ *
+ * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
+ * Please see LICENSE.txt in the root of the source tree.
+ *
+ * SDL 1.2 falls under the LGPL license. SDL 1.3+ is zlib, like PhysicsFS.
+ * You can get SDL at https://www.libsdl.org/
+ *
+ * This file was written by Ryan C. Gordon. (icculus@icculus.org).
+ */
+
+#ifndef _INCLUDE_PHYSFSRWOPS_H_
+#define _INCLUDE_PHYSFSRWOPS_H_
+
+#include "physfs.h"
+#include "SDL.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Open a platform-independent filename for reading, and make it accessible
+ * via an SDL_RWops structure. The file will be closed in PhysicsFS when the
+ * RWops is closed. PhysicsFS should be configured to your liking before
+ * opening files through this method.
+ *
+ * @param filename File to open in platform-independent notation.
+ * @return A valid SDL_RWops structure on success, NULL on error. Specifics
+ * of the error can be gleaned from PHYSFS_getLastError().
+ */
+PHYSFS_DECL SDL_RWops *PHYSFSRWOPS_openRead(const char *fname);
+
+/**
+ * Open a platform-independent filename for writing, and make it accessible
+ * via an SDL_RWops structure. The file will be closed in PhysicsFS when the
+ * RWops is closed. PhysicsFS should be configured to your liking before
+ * opening files through this method.
+ *
+ * @param filename File to open in platform-independent notation.
+ * @return A valid SDL_RWops structure on success, NULL on error. Specifics
+ * of the error can be gleaned from PHYSFS_getLastError().
+ */
+PHYSFS_DECL SDL_RWops *PHYSFSRWOPS_openWrite(const char *fname);
+
+/**
+ * Open a platform-independent filename for appending, and make it accessible
+ * via an SDL_RWops structure. The file will be closed in PhysicsFS when the
+ * RWops is closed. PhysicsFS should be configured to your liking before
+ * opening files through this method.
+ *
+ * @param filename File to open in platform-independent notation.
+ * @return A valid SDL_RWops structure on success, NULL on error. Specifics
+ * of the error can be gleaned from PHYSFS_getLastError().
+ */
+PHYSFS_DECL SDL_RWops *PHYSFSRWOPS_openAppend(const char *fname);
+
+/**
+ * Make a SDL_RWops from an existing PhysicsFS file handle. You should
+ * dispose of any references to the handle after successful creation of
+ * the RWops. The actual PhysicsFS handle will be destroyed when the
+ * RWops is closed.
+ *
+ * @param handle a valid PhysicsFS file handle.
+ * @return A valid SDL_RWops structure on success, NULL on error. Specifics
+ * of the error can be gleaned from PHYSFS_getLastError().
+ */
+PHYSFS_DECL SDL_RWops *PHYSFSRWOPS_makeRWops(PHYSFS_File *handle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* include-once blocker */
+
+/* end of physfsrwops.h ... */
+
diff --git a/third-party/physfs/extras/physfsunpack.c b/third-party/physfs/extras/physfsunpack.c
new file mode 100644
index 0000000..96f87de
--- /dev/null
+++ b/third-party/physfs/extras/physfsunpack.c
@@ -0,0 +1,181 @@
+#include
+#include
+#include
+#include
+
+#include "physfs.h"
+
+
+static int failure = 0;
+
+static void modTimeToStr(PHYSFS_sint64 modtime, char *modstr, size_t strsize)
+{
+ const char *str = "unknown modtime";
+ if (modtime != -1)
+ {
+ time_t t = (time_t) modtime;
+ str = ctime(&t);
+ } /* if */
+
+ strncpy(modstr, str, strsize);
+ modstr[strsize-1] = '\0';
+ strsize = strlen(modstr);
+ while ((modstr[strsize-1] == '\n') || (modstr[strsize-1] == '\r'))
+ modstr[--strsize] = '\0';
+} /* modTimeToStr */
+
+
+static void fail(const char *what, const char *why)
+{
+ if (why == NULL)
+ why = PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode());
+ fprintf(stderr, "%s failed: %s\n", what, why);
+ failure = 1;
+} /* fail */
+
+
+static void dumpFile(const char *fname)
+{
+ const int origfailure = failure;
+ PHYSFS_File *out = NULL;
+ PHYSFS_File *in = NULL;
+
+ failure = 0;
+
+ if ((in = PHYSFS_openRead(fname)) == NULL)
+ fail("\nPHYSFS_openRead", NULL);
+ else if ((out = PHYSFS_openWrite(fname)) == NULL)
+ fail("\nPHYSFS_openWrite", NULL);
+ else
+ {
+ char modstr[64];
+ PHYSFS_sint64 size = PHYSFS_fileLength(in);
+
+ printf("(");
+ if (size == -1)
+ printf("?");
+ else
+ printf("%lld", (long long) size);
+ printf(" bytes");
+
+ modTimeToStr(PHYSFS_getLastModTime(fname), modstr, sizeof (modstr));
+ printf(", %s)\n", modstr);
+
+ while ( (!failure) && (!PHYSFS_eof(in)) )
+ {
+ static char buf[64 * 1024];
+ PHYSFS_sint64 br = PHYSFS_read(in, buf, 1, sizeof (buf));
+ if (br == -1)
+ fail("PHYSFS_read", NULL);
+ else
+ {
+ PHYSFS_sint64 bw = PHYSFS_write(out, buf, 1, br);
+ if (bw != br)
+ fail("PHYSFS_write", NULL);
+ else
+ size -= bw;
+ } /* else */
+ } /* while */
+
+ if ((!failure) && (size != 0))
+ fail("PHYSFS_eof", "BUG! eof != PHYSFS_fileLength bytes!");
+ } /* else */
+
+ if (in != NULL)
+ PHYSFS_close(in);
+
+ if (out != NULL)
+ {
+ if (!PHYSFS_close(out))
+ fail("PHYSFS_close", NULL);
+ } /* if */
+
+ if (failure)
+ PHYSFS_delete(fname);
+ else
+ failure = origfailure;
+} /* dumpFile */
+
+
+static void unpackCallback(void *_depth, const char *origdir, const char *str)
+{
+ int depth = *((int *) _depth);
+ const int len = strlen(origdir) + strlen(str) + 2;
+ char *fname = (char *) malloc(len);
+ if (fname == NULL)
+ fail("malloc", "Out of memory!");
+ else
+ {
+ if (strcmp(origdir, "/") == 0)
+ origdir = "";
+
+ snprintf(fname, len, "%s/%s", origdir, str);
+
+ printf("%s ", fname);
+ if (PHYSFS_isDirectory(fname))
+ {
+ depth++;
+ printf("(directory)\n");
+ if (!PHYSFS_mkdir(fname))
+ fail("PHYSFS_mkdir", NULL);
+ else
+ PHYSFS_enumerateFilesCallback(fname, unpackCallback, &depth);
+ } /* if */
+
+ else if (PHYSFS_isSymbolicLink(fname))
+ {
+ printf("(symlink)\n");
+ /* !!! FIXME: ? if (!symlink(fname, */
+ } /* else if */
+
+ else /* ...file. */
+ {
+ dumpFile(fname);
+ } /* else */
+
+ free(fname);
+ } /* else */
+} /* unpackCallback */
+
+
+int main(int argc, char **argv)
+{
+ int zero = 0;
+
+ if (argc != 3)
+ {
+ fprintf(stderr, "USAGE: %s \n", argv[0]);
+ return 1;
+ } /* if */
+
+ if (!PHYSFS_init(argv[0]))
+ {
+ fprintf(stderr, "PHYSFS_init() failed: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return 2;
+ } /* if */
+
+ if (!PHYSFS_setWriteDir(argv[2]))
+ {
+ fprintf(stderr, "PHYSFS_setWriteDir('%s') failed: %s\n",
+ argv[2], PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return 3;
+ } /* if */
+
+ if (!PHYSFS_mount(argv[1], NULL, 1))
+ {
+ fprintf(stderr, "PHYSFS_mount('%s') failed: %s\n",
+ argv[1], PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return 4;
+ } /* if */
+
+ PHYSFS_permitSymbolicLinks(1);
+ PHYSFS_enumerateFilesCallback("/", unpackCallback, &zero);
+ PHYSFS_deinit();
+ if (failure)
+ return 5;
+
+ return 0;
+} /* main */
+
+/* end of physfsunpack.c ... */
+
diff --git a/third-party/physfs/extras/selfextract.c b/third-party/physfs/extras/selfextract.c
new file mode 100644
index 0000000..2a110e8
--- /dev/null
+++ b/third-party/physfs/extras/selfextract.c
@@ -0,0 +1,66 @@
+/*
+ * This code shows how to read a zipfile included in an app's binary.
+ *
+ * License: this code is public domain. I make no warranty that it is useful,
+ * correct, harmless, or environmentally safe.
+ *
+ * This particular file may be used however you like, including copying it
+ * verbatim into a closed-source project, exploiting it commercially, and
+ * removing any trace of my name from the source (although I hope you won't
+ * do that). I welcome enhancements and corrections to this file, but I do
+ * not require you to send me patches if you make changes. This code has
+ * NO WARRANTY.
+ *
+ * Unless otherwise stated, the rest of PhysicsFS falls under the zlib license.
+ * Please see LICENSE.txt in the root of the source tree.
+ *
+ * This file was written by Ryan C. Gordon. (icculus@icculus.org).
+ */
+
+/*
+ * Compile this program and then attach a .zip file to the end of the
+ * compiled binary.
+ *
+ * On Linux, something like this will build the final binary:
+ * gcc -o selfextract.tmp selfextract.c -lphysfs && \
+ * cat selfextract.tmp myzipfile.zip >> selfextract && \
+ * chmod a+x selfextract && \
+ * rm -f selfextract.tmp
+ *
+ * This may not work on all platforms, and it probably only works with
+ * .zip files, since they are designed to be appended to another file.
+ */
+
+#include
+#include "physfs.h"
+
+int main(int argc, char **argv)
+{
+ int rc = 0;
+
+ if (!PHYSFS_init(argv[0]))
+ {
+ printf("PHYSFS_init() failed: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return 42;
+ } /* if */
+
+ rc = PHYSFS_addToSearchPath(argv[0], 0);
+ if (!rc)
+ {
+ printf("Couldn't find self-extract data: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ printf("This might mean you didn't append a zipfile to the binary.\n");
+ return 42;
+ } /* if */
+
+ char **files = PHYSFS_enumerateFiles("/");
+ char **i;
+ for (i = files; *i != NULL; i++)
+ {
+ const char *dirorfile = PHYSFS_isDirectory(*i) ? "Directory" : "File";
+ printf(" * %s '%s' is in root of attached data.\n", dirorfile, *i);
+ } /* for */
+ PHYSFS_freeList(files);
+
+ return 0;
+} /* main */
+
diff --git a/third-party/physfs/extras/uninstall.sh b/third-party/physfs/extras/uninstall.sh
new file mode 100644
index 0000000..69e0111
--- /dev/null
+++ b/third-party/physfs/extras/uninstall.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+if [ ! -f "./install_manifest.txt" ]; then
+ echo "ERROR: This needs to be run from your CMake build directory after installing." 1>&2
+ exit 1
+fi
+
+xargs rm -vf < install_manifest.txt
+exit 0
+
diff --git a/third-party/physfs/src/Makefile.os2 b/third-party/physfs/src/Makefile.os2
new file mode 100644
index 0000000..1974f79
--- /dev/null
+++ b/third-party/physfs/src/Makefile.os2
@@ -0,0 +1,93 @@
+# Open Watcom makefile to build PhysicsFS for OS/2
+# wmake -f Makefile.os2
+
+LIBNAME = physfs
+VERSION = 3.2.0
+
+LIBFILE = $(LIBNAME).lib
+DLLFILE = $(LIBNAME).dll
+LNKFILE = $(LIBNAME).lnk
+
+TITLENAME = $(LIBNAME) $(VERSION)
+
+SRCS = physfs.c &
+ physfs_byteorder.c &
+ physfs_unicode.c &
+ physfs_platform_os2.c &
+ physfs_archiver_dir.c &
+ physfs_archiver_unpacked.c &
+ physfs_archiver_grp.c &
+ physfs_archiver_hog.c &
+ physfs_archiver_7z.c &
+ physfs_archiver_mvl.c &
+ physfs_archiver_qpak.c &
+ physfs_archiver_wad.c &
+ physfs_archiver_zip.c &
+ physfs_archiver_slb.c &
+ physfs_archiver_iso9660.c &
+ physfs_archiver_vdf.c
+
+
+OBJS = $(SRCS:.c=.obj)
+
+CFLAGS_BASE = -bt=os2 -d0 -q -bm -5s -fp5 -fpi87 -sg -oeatxh -ei -j
+CFLAGS_BASE+= -DNDEBUG
+# warnings:
+CFLAGS_BASE+= -wx
+# newer OpenWatcom versions enable W303 by default
+CFLAGS_BASE+= -wcd=303
+# include paths:
+CFLAGS_BASE+= -I"$(%WATCOM)/h/os2" -I"$(%WATCOM)/h"
+CFLAGS = $(CFLAGS_BASE)
+# to build a dll:
+CFLAGS+= -bd
+
+.extensions:
+.extensions: .lib .dll .obj .c
+
+all: $(DLLFILE) test_physfs.exe
+
+.c: decoders
+.c: examples
+
+$(LIBFILE): $(DLLFILE)
+ @echo * Create library: $@...
+ wlib -b -n -q -c -pa -s -t -zld -ii -io $@ $(DLLFILE)
+
+$(DLLFILE): $(OBJS) $(MODPLIB) $(TIMILIB) $(LNKFILE)
+ @echo * Link: $@
+ wlink @$(LNKFILE)
+
+$(LNKFILE):
+ @%create $@
+ @%append $@ SYSTEM os2v2_dll INITINSTANCE TERMINSTANCE
+ @%append $@ NAME $(LIBNAME)
+ @for %i in ($(OBJS)) do @%append $@ FILE %i
+ @%append $@ OPTION QUIET
+ @%append $@ OPTION DESCRIPTION '@$#icculus org:$(VERSION)$#@PhysicsFS'
+ @%append $@ OPTION MAP=$^&.map
+ @%append $@ OPTION ELIMINATE
+ @%append $@ OPTION MANYAUTODATA
+ @%append $@ OPTION OSNAME='OS/2 and eComStation'
+ @%append $@ OPTION SHOWDEAD
+
+.c.obj:
+ wcc386 $(CFLAGS) -fo=$^@ $<
+
+test_physfs.obj: "../test/test_physfs.c"
+ wcc386 $(CFLAGS_BASE) -fo=$^@ $<
+
+test_physfs.exe: $(LIBFILE) test_physfs.obj
+ @echo * Link: $@
+ wlink SYS os2v2 LIBR {$(LIBFILE)} op q op el F {test_physfs.obj} N test_physfs.exe
+
+clean: .SYMBOLIC
+ @echo * Clean: $(TITLENAME)
+ @if exist *.obj rm *.obj
+ @if exist *.err rm *.err
+ @if exist $(LNKFILE) rm $(LNKFILE)
+distclean: .SYMBOLIC clean
+ @if exist $(DLLFILE) rm $(DLLFILE)
+ @if exist $(LIBFILE) rm $(LIBFILE)
+ @if exist *.map rm *.map
+ @if exist *.exe rm *.exe
diff --git a/third-party/physfs/src/physfs.c b/third-party/physfs/src/physfs.c
new file mode 100644
index 0000000..6476e0a
--- /dev/null
+++ b/third-party/physfs/src/physfs.c
@@ -0,0 +1,3462 @@
+/**
+ * PhysicsFS; a portable, flexible file i/o abstraction.
+ *
+ * Documentation is in physfs.h. It's verbose, honest. :)
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if defined(_MSC_VER)
+/* this code came from https://stackoverflow.com/a/8712996 */
+int __PHYSFS_msvc_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
+{
+ int count = -1;
+
+ if (size != 0)
+ count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
+ if (count == -1)
+ count = _vscprintf(format, ap);
+
+ return count;
+}
+
+int __PHYSFS_msvc_snprintf(char *outBuf, size_t size, const char *format, ...)
+{
+ int count;
+ va_list ap;
+
+ va_start(ap, format);
+ count = __PHYSFS_msvc_vsnprintf(outBuf, size, format, ap);
+ va_end(ap);
+
+ return count;
+}
+#endif
+
+
+typedef struct __PHYSFS_DIRHANDLE__
+{
+ void *opaque; /* Instance data unique to the archiver. */
+ char *dirName; /* Path to archive in platform-dependent notation. */
+ char *mountPoint; /* Mountpoint in virtual file tree. */
+ char *root; /* subdirectory of archiver to use as root of archive (NULL for actual root) */
+ size_t rootlen; /* subdirectory of archiver to use as root of archive (NULL for actual root) */
+ const PHYSFS_Archiver *funcs; /* Ptr to archiver info for this handle. */
+ struct __PHYSFS_DIRHANDLE__ *next; /* linked list stuff. */
+} DirHandle;
+
+
+typedef struct __PHYSFS_FILEHANDLE__
+{
+ PHYSFS_Io *io; /* Instance data unique to the archiver for this file. */
+ PHYSFS_uint8 forReading; /* Non-zero if reading, zero if write/append */
+ const DirHandle *dirHandle; /* Archiver instance that created this */
+ PHYSFS_uint8 *buffer; /* Buffer, if set (NULL otherwise). Don't touch! */
+ size_t bufsize; /* Bufsize, if set (0 otherwise). Don't touch! */
+ size_t buffill; /* Buffer fill size. Don't touch! */
+ size_t bufpos; /* Buffer position. Don't touch! */
+ struct __PHYSFS_FILEHANDLE__ *next; /* linked list stuff. */
+} FileHandle;
+
+
+typedef struct __PHYSFS_ERRSTATETYPE__
+{
+ void *tid;
+ PHYSFS_ErrorCode code;
+ struct __PHYSFS_ERRSTATETYPE__ *next;
+} ErrState;
+
+
+/* General PhysicsFS state ... */
+static int initialized = 0;
+static ErrState *errorStates = NULL;
+static DirHandle *searchPath = NULL;
+static DirHandle *writeDir = NULL;
+static FileHandle *openWriteList = NULL;
+static FileHandle *openReadList = NULL;
+static char *baseDir = NULL;
+static char *userDir = NULL;
+static char *prefDir = NULL;
+static int allowSymLinks = 0;
+static PHYSFS_Archiver **archivers = NULL;
+static PHYSFS_ArchiveInfo **archiveInfo = NULL;
+static volatile size_t numArchivers = 0;
+static size_t longest_root = 0;
+
+/* mutexes ... */
+static void *errorLock = NULL; /* protects error message list. */
+static void *stateLock = NULL; /* protects other PhysFS static state. */
+
+/* allocator ... */
+static int externalAllocator = 0;
+PHYSFS_Allocator allocator;
+
+
+#ifdef PHYSFS_NEED_ATOMIC_OP_FALLBACK
+static inline int __PHYSFS_atomicAdd(int *ptrval, const int val)
+{
+ int retval;
+ __PHYSFS_platformGrabMutex(stateLock);
+ *ptrval += val;
+ retval = *ptrval;
+ __PHYSFS_platformReleaseMutex(stateLock);
+ return retval;
+} /* __PHYSFS_atomicAdd */
+
+int __PHYSFS_ATOMIC_INCR(int *ptrval)
+{
+ return __PHYSFS_atomicAdd(ptrval, 1);
+} /* __PHYSFS_ATOMIC_INCR */
+
+int __PHYSFS_ATOMIC_DECR(int *ptrval)
+{
+ return __PHYSFS_atomicAdd(ptrval, -1);
+} /* __PHYSFS_ATOMIC_DECR */
+#endif
+
+
+
+/* PHYSFS_Io implementation for i/o to physical filesystem... */
+
+/* !!! FIXME: maybe refcount the paths in a string pool? */
+typedef struct __PHYSFS_NativeIoInfo
+{
+ void *handle;
+ const char *path;
+ int mode; /* 'r', 'w', or 'a' */
+} NativeIoInfo;
+
+static PHYSFS_sint64 nativeIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
+{
+ NativeIoInfo *info = (NativeIoInfo *) io->opaque;
+ return __PHYSFS_platformRead(info->handle, buf, len);
+} /* nativeIo_read */
+
+static PHYSFS_sint64 nativeIo_write(PHYSFS_Io *io, const void *buffer,
+ PHYSFS_uint64 len)
+{
+ NativeIoInfo *info = (NativeIoInfo *) io->opaque;
+ return __PHYSFS_platformWrite(info->handle, buffer, len);
+} /* nativeIo_write */
+
+static int nativeIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
+{
+ NativeIoInfo *info = (NativeIoInfo *) io->opaque;
+ return __PHYSFS_platformSeek(info->handle, offset);
+} /* nativeIo_seek */
+
+static PHYSFS_sint64 nativeIo_tell(PHYSFS_Io *io)
+{
+ NativeIoInfo *info = (NativeIoInfo *) io->opaque;
+ return __PHYSFS_platformTell(info->handle);
+} /* nativeIo_tell */
+
+static PHYSFS_sint64 nativeIo_length(PHYSFS_Io *io)
+{
+ NativeIoInfo *info = (NativeIoInfo *) io->opaque;
+ return __PHYSFS_platformFileLength(info->handle);
+} /* nativeIo_length */
+
+static PHYSFS_Io *nativeIo_duplicate(PHYSFS_Io *io)
+{
+ NativeIoInfo *info = (NativeIoInfo *) io->opaque;
+ return __PHYSFS_createNativeIo(info->path, info->mode);
+} /* nativeIo_duplicate */
+
+static int nativeIo_flush(PHYSFS_Io *io)
+{
+ NativeIoInfo *info = (NativeIoInfo *) io->opaque;
+ return __PHYSFS_platformFlush(info->handle);
+} /* nativeIo_flush */
+
+static void nativeIo_destroy(PHYSFS_Io *io)
+{
+ NativeIoInfo *info = (NativeIoInfo *) io->opaque;
+ __PHYSFS_platformClose(info->handle);
+ allocator.Free((void *) info->path);
+ allocator.Free(info);
+ allocator.Free(io);
+} /* nativeIo_destroy */
+
+static const PHYSFS_Io __PHYSFS_nativeIoInterface =
+{
+ CURRENT_PHYSFS_IO_API_VERSION, NULL,
+ nativeIo_read,
+ nativeIo_write,
+ nativeIo_seek,
+ nativeIo_tell,
+ nativeIo_length,
+ nativeIo_duplicate,
+ nativeIo_flush,
+ nativeIo_destroy
+};
+
+PHYSFS_Io *__PHYSFS_createNativeIo(const char *path, const int mode)
+{
+ PHYSFS_Io *io = NULL;
+ NativeIoInfo *info = NULL;
+ void *handle = NULL;
+ char *pathdup = NULL;
+
+ assert((mode == 'r') || (mode == 'w') || (mode == 'a'));
+
+ io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ GOTO_IF(!io, PHYSFS_ERR_OUT_OF_MEMORY, createNativeIo_failed);
+ info = (NativeIoInfo *) allocator.Malloc(sizeof (NativeIoInfo));
+ GOTO_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, createNativeIo_failed);
+ pathdup = (char *) allocator.Malloc(strlen(path) + 1);
+ GOTO_IF(!pathdup, PHYSFS_ERR_OUT_OF_MEMORY, createNativeIo_failed);
+
+ if (mode == 'r')
+ handle = __PHYSFS_platformOpenRead(path);
+ else if (mode == 'w')
+ handle = __PHYSFS_platformOpenWrite(path);
+ else if (mode == 'a')
+ handle = __PHYSFS_platformOpenAppend(path);
+
+ GOTO_IF_ERRPASS(!handle, createNativeIo_failed);
+
+ strcpy(pathdup, path);
+ info->handle = handle;
+ info->path = pathdup;
+ info->mode = mode;
+ memcpy(io, &__PHYSFS_nativeIoInterface, sizeof (*io));
+ io->opaque = info;
+ return io;
+
+createNativeIo_failed:
+ if (handle != NULL) __PHYSFS_platformClose(handle);
+ if (pathdup != NULL) allocator.Free(pathdup);
+ if (info != NULL) allocator.Free(info);
+ if (io != NULL) allocator.Free(io);
+ return NULL;
+} /* __PHYSFS_createNativeIo */
+
+
+/* PHYSFS_Io implementation for i/o to a memory buffer... */
+
+typedef struct __PHYSFS_MemoryIoInfo
+{
+ const PHYSFS_uint8 *buf;
+ PHYSFS_uint64 len;
+ PHYSFS_uint64 pos;
+ PHYSFS_Io *parent;
+ int refcount;
+ void (*destruct)(void *);
+} MemoryIoInfo;
+
+static PHYSFS_sint64 memoryIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
+{
+ MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+ const PHYSFS_uint64 avail = info->len - info->pos;
+ assert(avail <= info->len);
+
+ if (avail == 0)
+ return 0; /* we're at EOF; nothing to do. */
+
+ if (len > avail)
+ len = avail;
+
+ memcpy(buf, info->buf + info->pos, (size_t) len);
+ info->pos += len;
+ return len;
+} /* memoryIo_read */
+
+static PHYSFS_sint64 memoryIo_write(PHYSFS_Io *io, const void *buffer,
+ PHYSFS_uint64 len)
+{
+ BAIL(PHYSFS_ERR_OPEN_FOR_READING, -1);
+} /* memoryIo_write */
+
+static int memoryIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
+{
+ MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+ BAIL_IF(offset > info->len, PHYSFS_ERR_PAST_EOF, 0);
+ info->pos = offset;
+ return 1;
+} /* memoryIo_seek */
+
+static PHYSFS_sint64 memoryIo_tell(PHYSFS_Io *io)
+{
+ const MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+ return (PHYSFS_sint64) info->pos;
+} /* memoryIo_tell */
+
+static PHYSFS_sint64 memoryIo_length(PHYSFS_Io *io)
+{
+ const MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+ return (PHYSFS_sint64) info->len;
+} /* memoryIo_length */
+
+static PHYSFS_Io *memoryIo_duplicate(PHYSFS_Io *io)
+{
+ MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+ MemoryIoInfo *newinfo = NULL;
+ PHYSFS_Io *parent = info->parent;
+ PHYSFS_Io *retval = NULL;
+
+ /* avoid deep copies. */
+ assert((!parent) || (!((MemoryIoInfo *) parent->opaque)->parent) );
+
+ /* share the buffer between duplicates. */
+ if (parent != NULL) /* dup the parent, increment its refcount. */
+ return parent->duplicate(parent);
+
+ /* we're the parent. */
+
+ retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ newinfo = (MemoryIoInfo *) allocator.Malloc(sizeof (MemoryIoInfo));
+ if (!newinfo)
+ {
+ allocator.Free(retval);
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ } /* if */
+
+ __PHYSFS_ATOMIC_INCR(&info->refcount);
+
+ memset(newinfo, '\0', sizeof (*info));
+ newinfo->buf = info->buf;
+ newinfo->len = info->len;
+ newinfo->pos = 0;
+ newinfo->parent = io;
+ newinfo->refcount = 0;
+ newinfo->destruct = NULL;
+
+ memcpy(retval, io, sizeof (*retval));
+ retval->opaque = newinfo;
+ return retval;
+} /* memoryIo_duplicate */
+
+static int memoryIo_flush(PHYSFS_Io *io) { return 1; /* it's read-only. */ }
+
+static void memoryIo_destroy(PHYSFS_Io *io)
+{
+ MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+ PHYSFS_Io *parent = info->parent;
+
+ if (parent != NULL)
+ {
+ assert(info->buf == ((MemoryIoInfo *) info->parent->opaque)->buf);
+ assert(info->len == ((MemoryIoInfo *) info->parent->opaque)->len);
+ assert(info->refcount == 0);
+ assert(info->destruct == NULL);
+ allocator.Free(info);
+ allocator.Free(io);
+ parent->destroy(parent); /* decrements refcount. */
+ return;
+ } /* if */
+
+ /* we _are_ the parent. */
+ assert(info->refcount > 0); /* even in a race, we hold a reference. */
+
+ if (__PHYSFS_ATOMIC_DECR(&info->refcount) == 0)
+ {
+ void (*destruct)(void *) = info->destruct;
+ void *buf = (void *) info->buf;
+ io->opaque = NULL; /* kill this here in case of race. */
+ allocator.Free(info);
+ allocator.Free(io);
+ if (destruct != NULL)
+ destruct(buf);
+ } /* if */
+} /* memoryIo_destroy */
+
+
+static const PHYSFS_Io __PHYSFS_memoryIoInterface =
+{
+ CURRENT_PHYSFS_IO_API_VERSION, NULL,
+ memoryIo_read,
+ memoryIo_write,
+ memoryIo_seek,
+ memoryIo_tell,
+ memoryIo_length,
+ memoryIo_duplicate,
+ memoryIo_flush,
+ memoryIo_destroy
+};
+
+PHYSFS_Io *__PHYSFS_createMemoryIo(const void *buf, PHYSFS_uint64 len,
+ void (*destruct)(void *))
+{
+ PHYSFS_Io *io = NULL;
+ MemoryIoInfo *info = NULL;
+
+ io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ GOTO_IF(!io, PHYSFS_ERR_OUT_OF_MEMORY, createMemoryIo_failed);
+ info = (MemoryIoInfo *) allocator.Malloc(sizeof (MemoryIoInfo));
+ GOTO_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, createMemoryIo_failed);
+
+ memset(info, '\0', sizeof (*info));
+ info->buf = (const PHYSFS_uint8 *) buf;
+ info->len = len;
+ info->pos = 0;
+ info->parent = NULL;
+ info->refcount = 1;
+ info->destruct = destruct;
+
+ memcpy(io, &__PHYSFS_memoryIoInterface, sizeof (*io));
+ io->opaque = info;
+ return io;
+
+createMemoryIo_failed:
+ if (info != NULL) allocator.Free(info);
+ if (io != NULL) allocator.Free(io);
+ return NULL;
+} /* __PHYSFS_createMemoryIo */
+
+
+/* PHYSFS_Io implementation for i/o to a PHYSFS_File... */
+
+static PHYSFS_sint64 handleIo_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
+{
+ return PHYSFS_readBytes((PHYSFS_File *) io->opaque, buf, len);
+} /* handleIo_read */
+
+static PHYSFS_sint64 handleIo_write(PHYSFS_Io *io, const void *buffer,
+ PHYSFS_uint64 len)
+{
+ return PHYSFS_writeBytes((PHYSFS_File *) io->opaque, buffer, len);
+} /* handleIo_write */
+
+static int handleIo_seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
+{
+ return PHYSFS_seek((PHYSFS_File *) io->opaque, offset);
+} /* handleIo_seek */
+
+static PHYSFS_sint64 handleIo_tell(PHYSFS_Io *io)
+{
+ return PHYSFS_tell((PHYSFS_File *) io->opaque);
+} /* handleIo_tell */
+
+static PHYSFS_sint64 handleIo_length(PHYSFS_Io *io)
+{
+ return PHYSFS_fileLength((PHYSFS_File *) io->opaque);
+} /* handleIo_length */
+
+static PHYSFS_Io *handleIo_duplicate(PHYSFS_Io *io)
+{
+ /*
+ * There's no duplicate at the PHYSFS_File level, so we break the
+ * abstraction. We're allowed to: we're physfs.c!
+ */
+ FileHandle *origfh = (FileHandle *) io->opaque;
+ FileHandle *newfh = (FileHandle *) allocator.Malloc(sizeof (FileHandle));
+ PHYSFS_Io *retval = NULL;
+
+ GOTO_IF(!newfh, PHYSFS_ERR_OUT_OF_MEMORY, handleIo_dupe_failed);
+ memset(newfh, '\0', sizeof (*newfh));
+
+ retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, handleIo_dupe_failed);
+
+#if 0 /* we don't buffer the duplicate, at least not at the moment. */
+ if (origfh->buffer != NULL)
+ {
+ newfh->buffer = (PHYSFS_uint8 *) allocator.Malloc(origfh->bufsize);
+ if (!newfh->buffer)
+ GOTO(PHYSFS_ERR_OUT_OF_MEMORY, handleIo_dupe_failed);
+ newfh->bufsize = origfh->bufsize;
+ } /* if */
+#endif
+
+ newfh->io = origfh->io->duplicate(origfh->io);
+ GOTO_IF_ERRPASS(!newfh->io, handleIo_dupe_failed);
+
+ newfh->forReading = origfh->forReading;
+ newfh->dirHandle = origfh->dirHandle;
+
+ __PHYSFS_platformGrabMutex(stateLock);
+ if (newfh->forReading)
+ {
+ newfh->next = openReadList;
+ openReadList = newfh;
+ } /* if */
+ else
+ {
+ newfh->next = openWriteList;
+ openWriteList = newfh;
+ } /* else */
+ __PHYSFS_platformReleaseMutex(stateLock);
+
+ memcpy(retval, io, sizeof (PHYSFS_Io));
+ retval->opaque = newfh;
+ return retval;
+
+handleIo_dupe_failed:
+ if (newfh)
+ {
+ if (newfh->io != NULL) newfh->io->destroy(newfh->io);
+ if (newfh->buffer != NULL) allocator.Free(newfh->buffer);
+ allocator.Free(newfh);
+ } /* if */
+
+ return NULL;
+} /* handleIo_duplicate */
+
+static int handleIo_flush(PHYSFS_Io *io)
+{
+ return PHYSFS_flush((PHYSFS_File *) io->opaque);
+} /* handleIo_flush */
+
+static void handleIo_destroy(PHYSFS_Io *io)
+{
+ if (io->opaque != NULL)
+ PHYSFS_close((PHYSFS_File *) io->opaque);
+ allocator.Free(io);
+} /* handleIo_destroy */
+
+static const PHYSFS_Io __PHYSFS_handleIoInterface =
+{
+ CURRENT_PHYSFS_IO_API_VERSION, NULL,
+ handleIo_read,
+ handleIo_write,
+ handleIo_seek,
+ handleIo_tell,
+ handleIo_length,
+ handleIo_duplicate,
+ handleIo_flush,
+ handleIo_destroy
+};
+
+static PHYSFS_Io *__PHYSFS_createHandleIo(PHYSFS_File *f)
+{
+ PHYSFS_Io *io = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ BAIL_IF(!io, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ memcpy(io, &__PHYSFS_handleIoInterface, sizeof (*io));
+ io->opaque = f;
+ return io;
+} /* __PHYSFS_createHandleIo */
+
+
+/* functions ... */
+
+typedef struct
+{
+ char **list;
+ PHYSFS_uint32 size;
+ PHYSFS_ErrorCode errcode;
+} EnumStringListCallbackData;
+
+static void enumStringListCallback(void *data, const char *str)
+{
+ void *ptr;
+ char *newstr;
+ EnumStringListCallbackData *pecd = (EnumStringListCallbackData *) data;
+
+ if (pecd->errcode)
+ return;
+
+ ptr = allocator.Realloc(pecd->list, (pecd->size + 2) * sizeof (char *));
+ newstr = (char *) allocator.Malloc(strlen(str) + 1);
+ if (ptr != NULL)
+ pecd->list = (char **) ptr;
+
+ if ((ptr == NULL) || (newstr == NULL))
+ {
+ pecd->errcode = PHYSFS_ERR_OUT_OF_MEMORY;
+ pecd->list[pecd->size] = NULL;
+ PHYSFS_freeList(pecd->list);
+ return;
+ } /* if */
+
+ strcpy(newstr, str);
+ pecd->list[pecd->size] = newstr;
+ pecd->size++;
+} /* enumStringListCallback */
+
+
+static char **doEnumStringList(void (*func)(PHYSFS_StringCallback, void *))
+{
+ EnumStringListCallbackData ecd;
+ memset(&ecd, '\0', sizeof (ecd));
+ ecd.list = (char **) allocator.Malloc(sizeof (char *));
+ BAIL_IF(!ecd.list, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ func(enumStringListCallback, &ecd);
+
+ if (ecd.errcode)
+ {
+ PHYSFS_setErrorCode(ecd.errcode);
+ return NULL;
+ } /* if */
+
+ ecd.list[ecd.size] = NULL;
+ return ecd.list;
+} /* doEnumStringList */
+
+
+static void __PHYSFS_bubble_sort(void *a, size_t lo, size_t hi,
+ int (*cmpfn)(void *, size_t, size_t),
+ void (*swapfn)(void *, size_t, size_t))
+{
+ size_t i;
+ int sorted;
+
+ do
+ {
+ sorted = 1;
+ for (i = lo; i < hi; i++)
+ {
+ if (cmpfn(a, i, i + 1) > 0)
+ {
+ swapfn(a, i, i + 1);
+ sorted = 0;
+ } /* if */
+ } /* for */
+ } while (!sorted);
+} /* __PHYSFS_bubble_sort */
+
+
+static void __PHYSFS_quick_sort(void *a, size_t lo, size_t hi,
+ int (*cmpfn)(void *, size_t, size_t),
+ void (*swapfn)(void *, size_t, size_t))
+{
+ size_t i;
+ size_t j;
+ size_t v;
+
+ if ((hi - lo) <= PHYSFS_QUICKSORT_THRESHOLD)
+ __PHYSFS_bubble_sort(a, lo, hi, cmpfn, swapfn);
+ else
+ {
+ i = (hi + lo) / 2;
+
+ if (cmpfn(a, lo, i) > 0) swapfn(a, lo, i);
+ if (cmpfn(a, lo, hi) > 0) swapfn(a, lo, hi);
+ if (cmpfn(a, i, hi) > 0) swapfn(a, i, hi);
+
+ j = hi - 1;
+ swapfn(a, i, j);
+ i = lo;
+ v = j;
+ while (1)
+ {
+ while(cmpfn(a, ++i, v) < 0) { /* do nothing */ }
+ while(cmpfn(a, --j, v) > 0) { /* do nothing */ }
+ if (j < i)
+ break;
+ swapfn(a, i, j);
+ } /* while */
+ if (i != (hi-1))
+ swapfn(a, i, hi-1);
+ __PHYSFS_quick_sort(a, lo, j, cmpfn, swapfn);
+ __PHYSFS_quick_sort(a, i+1, hi, cmpfn, swapfn);
+ } /* else */
+} /* __PHYSFS_quick_sort */
+
+
+void __PHYSFS_sort(void *entries, size_t max,
+ int (*cmpfn)(void *, size_t, size_t),
+ void (*swapfn)(void *, size_t, size_t))
+{
+ /*
+ * Quicksort w/ Bubblesort fallback algorithm inspired by code from here:
+ * https://www.cs.ubc.ca/spider/harrison/Java/sorting-demo.html
+ */
+ if (max > 0)
+ __PHYSFS_quick_sort(entries, 0, max - 1, cmpfn, swapfn);
+} /* __PHYSFS_sort */
+
+
+static ErrState *findErrorForCurrentThread(void)
+{
+ ErrState *i;
+ void *tid;
+
+ if (errorLock != NULL)
+ __PHYSFS_platformGrabMutex(errorLock);
+
+ if (errorStates != NULL)
+ {
+ tid = __PHYSFS_platformGetThreadID();
+
+ for (i = errorStates; i != NULL; i = i->next)
+ {
+ if (i->tid == tid)
+ {
+ if (errorLock != NULL)
+ __PHYSFS_platformReleaseMutex(errorLock);
+ return i;
+ } /* if */
+ } /* for */
+ } /* if */
+
+ if (errorLock != NULL)
+ __PHYSFS_platformReleaseMutex(errorLock);
+
+ return NULL; /* no error available. */
+} /* findErrorForCurrentThread */
+
+
+/* this doesn't reset the error state. */
+static inline PHYSFS_ErrorCode currentErrorCode(void)
+{
+ const ErrState *err = findErrorForCurrentThread();
+ return err ? err->code : PHYSFS_ERR_OK;
+} /* currentErrorCode */
+
+
+PHYSFS_ErrorCode PHYSFS_getLastErrorCode(void)
+{
+ ErrState *err = findErrorForCurrentThread();
+ const PHYSFS_ErrorCode retval = (err) ? err->code : PHYSFS_ERR_OK;
+ if (err)
+ err->code = PHYSFS_ERR_OK;
+ return retval;
+} /* PHYSFS_getLastErrorCode */
+
+
+PHYSFS_DECL const char *PHYSFS_getErrorByCode(PHYSFS_ErrorCode code)
+{
+ switch (code)
+ {
+ case PHYSFS_ERR_OK: return "no error";
+ case PHYSFS_ERR_OTHER_ERROR: return "unknown error";
+ case PHYSFS_ERR_OUT_OF_MEMORY: return "out of memory";
+ case PHYSFS_ERR_NOT_INITIALIZED: return "not initialized";
+ case PHYSFS_ERR_IS_INITIALIZED: return "already initialized";
+ case PHYSFS_ERR_ARGV0_IS_NULL: return "argv[0] is NULL";
+ case PHYSFS_ERR_UNSUPPORTED: return "unsupported";
+ case PHYSFS_ERR_PAST_EOF: return "past end of file";
+ case PHYSFS_ERR_FILES_STILL_OPEN: return "files still open";
+ case PHYSFS_ERR_INVALID_ARGUMENT: return "invalid argument";
+ case PHYSFS_ERR_NOT_MOUNTED: return "not mounted";
+ case PHYSFS_ERR_NOT_FOUND: return "not found";
+ case PHYSFS_ERR_SYMLINK_FORBIDDEN: return "symlinks are forbidden";
+ case PHYSFS_ERR_NO_WRITE_DIR: return "write directory is not set";
+ case PHYSFS_ERR_OPEN_FOR_READING: return "file open for reading";
+ case PHYSFS_ERR_OPEN_FOR_WRITING: return "file open for writing";
+ case PHYSFS_ERR_NOT_A_FILE: return "not a file";
+ case PHYSFS_ERR_READ_ONLY: return "read-only filesystem";
+ case PHYSFS_ERR_CORRUPT: return "corrupted";
+ case PHYSFS_ERR_SYMLINK_LOOP: return "infinite symbolic link loop";
+ case PHYSFS_ERR_IO: return "i/o error";
+ case PHYSFS_ERR_PERMISSION: return "permission denied";
+ case PHYSFS_ERR_NO_SPACE: return "no space available for writing";
+ case PHYSFS_ERR_BAD_FILENAME: return "filename is illegal or insecure";
+ case PHYSFS_ERR_BUSY: return "tried to modify a file the OS needs";
+ case PHYSFS_ERR_DIR_NOT_EMPTY: return "directory isn't empty";
+ case PHYSFS_ERR_OS_ERROR: return "OS reported an error";
+ case PHYSFS_ERR_DUPLICATE: return "duplicate resource";
+ case PHYSFS_ERR_BAD_PASSWORD: return "bad password";
+ case PHYSFS_ERR_APP_CALLBACK: return "app callback reported error";
+ } /* switch */
+
+ return NULL; /* don't know this error code. */
+} /* PHYSFS_getErrorByCode */
+
+
+void PHYSFS_setErrorCode(PHYSFS_ErrorCode errcode)
+{
+ ErrState *err;
+
+ if (!errcode)
+ return;
+
+ err = findErrorForCurrentThread();
+ if (err == NULL)
+ {
+ err = (ErrState *) allocator.Malloc(sizeof (ErrState));
+ if (err == NULL)
+ return; /* uhh...? */
+
+ memset(err, '\0', sizeof (ErrState));
+ err->tid = __PHYSFS_platformGetThreadID();
+
+ if (errorLock != NULL)
+ __PHYSFS_platformGrabMutex(errorLock);
+
+ err->next = errorStates;
+ errorStates = err;
+
+ if (errorLock != NULL)
+ __PHYSFS_platformReleaseMutex(errorLock);
+ } /* if */
+
+ err->code = errcode;
+} /* PHYSFS_setErrorCode */
+
+
+const char *PHYSFS_getLastError(void)
+{
+ const PHYSFS_ErrorCode err = PHYSFS_getLastErrorCode();
+ return (err) ? PHYSFS_getErrorByCode(err) : NULL;
+} /* PHYSFS_getLastError */
+
+
+/* MAKE SURE that errorLock is held before calling this! */
+static void freeErrorStates(void)
+{
+ ErrState *i;
+ ErrState *next;
+
+ for (i = errorStates; i != NULL; i = next)
+ {
+ next = i->next;
+ allocator.Free(i);
+ } /* for */
+
+ errorStates = NULL;
+} /* freeErrorStates */
+
+
+void PHYSFS_getLinkedVersion(PHYSFS_Version *ver)
+{
+ if (ver != NULL)
+ {
+ ver->major = PHYSFS_VER_MAJOR;
+ ver->minor = PHYSFS_VER_MINOR;
+ ver->patch = PHYSFS_VER_PATCH;
+ } /* if */
+} /* PHYSFS_getLinkedVersion */
+
+
+static const char *find_filename_extension(const char *fname)
+{
+ const char *retval = NULL;
+ if (fname != NULL)
+ {
+ const char *p = strchr(fname, '.');
+ retval = p;
+
+ while (p != NULL)
+ {
+ p = strchr(p + 1, '.');
+ if (p != NULL)
+ retval = p;
+ } /* while */
+
+ if (retval != NULL)
+ retval++; /* skip '.' */
+ } /* if */
+
+ return retval;
+} /* find_filename_extension */
+
+
+static DirHandle *tryOpenDir(PHYSFS_Io *io, const PHYSFS_Archiver *funcs,
+ const char *d, int forWriting, int *_claimed)
+{
+ DirHandle *retval = NULL;
+ void *opaque = NULL;
+
+ if (io != NULL)
+ BAIL_IF_ERRPASS(!io->seek(io, 0), NULL);
+
+ opaque = funcs->openArchive(io, d, forWriting, _claimed);
+ if (opaque != NULL)
+ {
+ retval = (DirHandle *) allocator.Malloc(sizeof (DirHandle));
+ if (retval == NULL)
+ funcs->closeArchive(opaque);
+ else
+ {
+ memset(retval, '\0', sizeof (DirHandle));
+ retval->mountPoint = NULL;
+ retval->funcs = funcs;
+ retval->opaque = opaque;
+ } /* else */
+ } /* if */
+
+ return retval;
+} /* tryOpenDir */
+
+
+static DirHandle *openDirectory(PHYSFS_Io *io, const char *d, int forWriting)
+{
+ DirHandle *retval = NULL;
+ PHYSFS_Archiver **i;
+ const char *ext;
+ int created_io = 0;
+ int claimed = 0;
+ PHYSFS_ErrorCode errcode;
+
+ assert((io != NULL) || (d != NULL));
+
+ if (io == NULL)
+ {
+ /* file doesn't exist, etc? Just fail out. */
+ PHYSFS_Stat statbuf;
+ BAIL_IF_ERRPASS(!__PHYSFS_platformStat(d, &statbuf, 1), NULL);
+
+ /* DIR gets first shot (unlike the rest, it doesn't deal with files). */
+ if (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY)
+ {
+ retval = tryOpenDir(io, &__PHYSFS_Archiver_DIR, d, forWriting, &claimed);
+ if (retval || claimed)
+ return retval;
+ } /* if */
+
+ io = __PHYSFS_createNativeIo(d, forWriting ? 'w' : 'r');
+ BAIL_IF_ERRPASS(!io, NULL);
+ created_io = 1;
+ } /* if */
+
+ ext = find_filename_extension(d);
+ if (ext != NULL)
+ {
+ /* Look for archivers with matching file extensions first... */
+ for (i = archivers; (*i != NULL) && (retval == NULL) && !claimed; i++)
+ {
+ if (PHYSFS_utf8stricmp(ext, (*i)->info.extension) == 0)
+ retval = tryOpenDir(io, *i, d, forWriting, &claimed);
+ } /* for */
+
+ /* failing an exact file extension match, try all the others... */
+ for (i = archivers; (*i != NULL) && (retval == NULL) && !claimed; i++)
+ {
+ if (PHYSFS_utf8stricmp(ext, (*i)->info.extension) != 0)
+ retval = tryOpenDir(io, *i, d, forWriting, &claimed);
+ } /* for */
+ } /* if */
+
+ else /* no extension? Try them all. */
+ {
+ for (i = archivers; (*i != NULL) && (retval == NULL) && !claimed; i++)
+ retval = tryOpenDir(io, *i, d, forWriting, &claimed);
+ } /* else */
+
+ errcode = claimed ? currentErrorCode() : PHYSFS_ERR_UNSUPPORTED;
+
+ if ((!retval) && (created_io))
+ io->destroy(io);
+
+ BAIL_IF(!retval, errcode, NULL);
+ return retval;
+} /* openDirectory */
+
+
+/*
+ * Make a platform-independent path string sane. Doesn't actually check the
+ * file hierarchy, it just cleans up the string.
+ * (dst) must be a buffer at least as big as (src), as this is where the
+ * cleaned up string is deposited.
+ * If there are illegal bits in the path (".." entries, etc) then we
+ * return zero and (dst) is undefined. Non-zero if the path was sanitized.
+ */
+static int sanitizePlatformIndependentPath(const char *src, char *dst)
+{
+ char *prev;
+ char ch;
+
+ while (*src == '/') /* skip initial '/' chars... */
+ src++;
+
+ /* Make sure the entire string isn't "." or ".." */
+ if ((strcmp(src, ".") == 0) || (strcmp(src, "..") == 0))
+ BAIL(PHYSFS_ERR_BAD_FILENAME, 0);
+
+ prev = dst;
+ do
+ {
+ ch = *(src++);
+
+ if ((ch == ':') || (ch == '\\')) /* illegal chars in a physfs path. */
+ BAIL(PHYSFS_ERR_BAD_FILENAME, 0);
+
+ if (ch == '/') /* path separator. */
+ {
+ *dst = '\0'; /* "." and ".." are illegal pathnames. */
+ if ((strcmp(prev, ".") == 0) || (strcmp(prev, "..") == 0))
+ BAIL(PHYSFS_ERR_BAD_FILENAME, 0);
+
+ while (*src == '/') /* chop out doubles... */
+ src++;
+
+ if (*src == '\0') /* ends with a pathsep? */
+ break; /* we're done, don't add final pathsep to dst. */
+
+ prev = dst + 1;
+ } /* if */
+
+ *(dst++) = ch;
+ } while (ch != '\0');
+
+ return 1;
+} /* sanitizePlatformIndependentPath */
+
+
+static inline size_t dirHandleRootLen(const DirHandle *h)
+{
+ return h ? h->rootlen : 0;
+} /* dirHandleRootLen */
+
+static inline int sanitizePlatformIndependentPathWithRoot(const DirHandle *h, const char *src, char *dst)
+{
+ return sanitizePlatformIndependentPath(src, dst + dirHandleRootLen(h));
+} /* sanitizePlatformIndependentPathWithRoot */
+
+
+
+/*
+ * Figure out if (fname) is part of (h)'s mountpoint. (fname) must be an
+ * output from sanitizePlatformIndependentPath(), so that it is in a known
+ * state.
+ *
+ * This only finds legitimate segments of a mountpoint. If the mountpoint is
+ * "/a/b/c" and (fname) is "/a/b/c", "/", or "/a/b/c/d", then the results are
+ * all zero. "/a/b" will succeed, though.
+ */
+static int partOfMountPoint(DirHandle *h, char *fname)
+{
+ int rc;
+ size_t len, mntpntlen;
+
+ if (h->mountPoint == NULL)
+ return 0;
+ else if (*fname == '\0')
+ return 1;
+
+ len = strlen(fname);
+ mntpntlen = strlen(h->mountPoint);
+ if (len > mntpntlen) /* can't be a subset of mountpoint. */
+ return 0;
+
+ /* if true, must be not a match or a complete match, but not a subset. */
+ if ((len + 1) == mntpntlen)
+ return 0;
+
+ rc = strncmp(fname, h->mountPoint, len); /* !!! FIXME: case insensitive? */
+ if (rc != 0)
+ return 0; /* not a match. */
+
+ /* make sure /a/b matches /a/b/ and not /a/bc ... */
+ return h->mountPoint[len] == '/';
+} /* partOfMountPoint */
+
+
+static DirHandle *createDirHandle(PHYSFS_Io *io, const char *newDir,
+ const char *mountPoint, int forWriting)
+{
+ DirHandle *dirHandle = NULL;
+ char *tmpmntpnt = NULL;
+
+ assert(newDir != NULL); /* should have caught this higher up. */
+
+ if (mountPoint != NULL)
+ {
+ const size_t len = strlen(mountPoint) + 1;
+ tmpmntpnt = (char *) __PHYSFS_smallAlloc(len);
+ GOTO_IF(!tmpmntpnt, PHYSFS_ERR_OUT_OF_MEMORY, badDirHandle);
+ if (!sanitizePlatformIndependentPath(mountPoint, tmpmntpnt))
+ goto badDirHandle;
+ mountPoint = tmpmntpnt; /* sanitized version. */
+ } /* if */
+
+ dirHandle = openDirectory(io, newDir, forWriting);
+ GOTO_IF_ERRPASS(!dirHandle, badDirHandle);
+
+ dirHandle->dirName = (char *) allocator.Malloc(strlen(newDir) + 1);
+ GOTO_IF(!dirHandle->dirName, PHYSFS_ERR_OUT_OF_MEMORY, badDirHandle);
+ strcpy(dirHandle->dirName, newDir);
+
+ if ((mountPoint != NULL) && (*mountPoint != '\0'))
+ {
+ dirHandle->mountPoint = (char *)allocator.Malloc(strlen(mountPoint)+2);
+ if (!dirHandle->mountPoint)
+ GOTO(PHYSFS_ERR_OUT_OF_MEMORY, badDirHandle);
+ strcpy(dirHandle->mountPoint, mountPoint);
+ strcat(dirHandle->mountPoint, "/");
+ } /* if */
+
+ __PHYSFS_smallFree(tmpmntpnt);
+ return dirHandle;
+
+badDirHandle:
+ if (dirHandle != NULL)
+ {
+ dirHandle->funcs->closeArchive(dirHandle->opaque);
+ allocator.Free(dirHandle->dirName);
+ allocator.Free(dirHandle->mountPoint);
+ allocator.Free(dirHandle);
+ } /* if */
+
+ __PHYSFS_smallFree(tmpmntpnt);
+ return NULL;
+} /* createDirHandle */
+
+
+/* MAKE SURE you've got the stateLock held before calling this! */
+static int freeDirHandle(DirHandle *dh, FileHandle *openList)
+{
+ FileHandle *i;
+
+ if (dh == NULL)
+ return 1;
+
+ for (i = openList; i != NULL; i = i->next)
+ BAIL_IF(i->dirHandle == dh, PHYSFS_ERR_FILES_STILL_OPEN, 0);
+
+ dh->funcs->closeArchive(dh->opaque);
+
+ if (dh->root) allocator.Free(dh->root);
+ allocator.Free(dh->dirName);
+ allocator.Free(dh->mountPoint);
+ allocator.Free(dh);
+ return 1;
+} /* freeDirHandle */
+
+
+static char *calculateBaseDir(const char *argv0)
+{
+ const char dirsep = __PHYSFS_platformDirSeparator;
+ char *retval = NULL;
+ char *ptr = NULL;
+
+ /* Give the platform layer first shot at this. */
+ retval = __PHYSFS_platformCalcBaseDir(argv0);
+ if (retval != NULL)
+ return retval;
+
+ /* We need argv0 to go on. */
+ BAIL_IF(argv0 == NULL, PHYSFS_ERR_ARGV0_IS_NULL, NULL);
+
+ ptr = strrchr(argv0, dirsep);
+ if (ptr != NULL)
+ {
+ const size_t size = ((size_t) (ptr - argv0)) + 1;
+ retval = (char *) allocator.Malloc(size + 1);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ memcpy(retval, argv0, size);
+ retval[size] = '\0';
+ return retval;
+ } /* if */
+
+ /* argv0 wasn't helpful. */
+ BAIL(PHYSFS_ERR_INVALID_ARGUMENT, NULL);
+} /* calculateBaseDir */
+
+
+static int initializeMutexes(void)
+{
+ errorLock = __PHYSFS_platformCreateMutex();
+ if (errorLock == NULL)
+ goto initializeMutexes_failed;
+
+ stateLock = __PHYSFS_platformCreateMutex();
+ if (stateLock == NULL)
+ goto initializeMutexes_failed;
+
+ return 1; /* success. */
+
+initializeMutexes_failed:
+ if (errorLock != NULL)
+ __PHYSFS_platformDestroyMutex(errorLock);
+
+ if (stateLock != NULL)
+ __PHYSFS_platformDestroyMutex(stateLock);
+
+ errorLock = stateLock = NULL;
+ return 0; /* failed. */
+} /* initializeMutexes */
+
+
+static int doRegisterArchiver(const PHYSFS_Archiver *_archiver);
+
+static int initStaticArchivers(void)
+{
+ #define REGISTER_STATIC_ARCHIVER(arc) { \
+ if (!doRegisterArchiver(&__PHYSFS_Archiver_##arc)) { \
+ return 0; \
+ } \
+ }
+
+ #if PHYSFS_SUPPORTS_ZIP
+ REGISTER_STATIC_ARCHIVER(ZIP);
+ #endif
+ #if PHYSFS_SUPPORTS_7Z
+ SZIP_global_init();
+ REGISTER_STATIC_ARCHIVER(7Z);
+ #endif
+ #if PHYSFS_SUPPORTS_GRP
+ REGISTER_STATIC_ARCHIVER(GRP);
+ #endif
+ #if PHYSFS_SUPPORTS_QPAK
+ REGISTER_STATIC_ARCHIVER(QPAK);
+ #endif
+ #if PHYSFS_SUPPORTS_HOG
+ REGISTER_STATIC_ARCHIVER(HOG);
+ #endif
+ #if PHYSFS_SUPPORTS_MVL
+ REGISTER_STATIC_ARCHIVER(MVL);
+ #endif
+ #if PHYSFS_SUPPORTS_WAD
+ REGISTER_STATIC_ARCHIVER(WAD);
+ #endif
+ #if PHYSFS_SUPPORTS_SLB
+ REGISTER_STATIC_ARCHIVER(SLB);
+ #endif
+ #if PHYSFS_SUPPORTS_ISO9660
+ REGISTER_STATIC_ARCHIVER(ISO9660);
+ #endif
+ #if PHYSFS_SUPPORTS_VDF
+ REGISTER_STATIC_ARCHIVER(VDF)
+ #endif
+
+ #undef REGISTER_STATIC_ARCHIVER
+
+ return 1;
+} /* initStaticArchivers */
+
+
+static void setDefaultAllocator(void);
+static int doDeinit(void);
+
+int PHYSFS_init(const char *argv0)
+{
+ BAIL_IF(initialized, PHYSFS_ERR_IS_INITIALIZED, 0);
+
+ if (!externalAllocator)
+ setDefaultAllocator();
+
+ if ((allocator.Init != NULL) && (!allocator.Init())) return 0;
+
+ if (!__PHYSFS_platformInit())
+ {
+ if (allocator.Deinit != NULL) allocator.Deinit();
+ return 0;
+ } /* if */
+
+ /* everything below here can be cleaned up safely by doDeinit(). */
+
+ if (!initializeMutexes()) goto initFailed;
+
+ baseDir = calculateBaseDir(argv0);
+ if (!baseDir) goto initFailed;
+
+ userDir = __PHYSFS_platformCalcUserDir();
+ if (!userDir) goto initFailed;
+
+ /* Platform layer is required to append a dirsep. */
+ #ifndef __ANDROID__ /* it's an APK file, not a directory, on Android. */
+ assert(baseDir[strlen(baseDir) - 1] == __PHYSFS_platformDirSeparator);
+ #endif
+ assert(userDir[strlen(userDir) - 1] == __PHYSFS_platformDirSeparator);
+
+ if (!initStaticArchivers()) goto initFailed;
+
+ initialized = 1;
+
+ /* This makes sure that the error subsystem is initialized. */
+ PHYSFS_setErrorCode(PHYSFS_getLastErrorCode());
+
+ return 1;
+
+initFailed:
+ doDeinit();
+ return 0;
+} /* PHYSFS_init */
+
+
+/* MAKE SURE you hold stateLock before calling this! */
+static int closeFileHandleList(FileHandle **list)
+{
+ FileHandle *i;
+ FileHandle *next = NULL;
+
+ for (i = *list; i != NULL; i = next)
+ {
+ PHYSFS_Io *io = i->io;
+ next = i->next;
+
+ if (io->flush && !io->flush(io))
+ {
+ *list = i;
+ return 0;
+ } /* if */
+
+ io->destroy(io);
+ allocator.Free(i);
+ } /* for */
+
+ *list = NULL;
+ return 1;
+} /* closeFileHandleList */
+
+
+/* MAKE SURE you hold the stateLock before calling this! */
+static void freeSearchPath(void)
+{
+ DirHandle *i;
+ DirHandle *next = NULL;
+
+ closeFileHandleList(&openReadList);
+
+ if (searchPath != NULL)
+ {
+ for (i = searchPath; i != NULL; i = next)
+ {
+ next = i->next;
+ freeDirHandle(i, openReadList);
+ } /* for */
+ searchPath = NULL;
+ } /* if */
+} /* freeSearchPath */
+
+
+/* MAKE SURE you hold stateLock before calling this! */
+static int archiverInUse(const PHYSFS_Archiver *arc, const DirHandle *list)
+{
+ const DirHandle *i;
+ for (i = list; i != NULL; i = i->next)
+ {
+ if (i->funcs == arc)
+ return 1;
+ } /* for */
+
+ return 0; /* not in use */
+} /* archiverInUse */
+
+
+/* MAKE SURE you hold stateLock before calling this! */
+static int doDeregisterArchiver(const size_t idx)
+{
+ const size_t len = (numArchivers - idx) * sizeof (void *);
+ PHYSFS_ArchiveInfo *info = archiveInfo[idx];
+ PHYSFS_Archiver *arc = archivers[idx];
+
+ /* make sure nothing is still using this archiver */
+ if (archiverInUse(arc, searchPath) || archiverInUse(arc, writeDir))
+ BAIL(PHYSFS_ERR_FILES_STILL_OPEN, 0);
+
+ allocator.Free((void *) info->extension);
+ allocator.Free((void *) info->description);
+ allocator.Free((void *) info->author);
+ allocator.Free((void *) info->url);
+ allocator.Free((void *) arc);
+
+ memmove(&archiveInfo[idx], &archiveInfo[idx+1], len);
+ memmove(&archivers[idx], &archivers[idx+1], len);
+
+ assert(numArchivers > 0);
+ numArchivers--;
+
+ return 1;
+} /* doDeregisterArchiver */
+
+
+/* Does NOT hold the state lock; we're shutting down. */
+static void freeArchivers(void)
+{
+ while (numArchivers > 0)
+ {
+ if (!doDeregisterArchiver(numArchivers - 1))
+ assert(!"nothing should be mounted during shutdown.");
+ } /* while */
+
+ allocator.Free(archivers);
+ allocator.Free(archiveInfo);
+ archivers = NULL;
+ archiveInfo = NULL;
+} /* freeArchivers */
+
+
+static int doDeinit(void)
+{
+ closeFileHandleList(&openWriteList);
+ BAIL_IF(!PHYSFS_setWriteDir(NULL), PHYSFS_ERR_FILES_STILL_OPEN, 0);
+
+ freeSearchPath();
+ freeArchivers();
+ freeErrorStates();
+
+ if (baseDir != NULL)
+ {
+ allocator.Free(baseDir);
+ baseDir = NULL;
+ } /* if */
+
+ if (userDir != NULL)
+ {
+ allocator.Free(userDir);
+ userDir = NULL;
+ } /* if */
+
+ if (prefDir != NULL)
+ {
+ allocator.Free(prefDir);
+ prefDir = NULL;
+ } /* if */
+
+ if (archiveInfo != NULL)
+ {
+ allocator.Free(archiveInfo);
+ archiveInfo = NULL;
+ } /* if */
+
+ if (archivers != NULL)
+ {
+ allocator.Free(archivers);
+ archivers = NULL;
+ } /* if */
+
+ longest_root = 0;
+ allowSymLinks = 0;
+ initialized = 0;
+
+ if (errorLock) __PHYSFS_platformDestroyMutex(errorLock);
+ if (stateLock) __PHYSFS_platformDestroyMutex(stateLock);
+
+ if (allocator.Deinit != NULL)
+ allocator.Deinit();
+
+ errorLock = stateLock = NULL;
+
+ __PHYSFS_platformDeinit();
+
+ return 1;
+} /* doDeinit */
+
+
+int PHYSFS_deinit(void)
+{
+ BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0);
+ return doDeinit();
+} /* PHYSFS_deinit */
+
+
+int PHYSFS_isInit(void)
+{
+ return initialized;
+} /* PHYSFS_isInit */
+
+
+char *__PHYSFS_strdup(const char *str)
+{
+ char *retval = (char *) allocator.Malloc(strlen(str) + 1);
+ if (retval)
+ strcpy(retval, str);
+ return retval;
+} /* __PHYSFS_strdup */
+
+
+PHYSFS_uint32 __PHYSFS_hashString(const char *str)
+{
+ PHYSFS_uint32 hash = 5381;
+ while (1)
+ {
+ const char ch = *(str++);
+ if (ch == 0)
+ break;
+ hash = ((hash << 5) + hash) ^ ch;
+ } /* while */
+ return hash;
+} /* __PHYSFS_hashString */
+
+
+PHYSFS_uint32 __PHYSFS_hashStringCaseFold(const char *str)
+{
+ PHYSFS_uint32 hash = 5381;
+ while (1)
+ {
+ const PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&str);
+ if (cp == 0)
+ break;
+ else
+ {
+ PHYSFS_uint32 folded[3];
+ const int numbytes = (int) (PHYSFS_caseFold(cp, folded) * sizeof (PHYSFS_uint32));
+ const char *bytes = (const char *) folded;
+ int i;
+ for (i = 0; i < numbytes; i++)
+ hash = ((hash << 5) + hash) ^ *(bytes++);
+ } /* else */
+ } /* while */
+
+ return hash;
+} /* __PHYSFS_hashStringCaseFold */
+
+
+PHYSFS_uint32 __PHYSFS_hashStringCaseFoldUSAscii(const char *str)
+{
+ PHYSFS_uint32 hash = 5381;
+ while (1)
+ {
+ char ch = *(str++);
+ if (ch == 0)
+ break;
+ else if ((ch >= 'A') && (ch <= 'Z'))
+ ch -= ('A' - 'a');
+
+ hash = ((hash << 5) + hash) ^ ch;
+ } /* while */
+ return hash;
+} /* __PHYSFS_hashStringCaseFoldUSAscii */
+
+
+/* MAKE SURE you hold stateLock before calling this! */
+static int doRegisterArchiver(const PHYSFS_Archiver *_archiver)
+{
+ const PHYSFS_uint32 maxver = CURRENT_PHYSFS_ARCHIVER_API_VERSION;
+ const size_t len = (numArchivers + 2) * sizeof (void *);
+ PHYSFS_Archiver *archiver = NULL;
+ PHYSFS_ArchiveInfo *info = NULL;
+ const char *ext = NULL;
+ void *ptr = NULL;
+ size_t i;
+
+ BAIL_IF(!_archiver, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(_archiver->version > maxver, PHYSFS_ERR_UNSUPPORTED, 0);
+ BAIL_IF(!_archiver->info.extension, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->info.description, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->info.author, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->info.url, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->openArchive, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->enumerate, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->openRead, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->openWrite, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->openAppend, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->remove, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->mkdir, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->closeArchive, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!_archiver->stat, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ ext = _archiver->info.extension;
+ for (i = 0; i < numArchivers; i++)
+ {
+ if (PHYSFS_utf8stricmp(archiveInfo[i]->extension, ext) == 0)
+ BAIL(PHYSFS_ERR_DUPLICATE, 0);
+ } /* for */
+
+ /* make a copy of the data. */
+ archiver = (PHYSFS_Archiver *) allocator.Malloc(sizeof (*archiver));
+ GOTO_IF(!archiver, PHYSFS_ERR_OUT_OF_MEMORY, regfailed);
+
+ /* Must copy sizeof (OLD_VERSION_OF_STRUCT) when version changes! */
+ memcpy(archiver, _archiver, sizeof (*archiver));
+
+ info = (PHYSFS_ArchiveInfo *) &archiver->info;
+ memset(info, '\0', sizeof (*info)); /* NULL in case an alloc fails. */
+ #define CPYSTR(item) \
+ info->item = __PHYSFS_strdup(_archiver->info.item); \
+ GOTO_IF(!info->item, PHYSFS_ERR_OUT_OF_MEMORY, regfailed);
+ CPYSTR(extension);
+ CPYSTR(description);
+ CPYSTR(author);
+ CPYSTR(url);
+ info->supportsSymlinks = _archiver->info.supportsSymlinks;
+ #undef CPYSTR
+
+ ptr = allocator.Realloc(archiveInfo, len);
+ GOTO_IF(!ptr, PHYSFS_ERR_OUT_OF_MEMORY, regfailed);
+ archiveInfo = (PHYSFS_ArchiveInfo **) ptr;
+
+ ptr = allocator.Realloc(archivers, len);
+ GOTO_IF(!ptr, PHYSFS_ERR_OUT_OF_MEMORY, regfailed);
+ archivers = (PHYSFS_Archiver **) ptr;
+
+ archiveInfo[numArchivers] = info;
+ archiveInfo[numArchivers + 1] = NULL;
+
+ archivers[numArchivers] = archiver;
+ archivers[numArchivers + 1] = NULL;
+
+ numArchivers++;
+
+ return 1;
+
+regfailed:
+ if (info != NULL)
+ {
+ allocator.Free((void *) info->extension);
+ allocator.Free((void *) info->description);
+ allocator.Free((void *) info->author);
+ allocator.Free((void *) info->url);
+ } /* if */
+ allocator.Free(archiver);
+
+ return 0;
+} /* doRegisterArchiver */
+
+
+int PHYSFS_registerArchiver(const PHYSFS_Archiver *archiver)
+{
+ int retval;
+ BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0);
+ __PHYSFS_platformGrabMutex(stateLock);
+ retval = doRegisterArchiver(archiver);
+ __PHYSFS_platformReleaseMutex(stateLock);
+ return retval;
+} /* PHYSFS_registerArchiver */
+
+
+int PHYSFS_deregisterArchiver(const char *ext)
+{
+ size_t i;
+
+ BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0);
+ BAIL_IF(!ext, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ __PHYSFS_platformGrabMutex(stateLock);
+ for (i = 0; i < numArchivers; i++)
+ {
+ if (PHYSFS_utf8stricmp(archiveInfo[i]->extension, ext) == 0)
+ {
+ const int retval = doDeregisterArchiver(i);
+ __PHYSFS_platformReleaseMutex(stateLock);
+ return retval;
+ } /* if */
+ } /* for */
+ __PHYSFS_platformReleaseMutex(stateLock);
+
+ BAIL(PHYSFS_ERR_NOT_FOUND, 0);
+} /* PHYSFS_deregisterArchiver */
+
+
+const PHYSFS_ArchiveInfo **PHYSFS_supportedArchiveTypes(void)
+{
+ BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, NULL);
+ return (const PHYSFS_ArchiveInfo **) archiveInfo;
+} /* PHYSFS_supportedArchiveTypes */
+
+
+void PHYSFS_freeList(void *list)
+{
+ void **i;
+ if (list != NULL)
+ {
+ for (i = (void **) list; *i != NULL; i++)
+ allocator.Free(*i);
+
+ allocator.Free(list);
+ } /* if */
+} /* PHYSFS_freeList */
+
+
+const char *PHYSFS_getDirSeparator(void)
+{
+ static char retval[2] = { __PHYSFS_platformDirSeparator, '\0' };
+ return retval;
+} /* PHYSFS_getDirSeparator */
+
+
+char **PHYSFS_getCdRomDirs(void)
+{
+ return doEnumStringList(__PHYSFS_platformDetectAvailableCDs);
+} /* PHYSFS_getCdRomDirs */
+
+
+void PHYSFS_getCdRomDirsCallback(PHYSFS_StringCallback callback, void *data)
+{
+ __PHYSFS_platformDetectAvailableCDs(callback, data);
+} /* PHYSFS_getCdRomDirsCallback */
+
+
+const char *PHYSFS_getPrefDir(const char *org, const char *app)
+{
+ const char dirsep = __PHYSFS_platformDirSeparator;
+ PHYSFS_Stat statbuf;
+ char *ptr = NULL;
+ char *endstr = NULL;
+
+ BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0);
+ BAIL_IF(!org, PHYSFS_ERR_INVALID_ARGUMENT, NULL);
+ BAIL_IF(*org == '\0', PHYSFS_ERR_INVALID_ARGUMENT, NULL);
+ BAIL_IF(!app, PHYSFS_ERR_INVALID_ARGUMENT, NULL);
+ BAIL_IF(*app == '\0', PHYSFS_ERR_INVALID_ARGUMENT, NULL);
+
+ allocator.Free(prefDir);
+ prefDir = __PHYSFS_platformCalcPrefDir(org, app);
+ BAIL_IF_ERRPASS(!prefDir, NULL);
+
+ assert(strlen(prefDir) > 0);
+ endstr = prefDir + (strlen(prefDir) - 1);
+ assert(*endstr == dirsep);
+ *endstr = '\0'; /* mask out the final dirsep for now. */
+
+ if (!__PHYSFS_platformStat(prefDir, &statbuf, 1))
+ {
+ for (ptr = strchr(prefDir, dirsep); ptr; ptr = strchr(ptr+1, dirsep))
+ {
+ *ptr = '\0';
+ __PHYSFS_platformMkDir(prefDir);
+ *ptr = dirsep;
+ } /* for */
+
+ if (!__PHYSFS_platformMkDir(prefDir))
+ {
+ allocator.Free(prefDir);
+ prefDir = NULL;
+ } /* if */
+ } /* if */
+
+ *endstr = dirsep; /* readd the final dirsep. */
+
+ return prefDir;
+} /* PHYSFS_getPrefDir */
+
+
+const char *PHYSFS_getBaseDir(void)
+{
+ return baseDir; /* this is calculated in PHYSFS_init()... */
+} /* PHYSFS_getBaseDir */
+
+
+const char *__PHYSFS_getUserDir(void) /* not deprecated internal version. */
+{
+ return userDir; /* this is calculated in PHYSFS_init()... */
+} /* __PHYSFS_getUserDir */
+
+
+const char *PHYSFS_getUserDir(void)
+{
+ return __PHYSFS_getUserDir();
+} /* PHYSFS_getUserDir */
+
+
+const char *PHYSFS_getWriteDir(void)
+{
+ const char *retval = NULL;
+
+ __PHYSFS_platformGrabMutex(stateLock);
+ if (writeDir != NULL)
+ retval = writeDir->dirName;
+ __PHYSFS_platformReleaseMutex(stateLock);
+
+ return retval;
+} /* PHYSFS_getWriteDir */
+
+
+int PHYSFS_setWriteDir(const char *newDir)
+{
+ int retval = 1;
+
+ __PHYSFS_platformGrabMutex(stateLock);
+
+ if (writeDir != NULL)
+ {
+ BAIL_IF_MUTEX_ERRPASS(!freeDirHandle(writeDir, openWriteList),
+ stateLock, 0);
+ writeDir = NULL;
+ } /* if */
+
+ if (newDir != NULL)
+ {
+ writeDir = createDirHandle(NULL, newDir, NULL, 1);
+ retval = (writeDir != NULL);
+ } /* if */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+
+ return retval;
+} /* PHYSFS_setWriteDir */
+
+
+int PHYSFS_setRoot(const char *archive, const char *subdir)
+{
+ DirHandle *i;
+
+ BAIL_IF(!archive, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ __PHYSFS_platformGrabMutex(stateLock);
+
+ for (i = searchPath; i != NULL; i = i->next)
+ {
+ if ((i->dirName != NULL) && (strcmp(archive, i->dirName) == 0))
+ {
+ if (!subdir || (strcmp(subdir, "/") == 0))
+ {
+ if (i->root)
+ allocator.Free(i->root);
+ i->root = NULL;
+ i->rootlen = 0;
+ } /* if */
+ else
+ {
+ const size_t len = strlen(subdir) + 1;
+ char *ptr = (char *) allocator.Malloc(len);
+ BAIL_IF_MUTEX(!ptr, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
+ if (!sanitizePlatformIndependentPath(subdir, ptr))
+ {
+ allocator.Free(ptr);
+ BAIL_MUTEX_ERRPASS(stateLock, 0);
+ } /* if */
+
+ if (i->root)
+ allocator.Free(i->root);
+ i->root = ptr;
+ i->rootlen = strlen(i->root); /* in case sanitizePlatformIndependentPath changed subdir */
+
+ if (longest_root < i->rootlen)
+ longest_root = i->rootlen;
+ } /* else */
+
+ break;
+ } /* if */
+ } /* for */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+ return 1;
+} /* PHYSFS_setRoot */
+
+
+static int doMount(PHYSFS_Io *io, const char *fname,
+ const char *mountPoint, int appendToPath)
+{
+ DirHandle *dh;
+ DirHandle *prev = NULL;
+ DirHandle *i;
+
+ BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ if (mountPoint == NULL)
+ mountPoint = "/";
+
+ __PHYSFS_platformGrabMutex(stateLock);
+
+ for (i = searchPath; i != NULL; i = i->next)
+ {
+ /* already in search path? */
+ if ((i->dirName != NULL) && (strcmp(fname, i->dirName) == 0))
+ BAIL_MUTEX_ERRPASS(stateLock, 1);
+ prev = i;
+ } /* for */
+
+ dh = createDirHandle(io, fname, mountPoint, 0);
+ BAIL_IF_MUTEX_ERRPASS(!dh, stateLock, 0);
+
+ if (appendToPath)
+ {
+ if (prev == NULL)
+ searchPath = dh;
+ else
+ prev->next = dh;
+ } /* if */
+ else
+ {
+ dh->next = searchPath;
+ searchPath = dh;
+ } /* else */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+ return 1;
+} /* doMount */
+
+
+int PHYSFS_mountIo(PHYSFS_Io *io, const char *fname,
+ const char *mountPoint, int appendToPath)
+{
+ BAIL_IF(!io, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(io->version != 0, PHYSFS_ERR_UNSUPPORTED, 0);
+ return doMount(io, fname, mountPoint, appendToPath);
+} /* PHYSFS_mountIo */
+
+
+int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, void (*del)(void *),
+ const char *fname, const char *mountPoint,
+ int appendToPath)
+{
+ int retval = 0;
+ PHYSFS_Io *io = NULL;
+
+ BAIL_IF(!buf, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ io = __PHYSFS_createMemoryIo(buf, len, del);
+ BAIL_IF_ERRPASS(!io, 0);
+ retval = doMount(io, fname, mountPoint, appendToPath);
+ if (!retval)
+ {
+ /* docs say not to call (del) in case of failure, so cheat. */
+ MemoryIoInfo *info = (MemoryIoInfo *) io->opaque;
+ info->destruct = NULL;
+ io->destroy(io);
+ } /* if */
+
+ return retval;
+} /* PHYSFS_mountMemory */
+
+
+int PHYSFS_mountHandle(PHYSFS_File *file, const char *fname,
+ const char *mountPoint, int appendToPath)
+{
+ int retval = 0;
+ PHYSFS_Io *io = NULL;
+
+ BAIL_IF(!file, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!fname, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ io = __PHYSFS_createHandleIo(file);
+ BAIL_IF_ERRPASS(!io, 0);
+ retval = doMount(io, fname, mountPoint, appendToPath);
+ if (!retval)
+ {
+ /* docs say not to destruct in case of failure, so cheat. */
+ io->opaque = NULL;
+ io->destroy(io);
+ } /* if */
+
+ return retval;
+} /* PHYSFS_mountHandle */
+
+
+int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath)
+{
+ BAIL_IF(!newDir, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ return doMount(NULL, newDir, mountPoint, appendToPath);
+} /* PHYSFS_mount */
+
+
+int PHYSFS_addToSearchPath(const char *newDir, int appendToPath)
+{
+ return PHYSFS_mount(newDir, NULL, appendToPath);
+} /* PHYSFS_addToSearchPath */
+
+
+int PHYSFS_removeFromSearchPath(const char *oldDir)
+{
+ return PHYSFS_unmount(oldDir);
+} /* PHYSFS_removeFromSearchPath */
+
+
+int PHYSFS_unmount(const char *oldDir)
+{
+ DirHandle *i;
+ DirHandle *prev = NULL;
+ DirHandle *next = NULL;
+
+ BAIL_IF(oldDir == NULL, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ __PHYSFS_platformGrabMutex(stateLock);
+ for (i = searchPath; i != NULL; i = i->next)
+ {
+ if (strcmp(i->dirName, oldDir) == 0)
+ {
+ next = i->next;
+ BAIL_IF_MUTEX_ERRPASS(!freeDirHandle(i, openReadList),
+ stateLock, 0);
+
+ if (prev == NULL)
+ searchPath = next;
+ else
+ prev->next = next;
+
+ BAIL_MUTEX_ERRPASS(stateLock, 1);
+ } /* if */
+ prev = i;
+ } /* for */
+
+ BAIL_MUTEX(PHYSFS_ERR_NOT_MOUNTED, stateLock, 0);
+} /* PHYSFS_unmount */
+
+
+char **PHYSFS_getSearchPath(void)
+{
+ return doEnumStringList(PHYSFS_getSearchPathCallback);
+} /* PHYSFS_getSearchPath */
+
+
+const char *PHYSFS_getMountPoint(const char *dir)
+{
+ DirHandle *i;
+ __PHYSFS_platformGrabMutex(stateLock);
+ for (i = searchPath; i != NULL; i = i->next)
+ {
+ if (strcmp(i->dirName, dir) == 0)
+ {
+ const char *retval = ((i->mountPoint) ? i->mountPoint : "/");
+ __PHYSFS_platformReleaseMutex(stateLock);
+ return retval;
+ } /* if */
+ } /* for */
+ __PHYSFS_platformReleaseMutex(stateLock);
+
+ BAIL(PHYSFS_ERR_NOT_MOUNTED, NULL);
+} /* PHYSFS_getMountPoint */
+
+
+void PHYSFS_getSearchPathCallback(PHYSFS_StringCallback callback, void *data)
+{
+ DirHandle *i;
+
+ __PHYSFS_platformGrabMutex(stateLock);
+
+ for (i = searchPath; i != NULL; i = i->next)
+ callback(data, i->dirName);
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+} /* PHYSFS_getSearchPathCallback */
+
+
+typedef struct setSaneCfgEnumData
+{
+ const char *archiveExt;
+ size_t archiveExtLen;
+ int archivesFirst;
+ PHYSFS_ErrorCode errcode;
+} setSaneCfgEnumData;
+
+static PHYSFS_EnumerateCallbackResult setSaneCfgEnumCallback(void *_data,
+ const char *dir, const char *f)
+{
+ setSaneCfgEnumData *data = (setSaneCfgEnumData *) _data;
+ const size_t extlen = data->archiveExtLen;
+ const size_t l = strlen(f);
+ const char *ext;
+
+ if ((l > extlen) && (f[l - extlen - 1] == '.'))
+ {
+ ext = f + (l - extlen);
+ if (PHYSFS_utf8stricmp(ext, data->archiveExt) == 0)
+ {
+ const char dirsep = __PHYSFS_platformDirSeparator;
+ const char *d = PHYSFS_getRealDir(f);
+ const size_t allocsize = strlen(d) + l + 2;
+ char *str = (char *) __PHYSFS_smallAlloc(allocsize);
+ if (str == NULL)
+ data->errcode = PHYSFS_ERR_OUT_OF_MEMORY;
+ else
+ {
+ snprintf(str, allocsize, "%s%c%s", d, dirsep, f);
+ if (!PHYSFS_mount(str, NULL, data->archivesFirst == 0))
+ data->errcode = currentErrorCode();
+ __PHYSFS_smallFree(str);
+ } /* else */
+ } /* if */
+ } /* if */
+
+ /* !!! FIXME: if we want to abort on errors... */
+ /*return (data->errcode != PHYSFS_ERR_OK) ? PHYSFS_ENUM_ERROR : PHYSFS_ENUM_OK;*/
+
+ return PHYSFS_ENUM_OK; /* keep going */
+} /* setSaneCfgEnumCallback */
+
+
+int PHYSFS_setSaneConfig(const char *organization, const char *appName,
+ const char *archiveExt, int includeCdRoms,
+ int archivesFirst)
+{
+ const char *basedir;
+ const char *prefdir;
+
+ BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, 0);
+
+ prefdir = PHYSFS_getPrefDir(organization, appName);
+ BAIL_IF_ERRPASS(!prefdir, 0);
+
+ basedir = PHYSFS_getBaseDir();
+ BAIL_IF_ERRPASS(!basedir, 0);
+
+ BAIL_IF(!PHYSFS_setWriteDir(prefdir), PHYSFS_ERR_NO_WRITE_DIR, 0);
+
+ /* !!! FIXME: these can fail and we should report that... */
+
+ /* Put write dir first in search path... */
+ PHYSFS_mount(prefdir, NULL, 0);
+
+ /* Put base path on search path... */
+ PHYSFS_mount(basedir, NULL, 1);
+
+ /* handle CD-ROMs... */
+ if (includeCdRoms)
+ {
+ char **cds = PHYSFS_getCdRomDirs();
+ char **i;
+ for (i = cds; *i != NULL; i++)
+ PHYSFS_mount(*i, NULL, 1);
+ PHYSFS_freeList(cds);
+ } /* if */
+
+ /* Root out archives, and add them to search path... */
+ if (archiveExt != NULL)
+ {
+ setSaneCfgEnumData data;
+ memset(&data, '\0', sizeof (data));
+ data.archiveExt = archiveExt;
+ data.archiveExtLen = strlen(archiveExt);
+ data.archivesFirst = archivesFirst;
+ data.errcode = PHYSFS_ERR_OK;
+ if (!PHYSFS_enumerate("/", setSaneCfgEnumCallback, &data))
+ {
+ /* !!! FIXME: use this if we're reporting errors.
+ PHYSFS_ErrorCode errcode = currentErrorCode();
+ if (errcode == PHYSFS_ERR_APP_CALLBACK)
+ errcode = data->errcode; */
+ } /* if */
+ } /* if */
+
+ return 1;
+} /* PHYSFS_setSaneConfig */
+
+
+void PHYSFS_permitSymbolicLinks(int allow)
+{
+ allowSymLinks = allow;
+} /* PHYSFS_permitSymbolicLinks */
+
+
+int PHYSFS_symbolicLinksPermitted(void)
+{
+ return allowSymLinks;
+} /* PHYSFS_symbolicLinksPermitted */
+
+
+/*
+ * Verify that (fname) (in platform-independent notation), in relation
+ * to (h) is secure. That means that each element of fname is checked
+ * for symlinks (if they aren't permitted). This also allows for quick
+ * rejection of files that exist outside an archive's mountpoint.
+ *
+ * With some exceptions (like PHYSFS_mkdir(), which builds multiple subdirs
+ * at a time), you should always pass zero for "allowMissing" for efficiency.
+ *
+ * (fname) must point to an output from sanitizePlatformIndependentPath(),
+ * since it will make sure that path names are in the right format for
+ * passing certain checks. It will also do checks for "insecure" pathnames
+ * like ".." which should be done once instead of once per archive. This also
+ * gives us license to treat (fname) as scratch space in this function.
+ *
+ * (fname)'s buffer must have enough space available before it for this
+ * function to prepend any root directory for this DirHandle.
+ *
+ * Returns non-zero if string is safe, zero if there's a security issue.
+ * PHYSFS_getLastError() will specify what was wrong. (*fname) will be
+ * updated to point past any mount point elements so it is prepared to
+ * be used with the archiver directly.
+ */
+static int verifyPath(DirHandle *h, char **_fname, int allowMissing)
+{
+ char *fname = *_fname;
+ int retval = 1;
+ char *start;
+ char *end;
+
+ if ((*fname == '\0') && (!h->root)) /* quick rejection. */
+ return 1;
+
+ /* !!! FIXME: This codeblock sucks. */
+ if (h->mountPoint != NULL) /* NULL mountpoint means "/". */
+ {
+ size_t mntpntlen = strlen(h->mountPoint);
+ size_t len = strlen(fname);
+ assert(mntpntlen > 1); /* root mount points should be NULL. */
+ /* not under the mountpoint, so skip this archive. */
+ BAIL_IF(len < mntpntlen-1, PHYSFS_ERR_NOT_FOUND, 0);
+ /* !!! FIXME: Case insensitive? */
+ retval = strncmp(h->mountPoint, fname, mntpntlen-1);
+ BAIL_IF(retval != 0, PHYSFS_ERR_NOT_FOUND, 0);
+ if (len > mntpntlen-1) /* corner case... */
+ BAIL_IF(fname[mntpntlen-1]!='/', PHYSFS_ERR_NOT_FOUND, 0);
+ fname += mntpntlen-1; /* move to start of actual archive path. */
+ if (*fname == '/')
+ fname++;
+ *_fname = fname; /* skip mountpoint for later use. */
+ retval = 1; /* may be reset, below. */
+ } /* if */
+
+ /* prepend the root directory, if any. */
+ if (h->root)
+ {
+ const int isempty = (*fname == '\0');
+ fname -= h->rootlen + (isempty ? 0 : 1);
+ strcpy(fname, h->root);
+ if (!isempty)
+ fname[h->rootlen] = '/';
+ *_fname = fname;
+ } /* if */
+
+ start = fname;
+ if (!allowSymLinks)
+ {
+ while (1)
+ {
+ PHYSFS_Stat statbuf;
+ int rc = 0;
+ end = strchr(start, '/');
+
+ if (end != NULL) *end = '\0';
+ rc = h->funcs->stat(h->opaque, fname, &statbuf);
+ if (rc)
+ rc = (statbuf.filetype == PHYSFS_FILETYPE_SYMLINK);
+ else if (currentErrorCode() == PHYSFS_ERR_NOT_FOUND)
+ retval = 0;
+
+ if (end != NULL) *end = '/';
+
+ /* insecure path (has a disallowed symlink in it)? */
+ BAIL_IF(rc, PHYSFS_ERR_SYMLINK_FORBIDDEN, 0);
+
+ /* break out early if path element is missing. */
+ if (!retval)
+ {
+ /*
+ * We need to clear it if it's the last element of the path,
+ * since this might be a non-existant file we're opening
+ * for writing...
+ */
+ if ((end == NULL) || (allowMissing))
+ retval = 1;
+ break;
+ } /* if */
+
+ if (end == NULL)
+ break;
+
+ start = end + 1;
+ } /* while */
+ } /* if */
+
+ return retval;
+} /* verifyPath */
+
+
+/* This must hold the stateLock before calling. */
+static int doMkdir(const char *_dname, char *dname)
+{
+ DirHandle *h = writeDir;
+ char *start;
+ char *end;
+ int retval = 0;
+ int exists = 1; /* force existance check on first path element. */
+
+ assert(h != NULL);
+
+ BAIL_IF_ERRPASS(!sanitizePlatformIndependentPathWithRoot(h, _dname, dname), 0);
+ BAIL_IF_ERRPASS(!verifyPath(h, &dname, 1), 0);
+
+ start = dname;
+ while (1)
+ {
+ end = strchr(start, '/');
+ if (end != NULL)
+ *end = '\0';
+
+ /* only check for existance if all parent dirs existed, too... */
+ if (exists)
+ {
+ PHYSFS_Stat statbuf;
+ const int rc = h->funcs->stat(h->opaque, dname, &statbuf);
+ if ((!rc) && (currentErrorCode() == PHYSFS_ERR_NOT_FOUND))
+ exists = 0;
+ /* verifyPath made sure that (dname) doesn't have symlinks if they aren't
+ allowed, but it's possible the mounted writeDir itself has symlinks in it,
+ (for example "/var" on iOS is a symlink, and the prefpath will be somewhere
+ under that)...if we mounted that writeDir, we must allow those symlinks here
+ unconditionally. */
+ retval = ( (rc) && ((statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY) || (statbuf.filetype == PHYSFS_FILETYPE_SYMLINK)) );
+ } /* if */
+
+ if (!exists)
+ retval = h->funcs->mkdir(h->opaque, dname);
+
+ if (!retval)
+ break;
+
+ if (end == NULL)
+ break;
+
+ *end = '/';
+ start = end + 1;
+ } /* while */
+
+ return retval;
+} /* doMkdir */
+
+
+int PHYSFS_mkdir(const char *_dname)
+{
+ int retval = 0;
+ char *dname;
+ size_t len;
+
+ BAIL_IF(!_dname, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ __PHYSFS_platformGrabMutex(stateLock);
+ BAIL_IF_MUTEX(!writeDir, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0);
+ len = strlen(_dname) + dirHandleRootLen(writeDir) + 1;
+ dname = (char *) __PHYSFS_smallAlloc(len);
+ BAIL_IF_MUTEX(!dname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
+ retval = doMkdir(_dname, dname);
+ __PHYSFS_platformReleaseMutex(stateLock);
+ __PHYSFS_smallFree(dname);
+ return retval;
+} /* PHYSFS_mkdir */
+
+
+/* This must hold the stateLock before calling. */
+static int doDelete(const char *_fname, char *fname)
+{
+ DirHandle *h = writeDir;
+ BAIL_IF_ERRPASS(!sanitizePlatformIndependentPathWithRoot(h, _fname, fname), 0);
+ BAIL_IF_ERRPASS(!verifyPath(h, &fname, 0), 0);
+ return h->funcs->remove(h->opaque, fname);
+} /* doDelete */
+
+
+int PHYSFS_delete(const char *_fname)
+{
+ int retval;
+ char *fname;
+ size_t len;
+
+ __PHYSFS_platformGrabMutex(stateLock);
+ BAIL_IF_MUTEX(!writeDir, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0);
+ len = strlen(_fname) + dirHandleRootLen(writeDir) + 1;
+ fname = (char *) __PHYSFS_smallAlloc(len);
+ BAIL_IF_MUTEX(!fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
+ retval = doDelete(_fname, fname);
+ __PHYSFS_platformReleaseMutex(stateLock);
+ __PHYSFS_smallFree(fname);
+ return retval;
+} /* PHYSFS_delete */
+
+
+static DirHandle *getRealDirHandle(const char *_fname)
+{
+ DirHandle *retval = NULL;
+ char *allocated_fname = NULL;
+ char *fname = NULL;
+ size_t len;
+
+ BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, NULL);
+
+ __PHYSFS_platformGrabMutex(stateLock);
+ len = strlen(_fname) + longest_root + 2;
+ allocated_fname = __PHYSFS_smallAlloc(len);
+ BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, NULL);
+ fname = allocated_fname + longest_root + 1;
+ if (sanitizePlatformIndependentPath(_fname, fname))
+ {
+ DirHandle *i;
+ for (i = searchPath; i != NULL; i = i->next)
+ {
+ char *arcfname = fname;
+ if (partOfMountPoint(i, arcfname))
+ {
+ retval = i;
+ break;
+ } /* if */
+ else if (verifyPath(i, &arcfname, 0))
+ {
+ PHYSFS_Stat statbuf;
+ if (i->funcs->stat(i->opaque, arcfname, &statbuf))
+ {
+ retval = i;
+ break;
+ } /* if */
+ } /* if */
+ } /* for */
+ } /* if */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+ __PHYSFS_smallFree(allocated_fname);
+ return retval;
+} /* getRealDirHandle */
+
+const char *PHYSFS_getRealDir(const char *fname)
+{
+ DirHandle *dh = getRealDirHandle(fname);
+ return dh ? dh->dirName : NULL;
+} /* PHYSFS_getRealDir */
+
+
+static int locateInStringList(const char *str,
+ char **list,
+ PHYSFS_uint32 *pos)
+{
+ PHYSFS_uint32 len = *pos;
+ PHYSFS_uint32 half_len;
+ PHYSFS_uint32 lo = 0;
+ PHYSFS_uint32 middle;
+ int cmp;
+
+ while (len > 0)
+ {
+ half_len = len >> 1;
+ middle = lo + half_len;
+ cmp = strcmp(list[middle], str);
+
+ if (cmp == 0) /* it's in the list already. */
+ return 1;
+ else if (cmp > 0)
+ len = half_len;
+ else
+ {
+ lo = middle + 1;
+ len -= half_len + 1;
+ } /* else */
+ } /* while */
+
+ *pos = lo;
+ return 0;
+} /* locateInStringList */
+
+
+static PHYSFS_EnumerateCallbackResult enumFilesCallback(void *data,
+ const char *origdir, const char *str)
+{
+ PHYSFS_uint32 pos;
+ void *ptr;
+ char *newstr;
+ EnumStringListCallbackData *pecd = (EnumStringListCallbackData *) data;
+
+ /*
+ * See if file is in the list already, and if not, insert it in there
+ * alphabetically...
+ */
+ pos = pecd->size;
+ if (locateInStringList(str, pecd->list, &pos))
+ return PHYSFS_ENUM_OK; /* already in the list, but keep going. */
+
+ ptr = allocator.Realloc(pecd->list, (pecd->size + 2) * sizeof (char *));
+ newstr = (char *) allocator.Malloc(strlen(str) + 1);
+ if (ptr != NULL)
+ pecd->list = (char **) ptr;
+
+ if ((ptr == NULL) || (newstr == NULL))
+ {
+ if (newstr)
+ allocator.Free(newstr);
+
+ pecd->errcode = PHYSFS_ERR_OUT_OF_MEMORY;
+ return PHYSFS_ENUM_ERROR; /* better luck next time. */
+ } /* if */
+
+ strcpy(newstr, str);
+
+ if (pos != pecd->size)
+ {
+ memmove(&pecd->list[pos+1], &pecd->list[pos],
+ sizeof (char *) * ((pecd->size) - pos));
+ } /* if */
+
+ pecd->list[pos] = newstr;
+ pecd->size++;
+
+ return PHYSFS_ENUM_OK;
+} /* enumFilesCallback */
+
+
+char **PHYSFS_enumerateFiles(const char *path)
+{
+ EnumStringListCallbackData ecd;
+ memset(&ecd, '\0', sizeof (ecd));
+ ecd.list = (char **) allocator.Malloc(sizeof (char *));
+ BAIL_IF(!ecd.list, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ if (!PHYSFS_enumerate(path, enumFilesCallback, &ecd))
+ {
+ const PHYSFS_ErrorCode errcode = currentErrorCode();
+ PHYSFS_uint32 i;
+ for (i = 0; i < ecd.size; i++)
+ allocator.Free(ecd.list[i]);
+ allocator.Free(ecd.list);
+ BAIL_IF(errcode == PHYSFS_ERR_APP_CALLBACK, ecd.errcode, NULL);
+ return NULL;
+ } /* if */
+
+ ecd.list[ecd.size] = NULL;
+ return ecd.list;
+} /* PHYSFS_enumerateFiles */
+
+
+/*
+ * Broke out to seperate function so we can use stack allocation gratuitously.
+ */
+static PHYSFS_EnumerateCallbackResult enumerateFromMountPoint(DirHandle *i,
+ const char *arcfname,
+ PHYSFS_EnumerateCallback callback,
+ const char *_fname, void *data)
+{
+ PHYSFS_EnumerateCallbackResult retval;
+ const size_t len = strlen(arcfname);
+ char *ptr = NULL;
+ char *end = NULL;
+ const size_t slen = strlen(i->mountPoint) + 1;
+ char *mountPoint = (char *) __PHYSFS_smallAlloc(slen);
+
+ BAIL_IF(!mountPoint, PHYSFS_ERR_OUT_OF_MEMORY, PHYSFS_ENUM_ERROR);
+
+ strcpy(mountPoint, i->mountPoint);
+ ptr = mountPoint + ((len) ? len + 1 : 0);
+ end = strchr(ptr, '/');
+ assert(end); /* should always find a terminating '/'. */
+ *end = '\0';
+ retval = callback(data, _fname, ptr);
+ __PHYSFS_smallFree(mountPoint);
+
+ BAIL_IF(retval == PHYSFS_ENUM_ERROR, PHYSFS_ERR_APP_CALLBACK, retval);
+ return retval;
+} /* enumerateFromMountPoint */
+
+
+typedef struct SymlinkFilterData
+{
+ PHYSFS_EnumerateCallback callback;
+ void *callbackData;
+ DirHandle *dirhandle;
+ const char *arcfname;
+ PHYSFS_ErrorCode errcode;
+} SymlinkFilterData;
+
+static PHYSFS_EnumerateCallbackResult enumCallbackFilterSymLinks(void *_data,
+ const char *origdir, const char *fname)
+{
+ SymlinkFilterData *data = (SymlinkFilterData *) _data;
+ const DirHandle *dh = data->dirhandle;
+ const char *arcfname = data->arcfname;
+ PHYSFS_Stat statbuf;
+ const char *trimmedDir = (*arcfname == '/') ? (arcfname + 1) : arcfname;
+ const size_t slen = strlen(trimmedDir) + strlen(fname) + 2;
+ char *path = (char *) __PHYSFS_smallAlloc(slen);
+ PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
+
+ if (path == NULL)
+ {
+ data->errcode = PHYSFS_ERR_OUT_OF_MEMORY;
+ return PHYSFS_ENUM_ERROR;
+ } /* if */
+
+ snprintf(path, slen, "%s%s%s", trimmedDir, *trimmedDir ? "/" : "", fname);
+
+ if (!dh->funcs->stat(dh->opaque, path, &statbuf))
+ {
+ data->errcode = currentErrorCode();
+ retval = PHYSFS_ENUM_ERROR;
+ } /* if */
+ else
+ {
+ /* Pass it on to the application if it's not a symlink. */
+ if (statbuf.filetype != PHYSFS_FILETYPE_SYMLINK)
+ {
+ retval = data->callback(data->callbackData, origdir, fname);
+ if (retval == PHYSFS_ENUM_ERROR)
+ data->errcode = PHYSFS_ERR_APP_CALLBACK;
+ } /* if */
+ } /* else */
+
+ __PHYSFS_smallFree(path);
+
+ return retval;
+} /* enumCallbackFilterSymLinks */
+
+
+int PHYSFS_enumerate(const char *_fn, PHYSFS_EnumerateCallback cb, void *data)
+{
+ PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
+ size_t len;
+ char *allocated_fname;
+ char *fname;
+
+ BAIL_IF(!_fn, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!cb, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ __PHYSFS_platformGrabMutex(stateLock);
+
+ len = strlen(_fn) + longest_root + 2;
+ allocated_fname = (char *) __PHYSFS_smallAlloc(len);
+ BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
+ fname = allocated_fname + longest_root + 1;
+ if (!sanitizePlatformIndependentPath(_fn, fname))
+ retval = PHYSFS_ENUM_STOP;
+ else
+ {
+ DirHandle *i;
+ SymlinkFilterData filterdata;
+
+ if (!allowSymLinks)
+ {
+ memset(&filterdata, '\0', sizeof (filterdata));
+ filterdata.callback = cb;
+ filterdata.callbackData = data;
+ } /* if */
+
+ for (i = searchPath; (retval == PHYSFS_ENUM_OK) && i; i = i->next)
+ {
+ char *arcfname = fname;
+
+ if (partOfMountPoint(i, arcfname))
+ retval = enumerateFromMountPoint(i, arcfname, cb, _fn, data);
+
+ else if (verifyPath(i, &arcfname, 0))
+ {
+ PHYSFS_Stat statbuf;
+ if (!i->funcs->stat(i->opaque, arcfname, &statbuf))
+ {
+ if (currentErrorCode() == PHYSFS_ERR_NOT_FOUND)
+ continue; /* no such dir in this archive, skip it. */
+ } /* if */
+
+ if (statbuf.filetype != PHYSFS_FILETYPE_DIRECTORY)
+ continue; /* not a directory in this archive, skip it. */
+
+ else if ((!allowSymLinks) && (i->funcs->info.supportsSymlinks))
+ {
+ filterdata.dirhandle = i;
+ filterdata.arcfname = arcfname;
+ filterdata.errcode = PHYSFS_ERR_OK;
+ retval = i->funcs->enumerate(i->opaque, arcfname,
+ enumCallbackFilterSymLinks,
+ _fn, &filterdata);
+ if (retval == PHYSFS_ENUM_ERROR)
+ {
+ if (currentErrorCode() == PHYSFS_ERR_APP_CALLBACK)
+ PHYSFS_setErrorCode(filterdata.errcode);
+ } /* if */
+ } /* else if */
+ else
+ {
+ retval = i->funcs->enumerate(i->opaque, arcfname,
+ cb, _fn, data);
+ } /* else */
+ } /* else if */
+ } /* for */
+
+ } /* if */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+
+ __PHYSFS_smallFree(allocated_fname);
+
+ return (retval == PHYSFS_ENUM_ERROR) ? 0 : 1;
+} /* PHYSFS_enumerate */
+
+
+typedef struct
+{
+ PHYSFS_EnumFilesCallback callback;
+ void *data;
+} LegacyEnumFilesCallbackData;
+
+static PHYSFS_EnumerateCallbackResult enumFilesCallbackAlwaysSucceed(void *d,
+ const char *origdir, const char *fname)
+{
+ LegacyEnumFilesCallbackData *cbdata = (LegacyEnumFilesCallbackData *) d;
+ cbdata->callback(cbdata->data, origdir, fname);
+ return PHYSFS_ENUM_OK;
+} /* enumFilesCallbackAlwaysSucceed */
+
+void PHYSFS_enumerateFilesCallback(const char *fname,
+ PHYSFS_EnumFilesCallback callback,
+ void *data)
+{
+ LegacyEnumFilesCallbackData cbdata;
+ cbdata.callback = callback;
+ cbdata.data = data;
+ (void) PHYSFS_enumerate(fname, enumFilesCallbackAlwaysSucceed, &cbdata);
+} /* PHYSFS_enumerateFilesCallback */
+
+
+int PHYSFS_exists(const char *fname)
+{
+ return (getRealDirHandle(fname) != NULL);
+} /* PHYSFS_exists */
+
+
+PHYSFS_sint64 PHYSFS_getLastModTime(const char *fname)
+{
+ PHYSFS_Stat statbuf;
+ BAIL_IF_ERRPASS(!PHYSFS_stat(fname, &statbuf), -1);
+ return statbuf.modtime;
+} /* PHYSFS_getLastModTime */
+
+
+int PHYSFS_isDirectory(const char *fname)
+{
+ PHYSFS_Stat statbuf;
+ BAIL_IF_ERRPASS(!PHYSFS_stat(fname, &statbuf), 0);
+ return (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY);
+} /* PHYSFS_isDirectory */
+
+
+int PHYSFS_isSymbolicLink(const char *fname)
+{
+ PHYSFS_Stat statbuf;
+ BAIL_IF_ERRPASS(!PHYSFS_stat(fname, &statbuf), 0);
+ return (statbuf.filetype == PHYSFS_FILETYPE_SYMLINK);
+} /* PHYSFS_isSymbolicLink */
+
+
+static PHYSFS_File *doOpenWrite(const char *_fname, const int appending)
+{
+ FileHandle *fh = NULL;
+ DirHandle *h;
+ size_t len;
+ char *fname;
+
+ BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ __PHYSFS_platformGrabMutex(stateLock);
+
+ h = writeDir;
+ BAIL_IF_MUTEX(!h, PHYSFS_ERR_NO_WRITE_DIR, stateLock, 0);
+
+ len = strlen(_fname) + dirHandleRootLen(h) + 1;
+ fname = (char *) __PHYSFS_smallAlloc(len);
+ BAIL_IF_MUTEX(!fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
+
+ if (sanitizePlatformIndependentPathWithRoot(h, _fname, fname))
+ {
+ PHYSFS_Io *io = NULL;
+ char *arcfname = fname;
+ if (verifyPath(h, &arcfname, 0))
+ {
+ const PHYSFS_Archiver *f = h->funcs;
+ if (appending)
+ io = f->openAppend(h->opaque, arcfname);
+ else
+ io = f->openWrite(h->opaque, arcfname);
+
+ if (io)
+ {
+ fh = (FileHandle *) allocator.Malloc(sizeof (FileHandle));
+ if (fh == NULL)
+ {
+ io->destroy(io);
+ PHYSFS_setErrorCode(PHYSFS_ERR_OUT_OF_MEMORY);
+ } /* if */
+ else
+ {
+ memset(fh, '\0', sizeof (FileHandle));
+ fh->io = io;
+ fh->dirHandle = h;
+ fh->next = openWriteList;
+ openWriteList = fh;
+ } /* else */
+ } /* if */
+ } /* if */
+ } /* if */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+
+ __PHYSFS_smallFree(fname);
+ return ((PHYSFS_File *) fh);
+} /* doOpenWrite */
+
+
+PHYSFS_File *PHYSFS_openWrite(const char *filename)
+{
+ return doOpenWrite(filename, 0);
+} /* PHYSFS_openWrite */
+
+
+PHYSFS_File *PHYSFS_openAppend(const char *filename)
+{
+ return doOpenWrite(filename, 1);
+} /* PHYSFS_openAppend */
+
+
+PHYSFS_File *PHYSFS_openRead(const char *_fname)
+{
+ FileHandle *fh = NULL;
+ char *allocated_fname;
+ char *fname;
+ size_t len;
+
+ BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ __PHYSFS_platformGrabMutex(stateLock);
+
+ BAIL_IF_MUTEX(!searchPath, PHYSFS_ERR_NOT_FOUND, stateLock, 0);
+
+ len = strlen(_fname) + longest_root + 2;
+ allocated_fname = (char *) __PHYSFS_smallAlloc(len);
+ BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
+ fname = allocated_fname + longest_root + 1;
+
+ if (sanitizePlatformIndependentPath(_fname, fname))
+ {
+ PHYSFS_Io *io = NULL;
+ DirHandle *i;
+
+ for (i = searchPath; i != NULL; i = i->next)
+ {
+ char *arcfname = fname;
+ if (verifyPath(i, &arcfname, 0))
+ {
+ io = i->funcs->openRead(i->opaque, arcfname);
+ if (io)
+ break;
+ } /* if */
+ } /* for */
+
+ if (io)
+ {
+ fh = (FileHandle *) allocator.Malloc(sizeof (FileHandle));
+ if (fh == NULL)
+ {
+ io->destroy(io);
+ PHYSFS_setErrorCode(PHYSFS_ERR_OUT_OF_MEMORY);
+ } /* if */
+ else
+ {
+ memset(fh, '\0', sizeof (FileHandle));
+ fh->io = io;
+ fh->forReading = 1;
+ fh->dirHandle = i;
+ fh->next = openReadList;
+ openReadList = fh;
+ } /* else */
+ } /* if */
+ } /* if */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+ __PHYSFS_smallFree(allocated_fname);
+ return ((PHYSFS_File *) fh);
+} /* PHYSFS_openRead */
+
+
+static int closeHandleInOpenList(FileHandle **list, FileHandle *handle)
+{
+ FileHandle *prev = NULL;
+ FileHandle *i;
+
+ for (i = *list; i != NULL; i = i->next)
+ {
+ if (i == handle) /* handle is in this list? */
+ {
+ PHYSFS_Io *io = handle->io;
+ PHYSFS_uint8 *tmp = handle->buffer;
+
+ /* send our buffer to io... */
+ if (!handle->forReading)
+ {
+ if (!PHYSFS_flush((PHYSFS_File *) handle))
+ return -1;
+
+ /* ...then have io send it to the disk... */
+ else if (io->flush && !io->flush(io))
+ return -1;
+ } /* if */
+
+ /* ...then close the underlying file. */
+ io->destroy(io);
+
+ if (tmp != NULL) /* free any associated buffer. */
+ allocator.Free(tmp);
+
+ if (prev == NULL)
+ *list = handle->next;
+ else
+ prev->next = handle->next;
+
+ allocator.Free(handle);
+ return 1;
+ } /* if */
+ prev = i;
+ } /* for */
+
+ return 0;
+} /* closeHandleInOpenList */
+
+
+int PHYSFS_close(PHYSFS_File *_handle)
+{
+ FileHandle *handle = (FileHandle *) _handle;
+ int rc;
+
+ __PHYSFS_platformGrabMutex(stateLock);
+
+ /* -1 == close failure. 0 == not found. 1 == success. */
+ rc = closeHandleInOpenList(&openReadList, handle);
+ BAIL_IF_MUTEX_ERRPASS(rc == -1, stateLock, 0);
+ if (!rc)
+ {
+ rc = closeHandleInOpenList(&openWriteList, handle);
+ BAIL_IF_MUTEX_ERRPASS(rc == -1, stateLock, 0);
+ } /* if */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+ BAIL_IF(!rc, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ return 1;
+} /* PHYSFS_close */
+
+
+static PHYSFS_sint64 doBufferedRead(FileHandle *fh, void *_buffer, size_t len)
+{
+ PHYSFS_uint8 *buffer = (PHYSFS_uint8 *) _buffer;
+ PHYSFS_sint64 retval = 0;
+
+ while (len > 0)
+ {
+ const size_t avail = fh->buffill - fh->bufpos;
+ if (avail > 0) /* data available in the buffer. */
+ {
+ const size_t cpy = (len < avail) ? len : avail;
+ memcpy(buffer, fh->buffer + fh->bufpos, cpy);
+ assert(len >= cpy);
+ buffer += cpy;
+ len -= cpy;
+ fh->bufpos += cpy;
+ retval += cpy;
+ } /* if */
+
+ else /* buffer is empty, refill it. */
+ {
+ PHYSFS_Io *io = fh->io;
+ const PHYSFS_sint64 rc = io->read(io, fh->buffer, fh->bufsize);
+ fh->bufpos = 0;
+ if (rc > 0)
+ fh->buffill = (size_t) rc;
+ else
+ {
+ fh->buffill = 0;
+ if (retval == 0) /* report already-read data, or failure. */
+ retval = rc;
+ break;
+ } /* else */
+ } /* else */
+ } /* while */
+
+ return retval;
+} /* doBufferedRead */
+
+
+PHYSFS_sint64 PHYSFS_read(PHYSFS_File *handle, void *buffer,
+ PHYSFS_uint32 size, PHYSFS_uint32 count)
+{
+ const PHYSFS_uint64 len = ((PHYSFS_uint64) size) * ((PHYSFS_uint64) count);
+ const PHYSFS_sint64 retval = PHYSFS_readBytes(handle, buffer, len);
+ return ( (retval <= 0) ? retval : (retval / ((PHYSFS_sint64) size)) );
+} /* PHYSFS_read */
+
+
+PHYSFS_sint64 PHYSFS_readBytes(PHYSFS_File *handle, void *buffer,
+ PHYSFS_uint64 _len)
+{
+ const size_t len = (size_t) _len;
+ FileHandle *fh = (FileHandle *) handle;
+
+#ifdef PHYSFS_NO_64BIT_SUPPORT
+ const PHYSFS_uint64 maxlen = __PHYSFS_UI64(0x7FFFFFFF);
+#else
+ const PHYSFS_uint64 maxlen = __PHYSFS_UI64(0x7FFFFFFFFFFFFFFF);
+#endif
+
+ if (!__PHYSFS_ui64FitsAddressSpace(_len))
+ BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
+
+ BAIL_IF(_len > maxlen, PHYSFS_ERR_INVALID_ARGUMENT, -1);
+ BAIL_IF(!fh->forReading, PHYSFS_ERR_OPEN_FOR_WRITING, -1);
+ BAIL_IF_ERRPASS(len == 0, 0);
+ if (fh->buffer)
+ return doBufferedRead(fh, buffer, len);
+
+ return fh->io->read(fh->io, buffer, len);
+} /* PHYSFS_readBytes */
+
+
+static PHYSFS_sint64 doBufferedWrite(PHYSFS_File *handle, const void *buffer,
+ const size_t len)
+{
+ FileHandle *fh = (FileHandle *) handle;
+
+ /* whole thing fits in the buffer? */
+ if ((fh->buffill + len) < fh->bufsize)
+ {
+ memcpy(fh->buffer + fh->buffill, buffer, len);
+ fh->buffill += len;
+ return (PHYSFS_sint64) len;
+ } /* if */
+
+ /* would overflow buffer. Flush and then write the new objects, too. */
+ BAIL_IF_ERRPASS(!PHYSFS_flush(handle), -1);
+ return fh->io->write(fh->io, buffer, len);
+} /* doBufferedWrite */
+
+
+PHYSFS_sint64 PHYSFS_write(PHYSFS_File *handle, const void *buffer,
+ PHYSFS_uint32 size, PHYSFS_uint32 count)
+{
+ const PHYSFS_uint64 len = ((PHYSFS_uint64) size) * ((PHYSFS_uint64) count);
+ const PHYSFS_sint64 retval = PHYSFS_writeBytes(handle, buffer, len);
+ return ( (retval <= 0) ? retval : (retval / ((PHYSFS_sint64) size)) );
+} /* PHYSFS_write */
+
+
+PHYSFS_sint64 PHYSFS_writeBytes(PHYSFS_File *handle, const void *buffer,
+ PHYSFS_uint64 _len)
+{
+ const size_t len = (size_t) _len;
+ FileHandle *fh = (FileHandle *) handle;
+
+#ifdef PHYSFS_NO_64BIT_SUPPORT
+ const PHYSFS_uint64 maxlen = __PHYSFS_UI64(0x7FFFFFFF);
+#else
+ const PHYSFS_uint64 maxlen = __PHYSFS_UI64(0x7FFFFFFFFFFFFFFF);
+#endif
+
+ if (!__PHYSFS_ui64FitsAddressSpace(_len))
+ BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
+
+ BAIL_IF(_len > maxlen, PHYSFS_ERR_INVALID_ARGUMENT, -1);
+ BAIL_IF(fh->forReading, PHYSFS_ERR_OPEN_FOR_READING, -1);
+ BAIL_IF_ERRPASS(len == 0, 0);
+ if (fh->buffer)
+ return doBufferedWrite(handle, buffer, len);
+
+ return fh->io->write(fh->io, buffer, len);
+} /* PHYSFS_write */
+
+
+int PHYSFS_eof(PHYSFS_File *handle)
+{
+ FileHandle *fh = (FileHandle *) handle;
+
+ if (!fh->forReading) /* never EOF on files opened for write/append. */
+ return 0;
+
+ /* can't be eof if buffer isn't empty */
+ if (fh->bufpos == fh->buffill)
+ {
+ /* check the Io. */
+ PHYSFS_Io *io = fh->io;
+ const PHYSFS_sint64 pos = io->tell(io);
+ const PHYSFS_sint64 len = io->length(io);
+ if ((pos < 0) || (len < 0))
+ return 0; /* beats me. */
+ return (pos >= len);
+ } /* if */
+
+ return 0;
+} /* PHYSFS_eof */
+
+
+PHYSFS_sint64 PHYSFS_tell(PHYSFS_File *handle)
+{
+ FileHandle *fh = (FileHandle *) handle;
+ const PHYSFS_sint64 pos = fh->io->tell(fh->io);
+ const PHYSFS_sint64 retval = fh->forReading ?
+ (pos - fh->buffill) + fh->bufpos :
+ (pos + fh->buffill);
+ return retval;
+} /* PHYSFS_tell */
+
+
+int PHYSFS_seek(PHYSFS_File *handle, PHYSFS_uint64 pos)
+{
+ FileHandle *fh = (FileHandle *) handle;
+ BAIL_IF_ERRPASS(!PHYSFS_flush(handle), 0);
+
+ if (fh->buffer && fh->forReading)
+ {
+ /* avoid throwing away our precious buffer if seeking within it. */
+ PHYSFS_sint64 offset = pos - PHYSFS_tell(handle);
+ if ( /* seeking within the already-buffered range? */
+ /* forward? */
+ ((offset >= 0) && (((size_t)offset) <= fh->buffill-fh->bufpos)) ||
+ /* backward? */
+ ((offset < 0) && (((size_t) -offset) <= fh->bufpos)) )
+ {
+ fh->bufpos = (size_t) (((PHYSFS_sint64) fh->bufpos) + offset);
+ return 1; /* successful seek */
+ } /* if */
+ } /* if */
+
+ /* we have to fall back to a 'raw' seek. */
+ fh->buffill = fh->bufpos = 0;
+ return fh->io->seek(fh->io, pos);
+} /* PHYSFS_seek */
+
+
+PHYSFS_sint64 PHYSFS_fileLength(PHYSFS_File *handle)
+{
+ PHYSFS_Io *io = ((FileHandle *) handle)->io;
+ return io->length(io);
+} /* PHYSFS_filelength */
+
+
+int PHYSFS_setBuffer(PHYSFS_File *handle, PHYSFS_uint64 _bufsize)
+{
+ FileHandle *fh = (FileHandle *) handle;
+ const size_t bufsize = (size_t) _bufsize;
+
+ if (!__PHYSFS_ui64FitsAddressSpace(_bufsize))
+ BAIL(PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ BAIL_IF_ERRPASS(!PHYSFS_flush(handle), 0);
+
+ /*
+ * For reads, we need to move the file pointer to where it would be
+ * if we weren't buffering, so that the next read will get the
+ * right chunk of stuff from the file. PHYSFS_flush() handles writes.
+ */
+ if ((fh->forReading) && (fh->buffill != fh->bufpos))
+ {
+ PHYSFS_uint64 pos;
+ const PHYSFS_sint64 curpos = fh->io->tell(fh->io);
+ BAIL_IF_ERRPASS(curpos == -1, 0);
+ pos = ((curpos - fh->buffill) + fh->bufpos);
+ BAIL_IF_ERRPASS(!fh->io->seek(fh->io, pos), 0);
+ } /* if */
+
+ if (bufsize == 0) /* delete existing buffer. */
+ {
+ if (fh->buffer)
+ {
+ allocator.Free(fh->buffer);
+ fh->buffer = NULL;
+ } /* if */
+ } /* if */
+
+ else
+ {
+ PHYSFS_uint8 *newbuf;
+ newbuf = (PHYSFS_uint8 *) allocator.Realloc(fh->buffer, bufsize);
+ BAIL_IF(!newbuf, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ fh->buffer = newbuf;
+ } /* else */
+
+ fh->bufsize = bufsize;
+ fh->buffill = fh->bufpos = 0;
+ return 1;
+} /* PHYSFS_setBuffer */
+
+
+int PHYSFS_flush(PHYSFS_File *handle)
+{
+ FileHandle *fh = (FileHandle *) handle;
+ PHYSFS_Io *io;
+ PHYSFS_sint64 rc;
+
+ if ((fh->forReading) || (fh->bufpos == fh->buffill))
+ return 1; /* open for read or buffer empty are successful no-ops. */
+
+ /* dump buffer to disk. */
+ io = fh->io;
+ rc = io->write(io, fh->buffer + fh->bufpos, fh->buffill - fh->bufpos);
+ BAIL_IF_ERRPASS(rc <= 0, 0);
+ fh->bufpos = fh->buffill = 0;
+ return 1;
+} /* PHYSFS_flush */
+
+
+int PHYSFS_stat(const char *_fname, PHYSFS_Stat *stat)
+{
+ int retval = 0;
+ char *allocated_fname;
+ char *fname;
+ size_t len;
+
+ BAIL_IF(!_fname, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ BAIL_IF(!stat, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+
+ /* set some sane defaults... */
+ stat->filesize = -1;
+ stat->modtime = -1;
+ stat->createtime = -1;
+ stat->accesstime = -1;
+ stat->filetype = PHYSFS_FILETYPE_OTHER;
+ stat->readonly = 1;
+
+ __PHYSFS_platformGrabMutex(stateLock);
+ len = strlen(_fname) + longest_root + 2;
+ allocated_fname = (char *) __PHYSFS_smallAlloc(len);
+ BAIL_IF_MUTEX(!allocated_fname, PHYSFS_ERR_OUT_OF_MEMORY, stateLock, 0);
+ fname = allocated_fname + longest_root + 1;
+
+ if (sanitizePlatformIndependentPath(_fname, fname))
+ {
+ if (*fname == '\0')
+ {
+ stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+ stat->readonly = !writeDir; /* Writeable if we have a writeDir */
+ retval = 1;
+ } /* if */
+ else
+ {
+ DirHandle *i;
+ int exists = 0;
+ for (i = searchPath; ((i != NULL) && (!exists)); i = i->next)
+ {
+ char *arcfname = fname;
+ exists = partOfMountPoint(i, arcfname);
+ if (exists)
+ {
+ stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+ stat->readonly = 1;
+ retval = 1;
+ } /* if */
+ else if (verifyPath(i, &arcfname, 0))
+ {
+ retval = i->funcs->stat(i->opaque, arcfname, stat);
+ if ((retval) || (currentErrorCode() != PHYSFS_ERR_NOT_FOUND))
+ exists = 1;
+ } /* else if */
+ } /* for */
+ } /* else */
+ } /* if */
+
+ __PHYSFS_platformReleaseMutex(stateLock);
+ __PHYSFS_smallFree(allocated_fname);
+ return retval;
+} /* PHYSFS_stat */
+
+
+int __PHYSFS_readAll(PHYSFS_Io *io, void *buf, const size_t _len)
+{
+ const PHYSFS_uint64 len = (PHYSFS_uint64) _len;
+ return (io->read(io, buf, len) == len);
+} /* __PHYSFS_readAll */
+
+
+void *__PHYSFS_initSmallAlloc(void *ptr, const size_t len)
+{
+ void *useHeap = ((ptr == NULL) ? ((void *) 1) : ((void *) 0));
+ if (useHeap) /* too large for stack allocation or alloca() failed. */
+ ptr = allocator.Malloc(len+sizeof (void *));
+
+ if (ptr != NULL)
+ {
+ void **retval = (void **) ptr;
+ /*printf("%s alloc'd (%lld) bytes at (%p).\n",
+ useHeap ? "heap" : "stack", (long long) len, ptr);*/
+ *retval = useHeap;
+ return retval + 1;
+ } /* if */
+
+ return NULL; /* allocation failed. */
+} /* __PHYSFS_initSmallAlloc */
+
+
+void __PHYSFS_smallFree(void *ptr)
+{
+ if (ptr != NULL)
+ {
+ void **block = ((void **) ptr) - 1;
+ const int useHeap = (*block != NULL);
+ if (useHeap)
+ allocator.Free(block);
+ /*printf("%s free'd (%p).\n", useHeap ? "heap" : "stack", block);*/
+ } /* if */
+} /* __PHYSFS_smallFree */
+
+
+int PHYSFS_setAllocator(const PHYSFS_Allocator *a)
+{
+ BAIL_IF(initialized, PHYSFS_ERR_IS_INITIALIZED, 0);
+ externalAllocator = (a != NULL);
+ if (externalAllocator)
+ memcpy(&allocator, a, sizeof (PHYSFS_Allocator));
+
+ return 1;
+} /* PHYSFS_setAllocator */
+
+
+const PHYSFS_Allocator *PHYSFS_getAllocator(void)
+{
+ BAIL_IF(!initialized, PHYSFS_ERR_NOT_INITIALIZED, NULL);
+ return &allocator;
+} /* PHYSFS_getAllocator */
+
+
+static void *mallocAllocatorMalloc(PHYSFS_uint64 s)
+{
+ if (!__PHYSFS_ui64FitsAddressSpace(s))
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ #undef malloc
+ return malloc((size_t) s);
+} /* mallocAllocatorMalloc */
+
+
+static void *mallocAllocatorRealloc(void *ptr, PHYSFS_uint64 s)
+{
+ if (!__PHYSFS_ui64FitsAddressSpace(s))
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ #undef realloc
+ return realloc(ptr, (size_t) s);
+} /* mallocAllocatorRealloc */
+
+
+static void mallocAllocatorFree(void *ptr)
+{
+ #undef free
+ free(ptr);
+} /* mallocAllocatorFree */
+
+
+static void setDefaultAllocator(void)
+{
+ assert(!externalAllocator);
+ allocator.Init = NULL;
+ allocator.Deinit = NULL;
+ allocator.Malloc = mallocAllocatorMalloc;
+ allocator.Realloc = mallocAllocatorRealloc;
+ allocator.Free = mallocAllocatorFree;
+} /* setDefaultAllocator */
+
+
+int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen, const int case_sensitive, const int only_usascii)
+{
+ static char rootpath[2] = { '/', '\0' };
+ size_t alloclen;
+
+ assert(entrylen >= sizeof (__PHYSFS_DirTreeEntry));
+
+ memset(dt, '\0', sizeof (*dt));
+ dt->case_sensitive = case_sensitive;
+ dt->only_usascii = only_usascii;
+
+ dt->root = (__PHYSFS_DirTreeEntry *) allocator.Malloc(entrylen);
+ BAIL_IF(!dt->root, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ memset(dt->root, '\0', entrylen);
+ dt->root->name = rootpath;
+ dt->root->isdir = 1;
+ dt->hashBuckets = 64;
+ if (!dt->hashBuckets)
+ dt->hashBuckets = 1;
+ dt->entrylen = entrylen;
+
+ alloclen = dt->hashBuckets * sizeof (__PHYSFS_DirTreeEntry *);
+ dt->hash = (__PHYSFS_DirTreeEntry **) allocator.Malloc(alloclen);
+ BAIL_IF(!dt->hash, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ memset(dt->hash, '\0', alloclen);
+
+ return 1;
+} /* __PHYSFS_DirTreeInit */
+
+
+static PHYSFS_uint32 hashPathName(__PHYSFS_DirTree *dt, const char *name)
+{
+ const PHYSFS_uint32 hashval = dt->case_sensitive ? __PHYSFS_hashString(name) : dt->only_usascii ? __PHYSFS_hashStringCaseFoldUSAscii(name) : __PHYSFS_hashStringCaseFold(name);
+ return hashval % dt->hashBuckets;
+} /* hashPathName */
+
+
+/* Fill in missing parent directories. */
+static __PHYSFS_DirTreeEntry *addAncestors(__PHYSFS_DirTree *dt, char *name)
+{
+ __PHYSFS_DirTreeEntry *retval = dt->root;
+ char *sep = strrchr(name, '/');
+
+ if (sep)
+ {
+ *sep = '\0'; /* chop off last piece. */
+ retval = (__PHYSFS_DirTreeEntry *) __PHYSFS_DirTreeFind(dt, name);
+
+ if (retval != NULL)
+ {
+ *sep = '/';
+ BAIL_IF(!retval->isdir, PHYSFS_ERR_CORRUPT, NULL);
+ return retval; /* already hashed. */
+ } /* if */
+
+ /* okay, this is a new dir. Build and hash us. */
+ retval = (__PHYSFS_DirTreeEntry*)__PHYSFS_DirTreeAdd(dt, name, 1);
+ *sep = '/';
+ } /* if */
+
+ return retval;
+} /* addAncestors */
+
+
+void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir)
+{
+ __PHYSFS_DirTreeEntry *retval = __PHYSFS_DirTreeFind(dt, name);
+ if (!retval)
+ {
+ const size_t alloclen = strlen(name) + 1 + dt->entrylen;
+ PHYSFS_uint32 hashval;
+ __PHYSFS_DirTreeEntry *parent = addAncestors(dt, name);
+ BAIL_IF_ERRPASS(!parent, NULL);
+ assert(dt->entrylen >= sizeof (__PHYSFS_DirTreeEntry));
+ retval = (__PHYSFS_DirTreeEntry *) allocator.Malloc(alloclen);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ memset(retval, '\0', dt->entrylen);
+ retval->name = ((char *) retval) + dt->entrylen;
+ strcpy(retval->name, name);
+ hashval = hashPathName(dt, name);
+ retval->hashnext = dt->hash[hashval];
+ dt->hash[hashval] = retval;
+ retval->sibling = parent->children;
+ retval->isdir = isdir;
+ parent->children = retval;
+ } /* if */
+
+ return retval;
+} /* __PHYSFS_DirTreeAdd */
+
+
+/* Find the __PHYSFS_DirTreeEntry for a path in platform-independent notation. */
+void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path)
+{
+ const int cs = dt->case_sensitive;
+ PHYSFS_uint32 hashval;
+ __PHYSFS_DirTreeEntry *prev = NULL;
+ __PHYSFS_DirTreeEntry *retval;
+
+ if (*path == '\0')
+ return dt->root;
+
+ hashval = hashPathName(dt, path);
+ for (retval = dt->hash[hashval]; retval; retval = retval->hashnext)
+ {
+ const int cmp = cs ? strcmp(retval->name, path) : PHYSFS_utf8stricmp(retval->name, path);
+ if (cmp == 0)
+ {
+ if (prev != NULL) /* move this to the front of the list */
+ {
+ prev->hashnext = retval->hashnext;
+ retval->hashnext = dt->hash[hashval];
+ dt->hash[hashval] = retval;
+ } /* if */
+
+ return retval;
+ } /* if */
+
+ prev = retval;
+ } /* for */
+
+ BAIL(PHYSFS_ERR_NOT_FOUND, NULL);
+} /* __PHYSFS_DirTreeFind */
+
+PHYSFS_EnumerateCallbackResult __PHYSFS_DirTreeEnumerate(void *opaque,
+ const char *dname, PHYSFS_EnumerateCallback cb,
+ const char *origdir, void *callbackdata)
+{
+ PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
+ __PHYSFS_DirTree *tree = (__PHYSFS_DirTree *) opaque;
+ const __PHYSFS_DirTreeEntry *entry = __PHYSFS_DirTreeFind(tree, dname);
+ BAIL_IF(!entry, PHYSFS_ERR_NOT_FOUND, PHYSFS_ENUM_ERROR);
+
+ entry = entry->children;
+
+ while (entry && (retval == PHYSFS_ENUM_OK))
+ {
+ const char *name = entry->name;
+ const char *ptr = strrchr(name, '/');
+ retval = cb(callbackdata, origdir, ptr ? ptr + 1 : name);
+ BAIL_IF(retval == PHYSFS_ENUM_ERROR, PHYSFS_ERR_APP_CALLBACK, retval);
+ entry = entry->sibling;
+ } /* while */
+
+ return retval;
+} /* __PHYSFS_DirTreeEnumerate */
+
+
+void __PHYSFS_DirTreeDeinit(__PHYSFS_DirTree *dt)
+{
+ if (!dt)
+ return;
+
+ if (dt->root)
+ {
+ assert(dt->root->sibling == NULL);
+ assert(dt->hash || (dt->root->children == NULL));
+ allocator.Free(dt->root);
+ } /* if */
+
+ if (dt->hash)
+ {
+ size_t i;
+ for (i = 0; i < dt->hashBuckets; i++)
+ {
+ __PHYSFS_DirTreeEntry *entry;
+ __PHYSFS_DirTreeEntry *next;
+ for (entry = dt->hash[i]; entry; entry = next)
+ {
+ next = entry->hashnext;
+ allocator.Free(entry);
+ } /* for */
+ } /* for */
+ allocator.Free(dt->hash);
+ } /* if */
+} /* __PHYSFS_DirTreeDeinit */
+
+/* end of physfs.c ... */
+
diff --git a/third-party/physfs/src/physfs.h b/third-party/physfs/src/physfs.h
new file mode 100644
index 0000000..6e55ce4
--- /dev/null
+++ b/third-party/physfs/src/physfs.h
@@ -0,0 +1,3924 @@
+/**
+ * \file physfs.h
+ *
+ * Main header file for PhysicsFS.
+ */
+
+/**
+ * \mainpage PhysicsFS
+ *
+ * The latest version of PhysicsFS can be found at:
+ * https://icculus.org/physfs/
+ *
+ * PhysicsFS; a portable, flexible file i/o abstraction.
+ *
+ * This API gives you access to a system file system in ways superior to the
+ * stdio or system i/o calls. The brief benefits:
+ *
+ * - It's portable.
+ * - It's safe. No file access is permitted outside the specified dirs.
+ * - It's flexible. Archives (.ZIP files) can be used transparently as
+ * directory structures.
+ *
+ * With PhysicsFS, you have a single writing directory and multiple
+ * directories (the "search path") for reading. You can think of this as a
+ * filesystem within a filesystem. If (on Windows) you were to set the
+ * writing directory to "C:\MyGame\MyWritingDirectory", then no PHYSFS calls
+ * could touch anything above this directory, including the "C:\MyGame" and
+ * "C:\" directories. This prevents an application's internal scripting
+ * language from piddling over c:\\config.sys, for example. If you'd rather
+ * give PHYSFS full access to the system's REAL file system, set the writing
+ * dir to "C:\", but that's generally A Bad Thing for several reasons.
+ *
+ * Drive letters are hidden in PhysicsFS once you set up your initial paths.
+ * The search path creates a single, hierarchical directory structure.
+ * Not only does this lend itself well to general abstraction with archives,
+ * it also gives better support to operating systems like MacOS and Unix.
+ * Generally speaking, you shouldn't ever hardcode a drive letter; not only
+ * does this hurt portability to non-Microsoft OSes, but it limits your win32
+ * users to a single drive, too. Use the PhysicsFS abstraction functions and
+ * allow user-defined configuration options, too. When opening a file, you
+ * specify it like it was on a Unix filesystem: if you want to write to
+ * "C:\MyGame\MyConfigFiles\game.cfg", then you might set the write dir to
+ * "C:\MyGame" and then open "MyConfigFiles/game.cfg". This gives an
+ * abstraction across all platforms. Specifying a file in this way is termed
+ * "platform-independent notation" in this documentation. Specifying a
+ * a filename in a form such as "C:\mydir\myfile" or
+ * "MacOS hard drive:My Directory:My File" is termed "platform-dependent
+ * notation". The only time you use platform-dependent notation is when
+ * setting up your write directory and search path; after that, all file
+ * access into those directories are done with platform-independent notation.
+ *
+ * All files opened for writing are opened in relation to the write directory,
+ * which is the root of the writable filesystem. When opening a file for
+ * reading, PhysicsFS goes through the search path. This is NOT the
+ * same thing as the PATH environment variable. An application using
+ * PhysicsFS specifies directories to be searched which may be actual
+ * directories, or archive files that contain files and subdirectories of
+ * their own. See the end of these docs for currently supported archive
+ * formats.
+ *
+ * Once the search path is defined, you may open files for reading. If you've
+ * got the following search path defined (to use a win32 example again):
+ *
+ * - C:\\mygame
+ * - C:\\mygame\\myuserfiles
+ * - D:\\mygamescdromdatafiles
+ * - C:\\mygame\\installeddatafiles.zip
+ *
+ * Then a call to PHYSFS_openRead("textfiles/myfile.txt") (note the directory
+ * separator, lack of drive letter, and lack of dir separator at the start of
+ * the string; this is platform-independent notation) will check for
+ * C:\\mygame\\textfiles\\myfile.txt, then
+ * C:\\mygame\\myuserfiles\\textfiles\\myfile.txt, then
+ * D:\\mygamescdromdatafiles\\textfiles\\myfile.txt, then, finally, for
+ * textfiles\\myfile.txt inside of C:\\mygame\\installeddatafiles.zip.
+ * Remember that most archive types and platform filesystems store their
+ * filenames in a case-sensitive manner, so you should be careful to specify
+ * it correctly.
+ *
+ * Files opened through PhysicsFS may NOT contain "." or ".." or ":" as dir
+ * elements. Not only are these meaningless on MacOS Classic and/or Unix,
+ * they are a security hole. Also, symbolic links (which can be found in
+ * some archive types and directly in the filesystem on Unix platforms) are
+ * NOT followed until you call PHYSFS_permitSymbolicLinks(). That's left to
+ * your own discretion, as following a symlink can allow for access outside
+ * the write dir and search paths. For portability, there is no mechanism for
+ * creating new symlinks in PhysicsFS.
+ *
+ * The write dir is not included in the search path unless you specifically
+ * add it. While you CAN change the write dir as many times as you like,
+ * you should probably set it once and stick to it. Remember that your
+ * program will not have permission to write in every directory on Unix and
+ * NT systems.
+ *
+ * All files are opened in binary mode; there is no endline conversion for
+ * textfiles. Other than that, PhysicsFS has some convenience functions for
+ * platform-independence. There is a function to tell you the current
+ * platform's dir separator ("\\" on windows, "/" on Unix, ":" on MacOS),
+ * which is needed only to set up your search/write paths. There is a
+ * function to tell you what CD-ROM drives contain accessible discs, and a
+ * function to recommend a good search path, etc.
+ *
+ * A recommended order for the search path is the write dir, then the base dir,
+ * then the cdrom dir, then any archives discovered. Quake 3 does something
+ * like this, but moves the archives to the start of the search path. Build
+ * Engine games, like Duke Nukem 3D and Blood, place the archives last, and
+ * use the base dir for both searching and writing. There is a helper
+ * function (PHYSFS_setSaneConfig()) that puts together a basic configuration
+ * for you, based on a few parameters. Also see the comments on
+ * PHYSFS_getBaseDir(), and PHYSFS_getPrefDir() for info on what those
+ * are and how they can help you determine an optimal search path.
+ *
+ * PhysicsFS 2.0 adds the concept of "mounting" archives to arbitrary points
+ * in the search path. If a zipfile contains "maps/level.map" and you mount
+ * that archive at "mods/mymod", then you would have to open
+ * "mods/mymod/maps/level.map" to access the file, even though "mods/mymod"
+ * isn't actually specified in the .zip file. Unlike the Unix mentality of
+ * mounting a filesystem, "mods/mymod" doesn't actually have to exist when
+ * mounting the zipfile. It's a "virtual" directory. The mounting mechanism
+ * allows the developer to seperate archives in the tree and avoid trampling
+ * over files when added new archives, such as including mod support in a
+ * game...keeping external content on a tight leash in this manner can be of
+ * utmost importance to some applications.
+ *
+ * PhysicsFS is mostly thread safe. The errors returned by
+ * PHYSFS_getLastErrorCode() are unique by thread, and library-state-setting
+ * functions are mutex'd. For efficiency, individual file accesses are
+ * not locked, so you can not safely read/write/seek/close/etc the same
+ * file from two threads at the same time. Other race conditions are bugs
+ * that should be reported/patched.
+ *
+ * While you CAN use stdio/syscall file access in a program that has PHYSFS_*
+ * calls, doing so is not recommended, and you can not directly use system
+ * filehandles with PhysicsFS and vice versa (but as of PhysicsFS 2.1, you
+ * can wrap them in a PHYSFS_Io interface yourself if you wanted to).
+ *
+ * Note that archives need not be named as such: if you have a ZIP file and
+ * rename it with a .PKG extension, the file will still be recognized as a
+ * ZIP archive by PhysicsFS; the file's contents are used to determine its
+ * type where possible.
+ *
+ * Currently supported archive types:
+ * - .ZIP (pkZip/WinZip/Info-ZIP compatible)
+ * - .7Z (7zip archives)
+ * - .ISO (ISO9660 files, CD-ROM images)
+ * - .GRP (Build Engine groupfile archives)
+ * - .PAK (Quake I/II archive format)
+ * - .HOG (Descent I/II/III HOG file archives)
+ * - .MVL (Descent II movielib archives)
+ * - .WAD (DOOM engine archives)
+ * - .VDF (Gothic I/II engine archives)
+ * - .SLB (Independence War archives)
+ *
+ * String policy for PhysicsFS 2.0 and later:
+ *
+ * PhysicsFS 1.0 could only deal with null-terminated ASCII strings. All high
+ * ASCII chars resulted in undefined behaviour, and there was no Unicode
+ * support at all. PhysicsFS 2.0 supports Unicode without breaking binary
+ * compatibility with the 1.0 API by using UTF-8 encoding of all strings
+ * passed in and out of the library.
+ *
+ * All strings passed through PhysicsFS are in null-terminated UTF-8 format.
+ * This means that if all you care about is English (ASCII characters <= 127)
+ * then you just use regular C strings. If you care about Unicode (and you
+ * should!) then you need to figure out what your platform wants, needs, and
+ * offers. If you are on Windows before Win2000 and build with Unicode
+ * support, your TCHAR strings are two bytes per character (this is called
+ * "UCS-2 encoding"). Any modern Windows uses UTF-16, which is two bytes
+ * per character for most characters, but some characters are four. You
+ * should convert them to UTF-8 before handing them to PhysicsFS with
+ * PHYSFS_utf8FromUtf16(), which handles both UTF-16 and UCS-2. If you're
+ * using Unix or Mac OS X, your wchar_t strings are four bytes per character
+ * ("UCS-4 encoding", sometimes called "UTF-32"). Use PHYSFS_utf8FromUcs4().
+ * Mac OS X can give you UTF-8 directly from a CFString or NSString, and many
+ * Unixes generally give you C strings in UTF-8 format everywhere. If you
+ * have a single-byte high ASCII charset, like so-many European "codepages"
+ * you may be out of luck. We'll convert from "Latin1" to UTF-8 only, and
+ * never back to Latin1. If you're above ASCII 127, all bets are off: move
+ * to Unicode or use your platform's facilities. Passing a C string with
+ * high-ASCII data that isn't UTF-8 encoded will NOT do what you expect!
+ *
+ * Naturally, there's also PHYSFS_utf8ToUcs2(), PHYSFS_utf8ToUtf16(), and
+ * PHYSFS_utf8ToUcs4() to get data back into a format you like. Behind the
+ * scenes, PhysicsFS will use Unicode where possible: the UTF-8 strings on
+ * Windows will be converted and used with the multibyte Windows APIs, for
+ * example.
+ *
+ * PhysicsFS offers basic encoding conversion support, but not a whole string
+ * library. Get your stuff into whatever format you can work with.
+ *
+ * Most platforms supported by PhysicsFS 2.1 and later fully support Unicode.
+ * Some older platforms have been dropped (Windows 95, Mac OS 9). Some, like
+ * OS/2, might be able to convert to a local codepage or will just fail to
+ * open/create the file. Modern OSes (macOS, Linux, Windows, etc) should all
+ * be fine.
+ *
+ * Many game-specific archivers are seriously unprepared for Unicode (the
+ * Descent HOG/MVL and Build Engine GRP archivers, for example, only offer a
+ * DOS 8.3 filename, for example). Nothing can be done for these, but they
+ * tend to be legacy formats for existing content that was all ASCII (and
+ * thus, valid UTF-8) anyhow. Other formats, like .ZIP, don't explicitly
+ * offer Unicode support, but unofficially expect filenames to be UTF-8
+ * encoded, and thus Just Work. Most everything does the right thing without
+ * bothering you, but it's good to be aware of these nuances in case they
+ * don't.
+ *
+ *
+ * Other stuff:
+ *
+ * Please see the file LICENSE.txt in the source's root directory for
+ * licensing and redistribution rights.
+ *
+ * Please see the file CREDITS.txt in the source's "docs" directory for
+ * a more or less complete list of who's responsible for this.
+ *
+ * \author Ryan C. Gordon.
+ */
+
+#ifndef _INCLUDE_PHYSFS_H_
+#define _INCLUDE_PHYSFS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(PHYSFS_DECL)
+/* do nothing. */
+#elif defined(PHYSFS_STATIC)
+#define PHYSFS_DECL /**/
+#elif defined(_WIN32) || defined(__OS2__)
+#define PHYSFS_DECL __declspec(dllexport)
+#elif defined(__SUNPRO_C)
+#define PHYSFS_DECL __global
+#elif ((__GNUC__ >= 3) && (!defined(__EMX__)) && (!defined(sun)))
+#define PHYSFS_DECL __attribute__((visibility("default")))
+#else
+#define PHYSFS_DECL
+#endif
+
+#if defined(PHYSFS_DEPRECATED)
+/* do nothing. */
+#elif (__GNUC__ >= 4) /* technically, this arrived in gcc 3.1, but oh well. */
+#define PHYSFS_DEPRECATED __attribute__((deprecated))
+#else
+#define PHYSFS_DEPRECATED
+#endif
+
+#if 0 /* !!! FIXME: look into this later. */
+#if defined(PHYSFS_CALL)
+/* do nothing. */
+#elif defined(__WIN32__) && !defined(__GNUC__)
+#define PHYSFS_CALL __cdecl
+#elif defined(__OS2__) || defined(OS2) /* should work across all compilers. */
+#define PHYSFS_CALL _System
+#else
+#define PHYSFS_CALL
+#endif
+#endif
+
+/**
+ * \typedef PHYSFS_uint8
+ * \brief An unsigned, 8-bit integer type.
+ */
+typedef unsigned char PHYSFS_uint8;
+
+/**
+ * \typedef PHYSFS_sint8
+ * \brief A signed, 8-bit integer type.
+ */
+typedef signed char PHYSFS_sint8;
+
+/**
+ * \typedef PHYSFS_uint16
+ * \brief An unsigned, 16-bit integer type.
+ */
+typedef unsigned short PHYSFS_uint16;
+
+/**
+ * \typedef PHYSFS_sint16
+ * \brief A signed, 16-bit integer type.
+ */
+typedef signed short PHYSFS_sint16;
+
+/**
+ * \typedef PHYSFS_uint32
+ * \brief An unsigned, 32-bit integer type.
+ */
+typedef unsigned int PHYSFS_uint32;
+
+/**
+ * \typedef PHYSFS_sint32
+ * \brief A signed, 32-bit integer type.
+ */
+typedef signed int PHYSFS_sint32;
+
+/**
+ * \typedef PHYSFS_uint64
+ * \brief An unsigned, 64-bit integer type.
+ * \warning on platforms without any sort of 64-bit datatype, this is
+ * equivalent to PHYSFS_uint32!
+ */
+
+/**
+ * \typedef PHYSFS_sint64
+ * \brief A signed, 64-bit integer type.
+ * \warning on platforms without any sort of 64-bit datatype, this is
+ * equivalent to PHYSFS_sint32!
+ */
+
+
+#if (defined PHYSFS_NO_64BIT_SUPPORT) /* oh well. */
+typedef PHYSFS_uint32 PHYSFS_uint64;
+typedef PHYSFS_sint32 PHYSFS_sint64;
+#elif (defined _MSC_VER)
+typedef signed __int64 PHYSFS_sint64;
+typedef unsigned __int64 PHYSFS_uint64;
+#else
+typedef unsigned long long PHYSFS_uint64;
+typedef signed long long PHYSFS_sint64;
+#endif
+
+
+#ifndef DOXYGEN_SHOULD_IGNORE_THIS
+/* Make sure the types really have the right sizes */
+#define PHYSFS_COMPILE_TIME_ASSERT(name, x) \
+ typedef int PHYSFS_compile_time_assert_##name[(x) * 2 - 1]
+
+PHYSFS_COMPILE_TIME_ASSERT(uint8IsOneByte, sizeof(PHYSFS_uint8) == 1);
+PHYSFS_COMPILE_TIME_ASSERT(sint8IsOneByte, sizeof(PHYSFS_sint8) == 1);
+PHYSFS_COMPILE_TIME_ASSERT(uint16IsTwoBytes, sizeof(PHYSFS_uint16) == 2);
+PHYSFS_COMPILE_TIME_ASSERT(sint16IsTwoBytes, sizeof(PHYSFS_sint16) == 2);
+PHYSFS_COMPILE_TIME_ASSERT(uint32IsFourBytes, sizeof(PHYSFS_uint32) == 4);
+PHYSFS_COMPILE_TIME_ASSERT(sint32IsFourBytes, sizeof(PHYSFS_sint32) == 4);
+
+#ifndef PHYSFS_NO_64BIT_SUPPORT
+PHYSFS_COMPILE_TIME_ASSERT(uint64IsEightBytes, sizeof(PHYSFS_uint64) == 8);
+PHYSFS_COMPILE_TIME_ASSERT(sint64IsEightBytes, sizeof(PHYSFS_sint64) == 8);
+#endif
+
+#undef PHYSFS_COMPILE_TIME_ASSERT
+
+#endif /* DOXYGEN_SHOULD_IGNORE_THIS */
+
+
+/**
+ * \struct PHYSFS_File
+ * \brief A PhysicsFS file handle.
+ *
+ * You get a pointer to one of these when you open a file for reading,
+ * writing, or appending via PhysicsFS.
+ *
+ * As you can see from the lack of meaningful fields, you should treat this
+ * as opaque data. Don't try to manipulate the file handle, just pass the
+ * pointer you got, unmolested, to various PhysicsFS APIs.
+ *
+ * \sa PHYSFS_openRead
+ * \sa PHYSFS_openWrite
+ * \sa PHYSFS_openAppend
+ * \sa PHYSFS_close
+ * \sa PHYSFS_read
+ * \sa PHYSFS_write
+ * \sa PHYSFS_seek
+ * \sa PHYSFS_tell
+ * \sa PHYSFS_eof
+ * \sa PHYSFS_setBuffer
+ * \sa PHYSFS_flush
+ */
+typedef struct PHYSFS_File
+{
+ void *opaque; /**< That's all you get. Don't touch. */
+} PHYSFS_File;
+
+
+/**
+ * \def PHYSFS_file
+ * \brief 1.0 API compatibility define.
+ *
+ * PHYSFS_file is identical to PHYSFS_File. This #define is here for backwards
+ * compatibility with the 1.0 API, which had an inconsistent capitalization
+ * convention in this case. New code should use PHYSFS_File, as this #define
+ * may go away someday.
+ *
+ * \sa PHYSFS_File
+ */
+#define PHYSFS_file PHYSFS_File
+
+
+/**
+ * \struct PHYSFS_ArchiveInfo
+ * \brief Information on various PhysicsFS-supported archives.
+ *
+ * This structure gives you details on what sort of archives are supported
+ * by this implementation of PhysicsFS. Archives tend to be things like
+ * ZIP files and such.
+ *
+ * \warning Not all binaries are created equal! PhysicsFS can be built with
+ * or without support for various archives. You can check with
+ * PHYSFS_supportedArchiveTypes() to see if your archive type is
+ * supported.
+ *
+ * \sa PHYSFS_supportedArchiveTypes
+ * \sa PHYSFS_registerArchiver
+ * \sa PHYSFS_deregisterArchiver
+ */
+typedef struct PHYSFS_ArchiveInfo
+{
+ const char *extension; /**< Archive file extension: "ZIP", for example. */
+ const char *description; /**< Human-readable archive description. */
+ const char *author; /**< Person who did support for this archive. */
+ const char *url; /**< URL related to this archive */
+ int supportsSymlinks; /**< non-zero if archive offers symbolic links. */
+} PHYSFS_ArchiveInfo;
+
+
+/**
+ * \struct PHYSFS_Version
+ * \brief Information the version of PhysicsFS in use.
+ *
+ * Represents the library's version as three levels: major revision
+ * (increments with massive changes, additions, and enhancements),
+ * minor revision (increments with backwards-compatible changes to the
+ * major revision), and patchlevel (increments with fixes to the minor
+ * revision).
+ *
+ * \sa PHYSFS_VERSION
+ * \sa PHYSFS_getLinkedVersion
+ */
+typedef struct PHYSFS_Version
+{
+ PHYSFS_uint8 major; /**< major revision */
+ PHYSFS_uint8 minor; /**< minor revision */
+ PHYSFS_uint8 patch; /**< patchlevel */
+} PHYSFS_Version;
+
+
+#ifndef DOXYGEN_SHOULD_IGNORE_THIS
+#define PHYSFS_VER_MAJOR 3
+#define PHYSFS_VER_MINOR 2
+#define PHYSFS_VER_PATCH 0
+#endif /* DOXYGEN_SHOULD_IGNORE_THIS */
+
+
+/* PhysicsFS state stuff ... */
+
+/**
+ * \def PHYSFS_VERSION(x)
+ * \brief Macro to determine PhysicsFS version program was compiled against.
+ *
+ * This macro fills in a PHYSFS_Version structure with the version of the
+ * library you compiled against. This is determined by what header the
+ * compiler uses. Note that if you dynamically linked the library, you might
+ * have a slightly newer or older version at runtime. That version can be
+ * determined with PHYSFS_getLinkedVersion(), which, unlike PHYSFS_VERSION,
+ * is not a macro.
+ *
+ * \param x A pointer to a PHYSFS_Version struct to initialize.
+ *
+ * \sa PHYSFS_Version
+ * \sa PHYSFS_getLinkedVersion
+ */
+#define PHYSFS_VERSION(x) \
+{ \
+ (x)->major = PHYSFS_VER_MAJOR; \
+ (x)->minor = PHYSFS_VER_MINOR; \
+ (x)->patch = PHYSFS_VER_PATCH; \
+}
+
+
+/**
+ * \fn void PHYSFS_getLinkedVersion(PHYSFS_Version *ver)
+ * \brief Get the version of PhysicsFS that is linked against your program.
+ *
+ * If you are using a shared library (DLL) version of PhysFS, then it is
+ * possible that it will be different than the version you compiled against.
+ *
+ * This is a real function; the macro PHYSFS_VERSION tells you what version
+ * of PhysFS you compiled against:
+ *
+ * \code
+ * PHYSFS_Version compiled;
+ * PHYSFS_Version linked;
+ *
+ * PHYSFS_VERSION(&compiled);
+ * PHYSFS_getLinkedVersion(&linked);
+ * printf("We compiled against PhysFS version %d.%d.%d ...\n",
+ * compiled.major, compiled.minor, compiled.patch);
+ * printf("But we linked against PhysFS version %d.%d.%d.\n",
+ * linked.major, linked.minor, linked.patch);
+ * \endcode
+ *
+ * This function may be called safely at any time, even before PHYSFS_init().
+ *
+ * \sa PHYSFS_VERSION
+ */
+PHYSFS_DECL void PHYSFS_getLinkedVersion(PHYSFS_Version *ver);
+
+
+#ifdef __ANDROID__
+typedef struct PHYSFS_AndroidInit
+{
+ void *jnienv;
+ void *context;
+} PHYSFS_AndroidInit;
+#endif
+
+/**
+ * \fn int PHYSFS_init(const char *argv0)
+ * \brief Initialize the PhysicsFS library.
+ *
+ * This must be called before any other PhysicsFS function.
+ *
+ * This should be called prior to any attempts to change your process's
+ * current working directory.
+ *
+ * \warning On Android, argv0 should be a non-NULL pointer to a
+ * PHYSFS_AndroidInit struct. This struct must hold a valid JNIEnv *
+ * and a JNI jobject of a Context (either the application context or
+ * the current Activity is fine). Both are cast to a void * so we
+ * don't need jni.h included wherever physfs.h is. PhysicsFS
+ * uses these objects to query some system details. PhysicsFS does
+ * not hold a reference to the JNIEnv or Context past the call to
+ * PHYSFS_init(). If you pass a NULL here, PHYSFS_init can still
+ * succeed, but PHYSFS_getBaseDir() and PHYSFS_getPrefDir() will be
+ * incorrect.
+ *
+ * \param argv0 the argv[0] string passed to your program's mainline.
+ * This may be NULL on most platforms (such as ones without a
+ * standard main() function), but you should always try to pass
+ * something in here. Many Unix-like systems _need_ to pass argv[0]
+ * from main() in here. See warning about Android, too!
+ * \return nonzero on success, zero on error. Specifics of the error can be
+ * gleaned from PHYSFS_getLastError().
+ *
+ * \sa PHYSFS_deinit
+ * \sa PHYSFS_isInit
+ */
+PHYSFS_DECL int PHYSFS_init(const char *argv0);
+
+
+/**
+ * \fn int PHYSFS_deinit(void)
+ * \brief Deinitialize the PhysicsFS library.
+ *
+ * This closes any files opened via PhysicsFS, blanks the search/write paths,
+ * frees memory, and invalidates all of your file handles.
+ *
+ * Note that this call can FAIL if there's a file open for writing that
+ * refuses to close (for example, the underlying operating system was
+ * buffering writes to network filesystem, and the fileserver has crashed,
+ * or a hard drive has failed, etc). It is usually best to close all write
+ * handles yourself before calling this function, so that you can gracefully
+ * handle a specific failure.
+ *
+ * Once successfully deinitialized, PHYSFS_init() can be called again to
+ * restart the subsystem. All default API states are restored at this
+ * point, with the exception of any custom allocator you might have
+ * specified, which survives between initializations.
+ *
+ * \return nonzero on success, zero on error. Specifics of the error can be
+ * gleaned from PHYSFS_getLastError(). If failure, state of PhysFS is
+ * undefined, and probably badly screwed up.
+ *
+ * \sa PHYSFS_init
+ * \sa PHYSFS_isInit
+ */
+PHYSFS_DECL int PHYSFS_deinit(void);
+
+
+/**
+ * \fn const PHYSFS_ArchiveInfo **PHYSFS_supportedArchiveTypes(void)
+ * \brief Get a list of supported archive types.
+ *
+ * Get a list of archive types supported by this implementation of PhysicFS.
+ * These are the file formats usable for search path entries. This is for
+ * informational purposes only. Note that the extension listed is merely
+ * convention: if we list "ZIP", you can open a PkZip-compatible archive
+ * with an extension of "XYZ", if you like.
+ *
+ * The returned value is an array of pointers to PHYSFS_ArchiveInfo structures,
+ * with a NULL entry to signify the end of the list:
+ *
+ * \code
+ * PHYSFS_ArchiveInfo **i;
+ *
+ * for (i = PHYSFS_supportedArchiveTypes(); *i != NULL; i++)
+ * {
+ * printf("Supported archive: [%s], which is [%s].\n",
+ * (*i)->extension, (*i)->description);
+ * }
+ * \endcode
+ *
+ * The return values are pointers to internal memory, and should
+ * be considered READ ONLY, and never freed. The returned values are
+ * valid until the next call to PHYSFS_deinit(), PHYSFS_registerArchiver(),
+ * or PHYSFS_deregisterArchiver().
+ *
+ * \return READ ONLY Null-terminated array of READ ONLY structures.
+ *
+ * \sa PHYSFS_registerArchiver
+ * \sa PHYSFS_deregisterArchiver
+ */
+PHYSFS_DECL const PHYSFS_ArchiveInfo **PHYSFS_supportedArchiveTypes(void);
+
+
+/**
+ * \fn void PHYSFS_freeList(void *listVar)
+ * \brief Deallocate resources of lists returned by PhysicsFS.
+ *
+ * Certain PhysicsFS functions return lists of information that are
+ * dynamically allocated. Use this function to free those resources.
+ *
+ * It is safe to pass a NULL here, but doing so will cause a crash in versions
+ * before PhysicsFS 2.1.0.
+ *
+ * \param listVar List of information specified as freeable by this function.
+ * Passing NULL is safe; it is a valid no-op.
+ *
+ * \sa PHYSFS_getCdRomDirs
+ * \sa PHYSFS_enumerateFiles
+ * \sa PHYSFS_getSearchPath
+ */
+PHYSFS_DECL void PHYSFS_freeList(void *listVar);
+
+
+/**
+ * \fn const char *PHYSFS_getLastError(void)
+ * \brief Get human-readable error information.
+ *
+ * \deprecated Use PHYSFS_getLastErrorCode() and PHYSFS_getErrorByCode() instead.
+ *
+ * \warning As of PhysicsFS 2.1, this function has been nerfed.
+ * Before PhysicsFS 2.1, this function was the only way to get
+ * error details beyond a given function's basic return value.
+ * This was meant to be a human-readable string in one of several
+ * languages, and was not useful for application parsing. This was
+ * a problem, because the developer and not the user chose the
+ * language at compile time, and the PhysicsFS maintainers had
+ * to (poorly) maintain a significant amount of localization work.
+ * The app couldn't parse the strings, even if they counted on a
+ * specific language, since some were dynamically generated.
+ * In 2.1 and later, this always returns a static string in
+ * English; you may use it as a key string for your own
+ * localizations if you like, as we'll promise not to change
+ * existing error strings. Also, if your application wants to
+ * look at specific errors, we now offer a better option:
+ * use PHYSFS_getLastErrorCode() instead.
+ *
+ * Get the last PhysicsFS error message as a human-readable, null-terminated
+ * string. This will return NULL if there's been no error since the last call
+ * to this function. The pointer returned by this call points to an internal
+ * buffer. Each thread has a unique error state associated with it, but each
+ * time a new error message is set, it will overwrite the previous one
+ * associated with that thread. It is safe to call this function at anytime,
+ * even before PHYSFS_init().
+ *
+ * PHYSFS_getLastError() and PHYSFS_getLastErrorCode() both reset the same
+ * thread-specific error state. Calling one will wipe out the other's
+ * data. If you need both, call PHYSFS_getLastErrorCode(), then pass that
+ * value to PHYSFS_getErrorByCode().
+ *
+ * As of PhysicsFS 2.1, this function only presents text in the English
+ * language, but the strings are static, so you can use them as keys into
+ * your own localization dictionary. These strings are meant to be passed on
+ * directly to the user.
+ *
+ * Generally, applications should only concern themselves with whether a
+ * given function failed; however, if your code require more specifics, you
+ * should use PHYSFS_getLastErrorCode() instead of this function.
+ *
+ * \return READ ONLY string of last error message.
+ *
+ * \sa PHYSFS_getLastErrorCode
+ * \sa PHYSFS_getErrorByCode
+ */
+PHYSFS_DECL const char *PHYSFS_getLastError(void) PHYSFS_DEPRECATED;
+
+
+/**
+ * \fn const char *PHYSFS_getDirSeparator(void)
+ * \brief Get platform-dependent dir separator string.
+ *
+ * This returns "\\" on win32, "/" on Unix, and ":" on MacOS. It may be more
+ * than one character, depending on the platform, and your code should take
+ * that into account. Note that this is only useful for setting up the
+ * search/write paths, since access into those dirs always use '/'
+ * (platform-independent notation) to separate directories. This is also
+ * handy for getting platform-independent access when using stdio calls.
+ *
+ * \return READ ONLY null-terminated string of platform's dir separator.
+ */
+PHYSFS_DECL const char *PHYSFS_getDirSeparator(void);
+
+
+/**
+ * \fn void PHYSFS_permitSymbolicLinks(int allow)
+ * \brief Enable or disable following of symbolic links.
+ *
+ * Some physical filesystems and archives contain files that are just pointers
+ * to other files. On the physical filesystem, opening such a link will
+ * (transparently) open the file that is pointed to.
+ *
+ * By default, PhysicsFS will check if a file is really a symlink during open
+ * calls and fail if it is. Otherwise, the link could take you outside the
+ * write and search paths, and compromise security.
+ *
+ * If you want to take that risk, call this function with a non-zero parameter.
+ * Note that this is more for sandboxing a program's scripting language, in
+ * case untrusted scripts try to compromise the system. Generally speaking,
+ * a user could very well have a legitimate reason to set up a symlink, so
+ * unless you feel there's a specific danger in allowing them, you should
+ * permit them.
+ *
+ * Symlinks are only explicitly checked when dealing with filenames
+ * in platform-independent notation. That is, when setting up your
+ * search and write paths, etc, symlinks are never checked for.
+ *
+ * Please note that PHYSFS_stat() will always check the path specified; if
+ * that path is a symlink, it will not be followed in any case. If symlinks
+ * aren't permitted through this function, PHYSFS_stat() ignores them, and
+ * would treat the query as if the path didn't exist at all.
+ *
+ * Symbolic link permission can be enabled or disabled at any time after
+ * you've called PHYSFS_init(), and is disabled by default.
+ *
+ * \param allow nonzero to permit symlinks, zero to deny linking.
+ *
+ * \sa PHYSFS_symbolicLinksPermitted
+ */
+PHYSFS_DECL void PHYSFS_permitSymbolicLinks(int allow);
+
+
+/**
+ * \fn char **PHYSFS_getCdRomDirs(void)
+ * \brief Get an array of paths to available CD-ROM drives.
+ *
+ * The dirs returned are platform-dependent ("D:\" on Win32, "/cdrom" or
+ * whatnot on Unix). Dirs are only returned if there is a disc ready and
+ * accessible in the drive. So if you've got two drives (D: and E:), and only
+ * E: has a disc in it, then that's all you get. If the user inserts a disc
+ * in D: and you call this function again, you get both drives. If, on a
+ * Unix box, the user unmounts a disc and remounts it elsewhere, the next
+ * call to this function will reflect that change.
+ *
+ * This function refers to "CD-ROM" media, but it really means "inserted disc
+ * media," such as DVD-ROM, HD-DVD, CDRW, and Blu-Ray discs. It looks for
+ * filesystems, and as such won't report an audio CD, unless there's a
+ * mounted filesystem track on it.
+ *
+ * The returned value is an array of strings, with a NULL entry to signify the
+ * end of the list:
+ *
+ * \code
+ * char **cds = PHYSFS_getCdRomDirs();
+ * char **i;
+ *
+ * for (i = cds; *i != NULL; i++)
+ * printf("cdrom dir [%s] is available.\n", *i);
+ *
+ * PHYSFS_freeList(cds);
+ * \endcode
+ *
+ * This call may block while drives spin up. Be forewarned.
+ *
+ * When you are done with the returned information, you may dispose of the
+ * resources by calling PHYSFS_freeList() with the returned pointer.
+ *
+ * \return Null-terminated array of null-terminated strings.
+ *
+ * \sa PHYSFS_getCdRomDirsCallback
+ */
+PHYSFS_DECL char **PHYSFS_getCdRomDirs(void);
+
+
+/**
+ * \fn const char *PHYSFS_getBaseDir(void)
+ * \brief Get the path where the application resides.
+ *
+ * Helper function.
+ *
+ * Get the "base dir". This is the directory where the application was run
+ * from, which is probably the installation directory, and may or may not
+ * be the process's current working directory.
+ *
+ * You should probably use the base dir in your search path.
+ *
+ * \warning On most platforms, this is a directory; on Android, this gives
+ * you the path to the app's package (APK) file. As APK files are
+ * just .zip files, you can mount them in PhysicsFS like regular
+ * directories. You'll probably want to call
+ * PHYSFS_setRoot(basedir, "/assets") after mounting to make your
+ * app's actual data available directly without all the Android
+ * metadata and directory offset. Note that if you passed a NULL to
+ * PHYSFS_init(), you will not get the APK file here.
+ *
+ * \return READ ONLY string of base dir in platform-dependent notation.
+ *
+ * \sa PHYSFS_getPrefDir
+ */
+PHYSFS_DECL const char *PHYSFS_getBaseDir(void);
+
+
+/**
+ * \fn const char *PHYSFS_getUserDir(void)
+ * \brief Get the path where user's home directory resides.
+ *
+ * \deprecated As of PhysicsFS 2.1, you probably want PHYSFS_getPrefDir().
+ *
+ * Helper function.
+ *
+ * Get the "user dir". This is meant to be a suggestion of where a specific
+ * user of the system can store files. On Unix, this is her home directory.
+ * On systems with no concept of multiple home directories (MacOS, win95),
+ * this will default to something like "C:\mybasedir\users\username"
+ * where "username" will either be the login name, or "default" if the
+ * platform doesn't support multiple users, either.
+ *
+ * \return READ ONLY string of user dir in platform-dependent notation.
+ *
+ * \sa PHYSFS_getBaseDir
+ * \sa PHYSFS_getPrefDir
+ */
+PHYSFS_DECL const char *PHYSFS_getUserDir(void) PHYSFS_DEPRECATED;
+
+
+/**
+ * \fn const char *PHYSFS_getWriteDir(void)
+ * \brief Get path where PhysicsFS will allow file writing.
+ *
+ * Get the current write dir. The default write dir is NULL.
+ *
+ * \return READ ONLY string of write dir in platform-dependent notation,
+ * OR NULL IF NO WRITE PATH IS CURRENTLY SET.
+ *
+ * \sa PHYSFS_setWriteDir
+ */
+PHYSFS_DECL const char *PHYSFS_getWriteDir(void);
+
+
+/**
+ * \fn int PHYSFS_setWriteDir(const char *newDir)
+ * \brief Tell PhysicsFS where it may write files.
+ *
+ * Set a new write dir. This will override the previous setting.
+ *
+ * This call will fail (and fail to change the write dir) if the current
+ * write dir still has files open in it.
+ *
+ * \param newDir The new directory to be the root of the write dir,
+ * specified in platform-dependent notation. Setting to NULL
+ * disables the write dir, so no files can be opened for
+ * writing via PhysicsFS.
+ * \return non-zero on success, zero on failure. All attempts to open a file
+ * for writing via PhysicsFS will fail until this call succeeds.
+ * Use PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_getWriteDir
+ */
+PHYSFS_DECL int PHYSFS_setWriteDir(const char *newDir);
+
+
+/**
+ * \fn int PHYSFS_addToSearchPath(const char *newDir, int appendToPath)
+ * \brief Add an archive or directory to the search path.
+ *
+ * \deprecated As of PhysicsFS 2.0, use PHYSFS_mount() instead. This
+ * function just wraps it anyhow.
+ *
+ * This function is equivalent to:
+ *
+ * \code
+ * PHYSFS_mount(newDir, NULL, appendToPath);
+ * \endcode
+ *
+ * You must use this and not PHYSFS_mount if binary compatibility with
+ * PhysicsFS 1.0 is important (which it may not be for many people).
+ *
+ * \sa PHYSFS_mount
+ * \sa PHYSFS_removeFromSearchPath
+ * \sa PHYSFS_getSearchPath
+ */
+PHYSFS_DECL int PHYSFS_addToSearchPath(const char *newDir, int appendToPath)
+ PHYSFS_DEPRECATED;
+
+/**
+ * \fn int PHYSFS_removeFromSearchPath(const char *oldDir)
+ * \brief Remove a directory or archive from the search path.
+ *
+ * \deprecated As of PhysicsFS 2.1, use PHYSFS_unmount() instead. This
+ * function just wraps it anyhow. There's no functional difference
+ * except the vocabulary changed from "adding to the search path"
+ * to "mounting" when that functionality was extended, and thus
+ * the preferred way to accomplish this function's work is now
+ * called "unmounting."
+ *
+ * This function is equivalent to:
+ *
+ * \code
+ * PHYSFS_unmount(oldDir);
+ * \endcode
+ *
+ * You must use this and not PHYSFS_unmount if binary compatibility with
+ * PhysicsFS 1.0 is important (which it may not be for many people).
+ *
+ * \sa PHYSFS_addToSearchPath
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_unmount
+ */
+PHYSFS_DECL int PHYSFS_removeFromSearchPath(const char *oldDir)
+ PHYSFS_DEPRECATED;
+
+
+/**
+ * \fn char **PHYSFS_getSearchPath(void)
+ * \brief Get the current search path.
+ *
+ * The default search path is an empty list.
+ *
+ * The returned value is an array of strings, with a NULL entry to signify the
+ * end of the list:
+ *
+ * \code
+ * char **i;
+ *
+ * for (i = PHYSFS_getSearchPath(); *i != NULL; i++)
+ * printf("[%s] is in the search path.\n", *i);
+ * \endcode
+ *
+ * When you are done with the returned information, you may dispose of the
+ * resources by calling PHYSFS_freeList() with the returned pointer.
+ *
+ * \return Null-terminated array of null-terminated strings. NULL if there
+ * was a problem (read: OUT OF MEMORY).
+ *
+ * \sa PHYSFS_getSearchPathCallback
+ * \sa PHYSFS_addToSearchPath
+ * \sa PHYSFS_removeFromSearchPath
+ */
+PHYSFS_DECL char **PHYSFS_getSearchPath(void);
+
+
+/**
+ * \fn int PHYSFS_setSaneConfig(const char *organization, const char *appName, const char *archiveExt, int includeCdRoms, int archivesFirst)
+ * \brief Set up sane, default paths.
+ *
+ * Helper function.
+ *
+ * The write dir will be set to the pref dir returned by
+ * \code PHYSFS_getPrefDir(organization, appName) \endcode, which is
+ * created if it doesn't exist.
+ *
+ * The above is sufficient to make sure your program's configuration directory
+ * is separated from other clutter, and platform-independent.
+ *
+ * The search path will be:
+ *
+ * - The Write Dir (created if it doesn't exist)
+ * - The Base Dir (PHYSFS_getBaseDir())
+ * - All found CD-ROM dirs (optionally)
+ *
+ * These directories are then searched for files ending with the extension
+ * (archiveExt), which, if they are valid and supported archives, will also
+ * be added to the search path. If you specified "PKG" for (archiveExt), and
+ * there's a file named data.PKG in the base dir, it'll be checked. Archives
+ * can either be appended or prepended to the search path in alphabetical
+ * order, regardless of which directories they were found in. All archives
+ * are mounted in the root of the virtual file system ("/").
+ *
+ * All of this can be accomplished from the application, but this just does it
+ * all for you. Feel free to add more to the search path manually, too.
+ *
+ * \param organization Name of your company/group/etc to be used as a
+ * dirname, so keep it small, and no-frills.
+ *
+ * \param appName Program-specific name of your program, to separate it
+ * from other programs using PhysicsFS.
+ *
+ * \param archiveExt File extension used by your program to specify an
+ * archive. For example, Quake 3 uses "pk3", even though
+ * they are just zipfiles. Specify NULL to not dig out
+ * archives automatically. Do not specify the '.' char;
+ * If you want to look for ZIP files, specify "ZIP" and
+ * not ".ZIP" ... the archive search is case-insensitive.
+ *
+ * \param includeCdRoms Non-zero to include CD-ROMs in the search path, and
+ * (if (archiveExt) != NULL) search them for archives.
+ * This may cause a significant amount of blocking
+ * while discs are accessed, and if there are no discs
+ * in the drive (or even not mounted on Unix systems),
+ * then they may not be made available anyhow. You may
+ * want to specify zero and handle the disc setup
+ * yourself.
+ *
+ * \param archivesFirst Non-zero to prepend the archives to the search path.
+ * Zero to append them. Ignored if !(archiveExt).
+ *
+ * \return nonzero on success, zero on error. Use PHYSFS_getLastErrorCode()
+ * to obtain the specific error.
+ */
+PHYSFS_DECL int PHYSFS_setSaneConfig(const char *organization,
+ const char *appName,
+ const char *archiveExt,
+ int includeCdRoms,
+ int archivesFirst);
+
+
+/* Directory management stuff ... */
+
+/**
+ * \fn int PHYSFS_mkdir(const char *dirName)
+ * \brief Create a directory.
+ *
+ * This is specified in platform-independent notation in relation to the
+ * write dir. All missing parent directories are also created if they
+ * don't exist.
+ *
+ * So if you've got the write dir set to "C:\mygame\writedir" and call
+ * PHYSFS_mkdir("downloads/maps") then the directories
+ * "C:\mygame\writedir\downloads" and "C:\mygame\writedir\downloads\maps"
+ * will be created if possible. If the creation of "maps" fails after we
+ * have successfully created "downloads", then the function leaves the
+ * created directory behind and reports failure.
+ *
+ * \param dirName New dir to create.
+ * \return nonzero on success, zero on error. Use
+ * PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_delete
+ */
+PHYSFS_DECL int PHYSFS_mkdir(const char *dirName);
+
+
+/**
+ * \fn int PHYSFS_delete(const char *filename)
+ * \brief Delete a file or directory.
+ *
+ * (filename) is specified in platform-independent notation in relation to the
+ * write dir.
+ *
+ * A directory must be empty before this call can delete it.
+ *
+ * Deleting a symlink will remove the link, not what it points to, regardless
+ * of whether you "permitSymLinks" or not.
+ *
+ * So if you've got the write dir set to "C:\mygame\writedir" and call
+ * PHYSFS_delete("downloads/maps/level1.map") then the file
+ * "C:\mygame\writedir\downloads\maps\level1.map" is removed from the
+ * physical filesystem, if it exists and the operating system permits the
+ * deletion.
+ *
+ * Note that on Unix systems, deleting a file may be successful, but the
+ * actual file won't be removed until all processes that have an open
+ * filehandle to it (including your program) close their handles.
+ *
+ * Chances are, the bits that make up the file still exist, they are just
+ * made available to be written over at a later point. Don't consider this
+ * a security method or anything. :)
+ *
+ * \param filename Filename to delete.
+ * \return nonzero on success, zero on error. Use PHYSFS_getLastErrorCode()
+ * to obtain the specific error.
+ */
+PHYSFS_DECL int PHYSFS_delete(const char *filename);
+
+
+/**
+ * \fn const char *PHYSFS_getRealDir(const char *filename)
+ * \brief Figure out where in the search path a file resides.
+ *
+ * The file is specified in platform-independent notation. The returned
+ * filename will be the element of the search path where the file was found,
+ * which may be a directory, or an archive. Even if there are multiple
+ * matches in different parts of the search path, only the first one found
+ * is used, just like when opening a file.
+ *
+ * So, if you look for "maps/level1.map", and C:\\mygame is in your search
+ * path and C:\\mygame\\maps\\level1.map exists, then "C:\mygame" is returned.
+ *
+ * If a any part of a match is a symbolic link, and you've not explicitly
+ * permitted symlinks, then it will be ignored, and the search for a match
+ * will continue.
+ *
+ * If you specify a fake directory that only exists as a mount point, it'll
+ * be associated with the first archive mounted there, even though that
+ * directory isn't necessarily contained in a real archive.
+ *
+ * \warning This will return NULL if there is no real directory associated
+ * with (filename). Specifically, PHYSFS_mountIo(),
+ * PHYSFS_mountMemory(), and PHYSFS_mountHandle() will return NULL
+ * even if the filename is found in the search path. Plan accordingly.
+ *
+ * \param filename file to look for.
+ * \return READ ONLY string of element of search path containing the
+ * the file in question. NULL if not found.
+ */
+PHYSFS_DECL const char *PHYSFS_getRealDir(const char *filename);
+
+
+/**
+ * \fn char **PHYSFS_enumerateFiles(const char *dir)
+ * \brief Get a file listing of a search path's directory.
+ *
+ * \warning In PhysicsFS versions prior to 2.1, this function would return
+ * as many items as it could in the face of a failure condition
+ * (out of memory, disk i/o error, etc). Since this meant apps
+ * couldn't distinguish between complete success and partial failure,
+ * and since the function could always return NULL to report
+ * catastrophic failures anyway, in PhysicsFS 2.1 this function's
+ * policy changed: it will either return a list of complete results
+ * or it will return NULL for any failure of any kind, so we can
+ * guarantee that the enumeration ran to completion and has no gaps
+ * in its results.
+ *
+ * Matching directories are interpolated. That is, if "C:\mydir" is in the
+ * search path and contains a directory "savegames" that contains "x.sav",
+ * "y.sav", and "z.sav", and there is also a "C:\userdir" in the search path
+ * that has a "savegames" subdirectory with "w.sav", then the following code:
+ *
+ * \code
+ * char **rc = PHYSFS_enumerateFiles("savegames");
+ * char **i;
+ *
+ * for (i = rc; *i != NULL; i++)
+ * printf(" * We've got [%s].\n", *i);
+ *
+ * PHYSFS_freeList(rc);
+ * \endcode
+ *
+ * \...will print:
+ *
+ * \verbatim
+ * We've got [x.sav].
+ * We've got [y.sav].
+ * We've got [z.sav].
+ * We've got [w.sav].\endverbatim
+ *
+ * Feel free to sort the list however you like. However, the returned data
+ * will always contain no duplicates, and will be always sorted in alphabetic
+ * (rather: case-sensitive Unicode) order for you.
+ *
+ * Don't forget to call PHYSFS_freeList() with the return value from this
+ * function when you are done with it.
+ *
+ * \param dir directory in platform-independent notation to enumerate.
+ * \return Null-terminated array of null-terminated strings, or NULL for
+ * failure cases.
+ *
+ * \sa PHYSFS_enumerate
+ */
+PHYSFS_DECL char **PHYSFS_enumerateFiles(const char *dir);
+
+
+/**
+ * \fn int PHYSFS_exists(const char *fname)
+ * \brief Determine if a file exists in the search path.
+ *
+ * Reports true if there is an entry anywhere in the search path by the
+ * name of (fname).
+ *
+ * Note that entries that are symlinks are ignored if
+ * PHYSFS_permitSymbolicLinks(1) hasn't been called, so you
+ * might end up further down in the search path than expected.
+ *
+ * \param fname filename in platform-independent notation.
+ * \return non-zero if filename exists. zero otherwise.
+ */
+PHYSFS_DECL int PHYSFS_exists(const char *fname);
+
+
+/**
+ * \fn int PHYSFS_isDirectory(const char *fname)
+ * \brief Determine if a file in the search path is really a directory.
+ *
+ * \deprecated As of PhysicsFS 2.1, use PHYSFS_stat() instead. This
+ * function just wraps it anyhow.
+ *
+ * Determine if the first occurence of (fname) in the search path is
+ * really a directory entry.
+ *
+ * Note that entries that are symlinks are ignored if
+ * PHYSFS_permitSymbolicLinks(1) hasn't been called, so you
+ * might end up further down in the search path than expected.
+ *
+ * \param fname filename in platform-independent notation.
+ * \return non-zero if filename exists and is a directory. zero otherwise.
+ *
+ * \sa PHYSFS_stat
+ * \sa PHYSFS_exists
+ */
+PHYSFS_DECL int PHYSFS_isDirectory(const char *fname) PHYSFS_DEPRECATED;
+
+
+/**
+ * \fn int PHYSFS_isSymbolicLink(const char *fname)
+ * \brief Determine if a file in the search path is really a symbolic link.
+ *
+ * \deprecated As of PhysicsFS 2.1, use PHYSFS_stat() instead. This
+ * function just wraps it anyhow.
+ *
+ * Determine if the first occurence of (fname) in the search path is
+ * really a symbolic link.
+ *
+ * Note that entries that are symlinks are ignored if
+ * PHYSFS_permitSymbolicLinks(1) hasn't been called, and as such,
+ * this function will always return 0 in that case.
+ *
+ * \param fname filename in platform-independent notation.
+ * \return non-zero if filename exists and is a symlink. zero otherwise.
+ *
+ * \sa PHYSFS_stat
+ * \sa PHYSFS_exists
+ */
+PHYSFS_DECL int PHYSFS_isSymbolicLink(const char *fname) PHYSFS_DEPRECATED;
+
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_getLastModTime(const char *filename)
+ * \brief Get the last modification time of a file.
+ *
+ * \deprecated As of PhysicsFS 2.1, use PHYSFS_stat() instead. This
+ * function just wraps it anyhow.
+ *
+ * The modtime is returned as a number of seconds since the Unix epoch
+ * (midnight, Jan 1, 1970). The exact derivation and accuracy of this time
+ * depends on the particular archiver. If there is no reasonable way to
+ * obtain this information for a particular archiver, or there was some sort
+ * of error, this function returns (-1).
+ *
+ * You must use this and not PHYSFS_stat() if binary compatibility with
+ * PhysicsFS 2.0 is important (which it may not be for many people).
+ *
+ * \param filename filename to check, in platform-independent notation.
+ * \return last modified time of the file. -1 if it can't be determined.
+ *
+ * \sa PHYSFS_stat
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_getLastModTime(const char *filename)
+ PHYSFS_DEPRECATED;
+
+
+/* i/o stuff... */
+
+/**
+ * \fn PHYSFS_File *PHYSFS_openWrite(const char *filename)
+ * \brief Open a file for writing.
+ *
+ * Open a file for writing, in platform-independent notation and in relation
+ * to the write dir as the root of the writable filesystem. The specified
+ * file is created if it doesn't exist. If it does exist, it is truncated to
+ * zero bytes, and the writing offset is set to the start.
+ *
+ * Note that entries that are symlinks are ignored if
+ * PHYSFS_permitSymbolicLinks(1) hasn't been called, and opening a
+ * symlink with this function will fail in such a case.
+ *
+ * \param filename File to open.
+ * \return A valid PhysicsFS filehandle on success, NULL on error. Use
+ * PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_openRead
+ * \sa PHYSFS_openAppend
+ * \sa PHYSFS_write
+ * \sa PHYSFS_close
+ */
+PHYSFS_DECL PHYSFS_File *PHYSFS_openWrite(const char *filename);
+
+
+/**
+ * \fn PHYSFS_File *PHYSFS_openAppend(const char *filename)
+ * \brief Open a file for appending.
+ *
+ * Open a file for writing, in platform-independent notation and in relation
+ * to the write dir as the root of the writable filesystem. The specified
+ * file is created if it doesn't exist. If it does exist, the writing offset
+ * is set to the end of the file, so the first write will be the byte after
+ * the end.
+ *
+ * Note that entries that are symlinks are ignored if
+ * PHYSFS_permitSymbolicLinks(1) hasn't been called, and opening a
+ * symlink with this function will fail in such a case.
+ *
+ * \param filename File to open.
+ * \return A valid PhysicsFS filehandle on success, NULL on error. Use
+ * PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_openRead
+ * \sa PHYSFS_openWrite
+ * \sa PHYSFS_write
+ * \sa PHYSFS_close
+ */
+PHYSFS_DECL PHYSFS_File *PHYSFS_openAppend(const char *filename);
+
+
+/**
+ * \fn PHYSFS_File *PHYSFS_openRead(const char *filename)
+ * \brief Open a file for reading.
+ *
+ * Open a file for reading, in platform-independent notation. The search path
+ * is checked one at a time until a matching file is found, in which case an
+ * abstract filehandle is associated with it, and reading may be done.
+ * The reading offset is set to the first byte of the file.
+ *
+ * Note that entries that are symlinks are ignored if
+ * PHYSFS_permitSymbolicLinks(1) hasn't been called, and opening a
+ * symlink with this function will fail in such a case.
+ *
+ * \param filename File to open.
+ * \return A valid PhysicsFS filehandle on success, NULL on error.
+ * Use PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_openWrite
+ * \sa PHYSFS_openAppend
+ * \sa PHYSFS_read
+ * \sa PHYSFS_close
+ */
+PHYSFS_DECL PHYSFS_File *PHYSFS_openRead(const char *filename);
+
+
+/**
+ * \fn int PHYSFS_close(PHYSFS_File *handle)
+ * \brief Close a PhysicsFS filehandle.
+ *
+ * This call is capable of failing if the operating system was buffering
+ * writes to the physical media, and, now forced to write those changes to
+ * physical media, can not store the data for some reason. In such a case,
+ * the filehandle stays open. A well-written program should ALWAYS check the
+ * return value from the close call in addition to every writing call!
+ *
+ * \param handle handle returned from PHYSFS_open*().
+ * \return nonzero on success, zero on error. Use PHYSFS_getLastErrorCode()
+ * to obtain the specific error.
+ *
+ * \sa PHYSFS_openRead
+ * \sa PHYSFS_openWrite
+ * \sa PHYSFS_openAppend
+ */
+PHYSFS_DECL int PHYSFS_close(PHYSFS_File *handle);
+
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_read(PHYSFS_File *handle, void *buffer, PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
+ * \brief Read data from a PhysicsFS filehandle
+ *
+ * The file must be opened for reading.
+ *
+ * \deprecated As of PhysicsFS 2.1, use PHYSFS_readBytes() instead. This
+ * function just wraps it anyhow. This function never clarified
+ * what would happen if you managed to read a partial object, so
+ * working at the byte level makes this cleaner for everyone,
+ * especially now that PHYSFS_Io interfaces can be supplied by the
+ * application.
+ *
+ * \param handle handle returned from PHYSFS_openRead().
+ * \param buffer buffer to store read data into.
+ * \param objSize size in bytes of objects being read from (handle).
+ * \param objCount number of (objSize) objects to read from (handle).
+ * \return number of objects read. PHYSFS_getLastErrorCode() can shed light
+ * on the reason this might be < (objCount), as can PHYSFS_eof().
+ * -1 if complete failure.
+ *
+ * \sa PHYSFS_readBytes
+ * \sa PHYSFS_eof
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_read(PHYSFS_File *handle,
+ void *buffer,
+ PHYSFS_uint32 objSize,
+ PHYSFS_uint32 objCount)
+ PHYSFS_DEPRECATED;
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_write(PHYSFS_File *handle, const void *buffer, PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
+ * \brief Write data to a PhysicsFS filehandle
+ *
+ * The file must be opened for writing.
+ *
+ * \deprecated As of PhysicsFS 2.1, use PHYSFS_writeBytes() instead. This
+ * function just wraps it anyhow. This function never clarified
+ * what would happen if you managed to write a partial object, so
+ * working at the byte level makes this cleaner for everyone,
+ * especially now that PHYSFS_Io interfaces can be supplied by the
+ * application.
+ *
+ * \param handle retval from PHYSFS_openWrite() or PHYSFS_openAppend().
+ * \param buffer buffer of bytes to write to (handle).
+ * \param objSize size in bytes of objects being written to (handle).
+ * \param objCount number of (objSize) objects to write to (handle).
+ * \return number of objects written. PHYSFS_getLastErrorCode() can shed
+ * light on the reason this might be < (objCount). -1 if complete
+ * failure.
+ *
+ * \sa PHYSFS_writeBytes
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_write(PHYSFS_File *handle,
+ const void *buffer,
+ PHYSFS_uint32 objSize,
+ PHYSFS_uint32 objCount)
+ PHYSFS_DEPRECATED;
+
+
+/* File position stuff... */
+
+/**
+ * \fn int PHYSFS_eof(PHYSFS_File *handle)
+ * \brief Check for end-of-file state on a PhysicsFS filehandle.
+ *
+ * Determine if the end of file has been reached in a PhysicsFS filehandle.
+ *
+ * \param handle handle returned from PHYSFS_openRead().
+ * \return nonzero if EOF, zero if not.
+ *
+ * \sa PHYSFS_read
+ * \sa PHYSFS_tell
+ */
+PHYSFS_DECL int PHYSFS_eof(PHYSFS_File *handle);
+
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_tell(PHYSFS_File *handle)
+ * \brief Determine current position within a PhysicsFS filehandle.
+ *
+ * \param handle handle returned from PHYSFS_open*().
+ * \return offset in bytes from start of file. -1 if error occurred.
+ * Use PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_seek
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_tell(PHYSFS_File *handle);
+
+
+/**
+ * \fn int PHYSFS_seek(PHYSFS_File *handle, PHYSFS_uint64 pos)
+ * \brief Seek to a new position within a PhysicsFS filehandle.
+ *
+ * The next read or write will occur at that place. Seeking past the
+ * beginning or end of the file is not allowed, and causes an error.
+ *
+ * \param handle handle returned from PHYSFS_open*().
+ * \param pos number of bytes from start of file to seek to.
+ * \return nonzero on success, zero on error. Use PHYSFS_getLastErrorCode()
+ * to obtain the specific error.
+ *
+ * \sa PHYSFS_tell
+ */
+PHYSFS_DECL int PHYSFS_seek(PHYSFS_File *handle, PHYSFS_uint64 pos);
+
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_fileLength(PHYSFS_File *handle)
+ * \brief Get total length of a file in bytes.
+ *
+ * Note that if another process/thread is writing to this file at the same
+ * time, then the information this function supplies could be incorrect
+ * before you get it. Use with caution, or better yet, don't use at all.
+ *
+ * \param handle handle returned from PHYSFS_open*().
+ * \return size in bytes of the file. -1 if can't be determined.
+ *
+ * \sa PHYSFS_tell
+ * \sa PHYSFS_seek
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_fileLength(PHYSFS_File *handle);
+
+
+/* Buffering stuff... */
+
+/**
+ * \fn int PHYSFS_setBuffer(PHYSFS_File *handle, PHYSFS_uint64 bufsize)
+ * \brief Set up buffering for a PhysicsFS file handle.
+ *
+ * Define an i/o buffer for a file handle. A memory block of (bufsize) bytes
+ * will be allocated and associated with (handle).
+ *
+ * For files opened for reading, up to (bufsize) bytes are read from (handle)
+ * and stored in the internal buffer. Calls to PHYSFS_read() will pull
+ * from this buffer until it is empty, and then refill it for more reading.
+ * Note that compressed files, like ZIP archives, will decompress while
+ * buffering, so this can be handy for offsetting CPU-intensive operations.
+ * The buffer isn't filled until you do your next read.
+ *
+ * For files opened for writing, data will be buffered to memory until the
+ * buffer is full or the buffer is flushed. Closing a handle implicitly
+ * causes a flush...check your return values!
+ *
+ * Seeking, etc transparently accounts for buffering.
+ *
+ * You can resize an existing buffer by calling this function more than once
+ * on the same file. Setting the buffer size to zero will free an existing
+ * buffer.
+ *
+ * PhysicsFS file handles are unbuffered by default.
+ *
+ * Please check the return value of this function! Failures can include
+ * not being able to seek backwards in a read-only file when removing the
+ * buffer, not being able to allocate the buffer, and not being able to
+ * flush the buffer to disk, among other unexpected problems.
+ *
+ * \param handle handle returned from PHYSFS_open*().
+ * \param bufsize size, in bytes, of buffer to allocate.
+ * \return nonzero if successful, zero on error.
+ *
+ * \sa PHYSFS_flush
+ * \sa PHYSFS_read
+ * \sa PHYSFS_write
+ * \sa PHYSFS_close
+ */
+PHYSFS_DECL int PHYSFS_setBuffer(PHYSFS_File *handle, PHYSFS_uint64 bufsize);
+
+
+/**
+ * \fn int PHYSFS_flush(PHYSFS_File *handle)
+ * \brief Flush a buffered PhysicsFS file handle.
+ *
+ * For buffered files opened for writing, this will put the current contents
+ * of the buffer to disk and flag the buffer as empty if possible.
+ *
+ * For buffered files opened for reading or unbuffered files, this is a safe
+ * no-op, and will report success.
+ *
+ * \param handle handle returned from PHYSFS_open*().
+ * \return nonzero if successful, zero on error.
+ *
+ * \sa PHYSFS_setBuffer
+ * \sa PHYSFS_close
+ */
+PHYSFS_DECL int PHYSFS_flush(PHYSFS_File *handle);
+
+
+/* Byteorder stuff... */
+
+/**
+ * \fn PHYSFS_sint16 PHYSFS_swapSLE16(PHYSFS_sint16 val)
+ * \brief Swap littleendian signed 16 to platform's native byte order.
+ *
+ * Take a 16-bit signed value in littleendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ */
+PHYSFS_DECL PHYSFS_sint16 PHYSFS_swapSLE16(PHYSFS_sint16 val);
+
+
+/**
+ * \fn PHYSFS_uint16 PHYSFS_swapULE16(PHYSFS_uint16 val)
+ * \brief Swap littleendian unsigned 16 to platform's native byte order.
+ *
+ * Take a 16-bit unsigned value in littleendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ */
+PHYSFS_DECL PHYSFS_uint16 PHYSFS_swapULE16(PHYSFS_uint16 val);
+
+/**
+ * \fn PHYSFS_sint32 PHYSFS_swapSLE32(PHYSFS_sint32 val)
+ * \brief Swap littleendian signed 32 to platform's native byte order.
+ *
+ * Take a 32-bit signed value in littleendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ */
+PHYSFS_DECL PHYSFS_sint32 PHYSFS_swapSLE32(PHYSFS_sint32 val);
+
+
+/**
+ * \fn PHYSFS_uint32 PHYSFS_swapULE32(PHYSFS_uint32 val)
+ * \brief Swap littleendian unsigned 32 to platform's native byte order.
+ *
+ * Take a 32-bit unsigned value in littleendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ */
+PHYSFS_DECL PHYSFS_uint32 PHYSFS_swapULE32(PHYSFS_uint32 val);
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_swapSLE64(PHYSFS_sint64 val)
+ * \brief Swap littleendian signed 64 to platform's native byte order.
+ *
+ * Take a 64-bit signed value in littleendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ *
+ * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_swapSLE64(PHYSFS_sint64 val);
+
+
+/**
+ * \fn PHYSFS_uint64 PHYSFS_swapULE64(PHYSFS_uint64 val)
+ * \brief Swap littleendian unsigned 64 to platform's native byte order.
+ *
+ * Take a 64-bit unsigned value in littleendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ *
+ * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL PHYSFS_uint64 PHYSFS_swapULE64(PHYSFS_uint64 val);
+
+
+/**
+ * \fn PHYSFS_sint16 PHYSFS_swapSBE16(PHYSFS_sint16 val)
+ * \brief Swap bigendian signed 16 to platform's native byte order.
+ *
+ * Take a 16-bit signed value in bigendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ */
+PHYSFS_DECL PHYSFS_sint16 PHYSFS_swapSBE16(PHYSFS_sint16 val);
+
+
+/**
+ * \fn PHYSFS_uint16 PHYSFS_swapUBE16(PHYSFS_uint16 val)
+ * \brief Swap bigendian unsigned 16 to platform's native byte order.
+ *
+ * Take a 16-bit unsigned value in bigendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ */
+PHYSFS_DECL PHYSFS_uint16 PHYSFS_swapUBE16(PHYSFS_uint16 val);
+
+/**
+ * \fn PHYSFS_sint32 PHYSFS_swapSBE32(PHYSFS_sint32 val)
+ * \brief Swap bigendian signed 32 to platform's native byte order.
+ *
+ * Take a 32-bit signed value in bigendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ */
+PHYSFS_DECL PHYSFS_sint32 PHYSFS_swapSBE32(PHYSFS_sint32 val);
+
+
+/**
+ * \fn PHYSFS_uint32 PHYSFS_swapUBE32(PHYSFS_uint32 val)
+ * \brief Swap bigendian unsigned 32 to platform's native byte order.
+ *
+ * Take a 32-bit unsigned value in bigendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ */
+PHYSFS_DECL PHYSFS_uint32 PHYSFS_swapUBE32(PHYSFS_uint32 val);
+
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_swapSBE64(PHYSFS_sint64 val)
+ * \brief Swap bigendian signed 64 to platform's native byte order.
+ *
+ * Take a 64-bit signed value in bigendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ *
+ * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_swapSBE64(PHYSFS_sint64 val);
+
+
+/**
+ * \fn PHYSFS_uint64 PHYSFS_swapUBE64(PHYSFS_uint64 val)
+ * \brief Swap bigendian unsigned 64 to platform's native byte order.
+ *
+ * Take a 64-bit unsigned value in bigendian format and convert it to
+ * the platform's native byte order.
+ *
+ * \param val value to convert
+ * \return converted value.
+ *
+ * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL PHYSFS_uint64 PHYSFS_swapUBE64(PHYSFS_uint64 val);
+
+
+/**
+ * \fn int PHYSFS_readSLE16(PHYSFS_File *file, PHYSFS_sint16 *val)
+ * \brief Read and convert a signed 16-bit littleendian value.
+ *
+ * Convenience function. Read a signed 16-bit littleendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_readSLE16(PHYSFS_File *file, PHYSFS_sint16 *val);
+
+
+/**
+ * \fn int PHYSFS_readULE16(PHYSFS_File *file, PHYSFS_uint16 *val)
+ * \brief Read and convert an unsigned 16-bit littleendian value.
+ *
+ * Convenience function. Read an unsigned 16-bit littleendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ *
+ */
+PHYSFS_DECL int PHYSFS_readULE16(PHYSFS_File *file, PHYSFS_uint16 *val);
+
+
+/**
+ * \fn int PHYSFS_readSBE16(PHYSFS_File *file, PHYSFS_sint16 *val)
+ * \brief Read and convert a signed 16-bit bigendian value.
+ *
+ * Convenience function. Read a signed 16-bit bigendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_readSBE16(PHYSFS_File *file, PHYSFS_sint16 *val);
+
+
+/**
+ * \fn int PHYSFS_readUBE16(PHYSFS_File *file, PHYSFS_uint16 *val)
+ * \brief Read and convert an unsigned 16-bit bigendian value.
+ *
+ * Convenience function. Read an unsigned 16-bit bigendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ *
+ */
+PHYSFS_DECL int PHYSFS_readUBE16(PHYSFS_File *file, PHYSFS_uint16 *val);
+
+
+/**
+ * \fn int PHYSFS_readSLE32(PHYSFS_File *file, PHYSFS_sint32 *val)
+ * \brief Read and convert a signed 32-bit littleendian value.
+ *
+ * Convenience function. Read a signed 32-bit littleendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_readSLE32(PHYSFS_File *file, PHYSFS_sint32 *val);
+
+
+/**
+ * \fn int PHYSFS_readULE32(PHYSFS_File *file, PHYSFS_uint32 *val)
+ * \brief Read and convert an unsigned 32-bit littleendian value.
+ *
+ * Convenience function. Read an unsigned 32-bit littleendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ *
+ */
+PHYSFS_DECL int PHYSFS_readULE32(PHYSFS_File *file, PHYSFS_uint32 *val);
+
+
+/**
+ * \fn int PHYSFS_readSBE32(PHYSFS_File *file, PHYSFS_sint32 *val)
+ * \brief Read and convert a signed 32-bit bigendian value.
+ *
+ * Convenience function. Read a signed 32-bit bigendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_readSBE32(PHYSFS_File *file, PHYSFS_sint32 *val);
+
+
+/**
+ * \fn int PHYSFS_readUBE32(PHYSFS_File *file, PHYSFS_uint32 *val)
+ * \brief Read and convert an unsigned 32-bit bigendian value.
+ *
+ * Convenience function. Read an unsigned 32-bit bigendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ *
+ */
+PHYSFS_DECL int PHYSFS_readUBE32(PHYSFS_File *file, PHYSFS_uint32 *val);
+
+
+/**
+ * \fn int PHYSFS_readSLE64(PHYSFS_File *file, PHYSFS_sint64 *val)
+ * \brief Read and convert a signed 64-bit littleendian value.
+ *
+ * Convenience function. Read a signed 64-bit littleendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ *
+ * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL int PHYSFS_readSLE64(PHYSFS_File *file, PHYSFS_sint64 *val);
+
+
+/**
+ * \fn int PHYSFS_readULE64(PHYSFS_File *file, PHYSFS_uint64 *val)
+ * \brief Read and convert an unsigned 64-bit littleendian value.
+ *
+ * Convenience function. Read an unsigned 64-bit littleendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ *
+ * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL int PHYSFS_readULE64(PHYSFS_File *file, PHYSFS_uint64 *val);
+
+
+/**
+ * \fn int PHYSFS_readSBE64(PHYSFS_File *file, PHYSFS_sint64 *val)
+ * \brief Read and convert a signed 64-bit bigendian value.
+ *
+ * Convenience function. Read a signed 64-bit bigendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ *
+ * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL int PHYSFS_readSBE64(PHYSFS_File *file, PHYSFS_sint64 *val);
+
+
+/**
+ * \fn int PHYSFS_readUBE64(PHYSFS_File *file, PHYSFS_uint64 *val)
+ * \brief Read and convert an unsigned 64-bit bigendian value.
+ *
+ * Convenience function. Read an unsigned 64-bit bigendian value from a
+ * file and convert it to the platform's native byte order.
+ *
+ * \param file PhysicsFS file handle from which to read.
+ * \param val pointer to where value should be stored.
+ * \return zero on failure, non-zero on success. If successful, (*val) will
+ * store the result. On failure, you can find out what went wrong
+ * from PHYSFS_getLastErrorCode().
+ *
+ * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL int PHYSFS_readUBE64(PHYSFS_File *file, PHYSFS_uint64 *val);
+
+
+/**
+ * \fn int PHYSFS_writeSLE16(PHYSFS_File *file, PHYSFS_sint16 val)
+ * \brief Convert and write a signed 16-bit littleendian value.
+ *
+ * Convenience function. Convert a signed 16-bit value from the platform's
+ * native byte order to littleendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_writeSLE16(PHYSFS_File *file, PHYSFS_sint16 val);
+
+
+/**
+ * \fn int PHYSFS_writeULE16(PHYSFS_File *file, PHYSFS_uint16 val)
+ * \brief Convert and write an unsigned 16-bit littleendian value.
+ *
+ * Convenience function. Convert an unsigned 16-bit value from the platform's
+ * native byte order to littleendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_writeULE16(PHYSFS_File *file, PHYSFS_uint16 val);
+
+
+/**
+ * \fn int PHYSFS_writeSBE16(PHYSFS_File *file, PHYSFS_sint16 val)
+ * \brief Convert and write a signed 16-bit bigendian value.
+ *
+ * Convenience function. Convert a signed 16-bit value from the platform's
+ * native byte order to bigendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_writeSBE16(PHYSFS_File *file, PHYSFS_sint16 val);
+
+
+/**
+ * \fn int PHYSFS_writeUBE16(PHYSFS_File *file, PHYSFS_uint16 val)
+ * \brief Convert and write an unsigned 16-bit bigendian value.
+ *
+ * Convenience function. Convert an unsigned 16-bit value from the platform's
+ * native byte order to bigendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_writeUBE16(PHYSFS_File *file, PHYSFS_uint16 val);
+
+
+/**
+ * \fn int PHYSFS_writeSLE32(PHYSFS_File *file, PHYSFS_sint32 val)
+ * \brief Convert and write a signed 32-bit littleendian value.
+ *
+ * Convenience function. Convert a signed 32-bit value from the platform's
+ * native byte order to littleendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_writeSLE32(PHYSFS_File *file, PHYSFS_sint32 val);
+
+
+/**
+ * \fn int PHYSFS_writeULE32(PHYSFS_File *file, PHYSFS_uint32 val)
+ * \brief Convert and write an unsigned 32-bit littleendian value.
+ *
+ * Convenience function. Convert an unsigned 32-bit value from the platform's
+ * native byte order to littleendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_writeULE32(PHYSFS_File *file, PHYSFS_uint32 val);
+
+
+/**
+ * \fn int PHYSFS_writeSBE32(PHYSFS_File *file, PHYSFS_sint32 val)
+ * \brief Convert and write a signed 32-bit bigendian value.
+ *
+ * Convenience function. Convert a signed 32-bit value from the platform's
+ * native byte order to bigendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_writeSBE32(PHYSFS_File *file, PHYSFS_sint32 val);
+
+
+/**
+ * \fn int PHYSFS_writeUBE32(PHYSFS_File *file, PHYSFS_uint32 val)
+ * \brief Convert and write an unsigned 32-bit bigendian value.
+ *
+ * Convenience function. Convert an unsigned 32-bit value from the platform's
+ * native byte order to bigendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ */
+PHYSFS_DECL int PHYSFS_writeUBE32(PHYSFS_File *file, PHYSFS_uint32 val);
+
+
+/**
+ * \fn int PHYSFS_writeSLE64(PHYSFS_File *file, PHYSFS_sint64 val)
+ * \brief Convert and write a signed 64-bit littleendian value.
+ *
+ * Convenience function. Convert a signed 64-bit value from the platform's
+ * native byte order to littleendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ *
+ * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL int PHYSFS_writeSLE64(PHYSFS_File *file, PHYSFS_sint64 val);
+
+
+/**
+ * \fn int PHYSFS_writeULE64(PHYSFS_File *file, PHYSFS_uint64 val)
+ * \brief Convert and write an unsigned 64-bit littleendian value.
+ *
+ * Convenience function. Convert an unsigned 64-bit value from the platform's
+ * native byte order to littleendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ *
+ * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL int PHYSFS_writeULE64(PHYSFS_File *file, PHYSFS_uint64 val);
+
+
+/**
+ * \fn int PHYSFS_writeSBE64(PHYSFS_File *file, PHYSFS_sint64 val)
+ * \brief Convert and write a signed 64-bit bigending value.
+ *
+ * Convenience function. Convert a signed 64-bit value from the platform's
+ * native byte order to bigendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ *
+ * \warning Remember, PHYSFS_sint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL int PHYSFS_writeSBE64(PHYSFS_File *file, PHYSFS_sint64 val);
+
+
+/**
+ * \fn int PHYSFS_writeUBE64(PHYSFS_File *file, PHYSFS_uint64 val)
+ * \brief Convert and write an unsigned 64-bit bigendian value.
+ *
+ * Convenience function. Convert an unsigned 64-bit value from the platform's
+ * native byte order to bigendian and write it to a file.
+ *
+ * \param file PhysicsFS file handle to which to write.
+ * \param val Value to convert and write.
+ * \return zero on failure, non-zero on success. On failure, you can
+ * find out what went wrong from PHYSFS_getLastErrorCode().
+ *
+ * \warning Remember, PHYSFS_uint64 is only 32 bits on platforms without
+ * any sort of 64-bit support.
+ */
+PHYSFS_DECL int PHYSFS_writeUBE64(PHYSFS_File *file, PHYSFS_uint64 val);
+
+
+/* Everything above this line is part of the PhysicsFS 1.0 API. */
+
+/**
+ * \fn int PHYSFS_isInit(void)
+ * \brief Determine if the PhysicsFS library is initialized.
+ *
+ * Once PHYSFS_init() returns successfully, this will return non-zero.
+ * Before a successful PHYSFS_init() and after PHYSFS_deinit() returns
+ * successfully, this will return zero. This function is safe to call at
+ * any time.
+ *
+ * \return non-zero if library is initialized, zero if library is not.
+ *
+ * \sa PHYSFS_init
+ * \sa PHYSFS_deinit
+ */
+PHYSFS_DECL int PHYSFS_isInit(void);
+
+
+/**
+ * \fn int PHYSFS_symbolicLinksPermitted(void)
+ * \brief Determine if the symbolic links are permitted.
+ *
+ * This reports the setting from the last call to PHYSFS_permitSymbolicLinks().
+ * If PHYSFS_permitSymbolicLinks() hasn't been called since the library was
+ * last initialized, symbolic links are implicitly disabled.
+ *
+ * \return non-zero if symlinks are permitted, zero if not.
+ *
+ * \sa PHYSFS_permitSymbolicLinks
+ */
+PHYSFS_DECL int PHYSFS_symbolicLinksPermitted(void);
+
+
+/**
+ * \struct PHYSFS_Allocator
+ * \brief PhysicsFS allocation function pointers.
+ *
+ * (This is for limited, hardcore use. If you don't immediately see a need
+ * for it, you can probably ignore this forever.)
+ *
+ * You create one of these structures for use with PHYSFS_setAllocator.
+ * Allocators are assumed to be reentrant by the caller; please mutex
+ * accordingly.
+ *
+ * Allocations are always discussed in 64-bits, for future expansion...we're
+ * on the cusp of a 64-bit transition, and we'll probably be allocating 6
+ * gigabytes like it's nothing sooner or later, and I don't want to change
+ * this again at that point. If you're on a 32-bit platform and have to
+ * downcast, it's okay to return NULL if the allocation is greater than
+ * 4 gigabytes, since you'd have to do so anyhow.
+ *
+ * \sa PHYSFS_setAllocator
+ */
+typedef struct PHYSFS_Allocator
+{
+ int (*Init)(void); /**< Initialize. Can be NULL. Zero on failure. */
+ void (*Deinit)(void); /**< Deinitialize your allocator. Can be NULL. */
+ void *(*Malloc)(PHYSFS_uint64); /**< Allocate like malloc(). */
+ void *(*Realloc)(void *, PHYSFS_uint64); /**< Reallocate like realloc(). */
+ void (*Free)(void *); /**< Free memory from Malloc or Realloc. */
+} PHYSFS_Allocator;
+
+
+/**
+ * \fn int PHYSFS_setAllocator(const PHYSFS_Allocator *allocator)
+ * \brief Hook your own allocation routines into PhysicsFS.
+ *
+ * (This is for limited, hardcore use. If you don't immediately see a need
+ * for it, you can probably ignore this forever.)
+ *
+ * By default, PhysicsFS will use whatever is reasonable for a platform
+ * to manage dynamic memory (usually ANSI C malloc/realloc/free, but
+ * some platforms might use something else), but in some uncommon cases, the
+ * app might want more control over the library's memory management. This
+ * lets you redirect PhysicsFS to use your own allocation routines instead.
+ * You can only call this function before PHYSFS_init(); if the library is
+ * initialized, it'll reject your efforts to change the allocator mid-stream.
+ * You may call this function after PHYSFS_deinit() if you are willing to
+ * shut down the library and restart it with a new allocator; this is a safe
+ * and supported operation. The allocator remains intact between deinit/init
+ * calls. If you want to return to the platform's default allocator, pass a
+ * NULL in here.
+ *
+ * If you aren't immediately sure what to do with this function, you can
+ * safely ignore it altogether.
+ *
+ * \param allocator Structure containing your allocator's entry points.
+ * \return zero on failure, non-zero on success. This call only fails
+ * when used between PHYSFS_init() and PHYSFS_deinit() calls.
+ */
+PHYSFS_DECL int PHYSFS_setAllocator(const PHYSFS_Allocator *allocator);
+
+
+/**
+ * \fn int PHYSFS_mount(const char *newDir, const char *mountPoint, int appendToPath)
+ * \brief Add an archive or directory to the search path.
+ *
+ * If this is a duplicate, the entry is not added again, even though the
+ * function succeeds. You may not add the same archive to two different
+ * mountpoints: duplicate checking is done against the archive and not the
+ * mountpoint.
+ *
+ * When you mount an archive, it is added to a virtual file system...all files
+ * in all of the archives are interpolated into a single hierachical file
+ * tree. Two archives mounted at the same place (or an archive with files
+ * overlapping another mountpoint) may have overlapping files: in such a case,
+ * the file earliest in the search path is selected, and the other files are
+ * inaccessible to the application. This allows archives to be used to
+ * override previous revisions; you can use the mounting mechanism to place
+ * archives at a specific point in the file tree and prevent overlap; this
+ * is useful for downloadable mods that might trample over application data
+ * or each other, for example.
+ *
+ * The mountpoint does not need to exist prior to mounting, which is different
+ * than those familiar with the Unix concept of "mounting" may expect.
+ * As well, more than one archive can be mounted to the same mountpoint, or
+ * mountpoints and archive contents can overlap...the interpolation mechanism
+ * still functions as usual.
+ *
+ * Specifying a symbolic link to an archive or directory is allowed here,
+ * regardless of the state of PHYSFS_permitSymbolicLinks(). That function
+ * only deals with symlinks inside the mounted directory or archive.
+ *
+ * \param newDir directory or archive to add to the path, in
+ * platform-dependent notation.
+ * \param mountPoint Location in the interpolated tree that this archive
+ * will be "mounted", in platform-independent notation.
+ * NULL or "" is equivalent to "/".
+ * \param appendToPath nonzero to append to search path, zero to prepend.
+ * \return nonzero if added to path, zero on failure (bogus archive, dir
+ * missing, etc). Use PHYSFS_getLastErrorCode() to obtain
+ * the specific error.
+ *
+ * \sa PHYSFS_removeFromSearchPath
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_getMountPoint
+ * \sa PHYSFS_mountIo
+ */
+PHYSFS_DECL int PHYSFS_mount(const char *newDir,
+ const char *mountPoint,
+ int appendToPath);
+
+/**
+ * \fn int PHYSFS_getMountPoint(const char *dir)
+ * \brief Determine a mounted archive's mountpoint.
+ *
+ * You give this function the name of an archive or dir you successfully
+ * added to the search path, and it reports the location in the interpolated
+ * tree where it is mounted. Files mounted with a NULL mountpoint or through
+ * PHYSFS_addToSearchPath() will report "/". The return value is READ ONLY
+ * and valid until the archive is removed from the search path.
+ *
+ * \param dir directory or archive previously added to the path, in
+ * platform-dependent notation. This must match the string
+ * used when adding, even if your string would also reference
+ * the same file with a different string of characters.
+ * \return READ-ONLY string of mount point if added to path, NULL on failure
+ * (bogus archive, etc). Use PHYSFS_getLastErrorCode() to obtain the
+ * specific error.
+ *
+ * \sa PHYSFS_removeFromSearchPath
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_getMountPoint
+ */
+PHYSFS_DECL const char *PHYSFS_getMountPoint(const char *dir);
+
+
+/**
+ * \typedef PHYSFS_StringCallback
+ * \brief Function signature for callbacks that report strings.
+ *
+ * These are used to report a list of strings to an original caller, one
+ * string per callback. All strings are UTF-8 encoded. Functions should not
+ * try to modify or free the string's memory.
+ *
+ * These callbacks are used, starting in PhysicsFS 1.1, as an alternative to
+ * functions that would return lists that need to be cleaned up with
+ * PHYSFS_freeList(). The callback means that the library doesn't need to
+ * allocate an entire list and all the strings up front.
+ *
+ * Be aware that promises data ordering in the list versions are not
+ * necessarily so in the callback versions. Check the documentation on
+ * specific APIs, but strings may not be sorted as you expect.
+ *
+ * \param data User-defined data pointer, passed through from the API
+ * that eventually called the callback.
+ * \param str The string data about which the callback is meant to inform.
+ *
+ * \sa PHYSFS_getCdRomDirsCallback
+ * \sa PHYSFS_getSearchPathCallback
+ */
+typedef void (*PHYSFS_StringCallback)(void *data, const char *str);
+
+
+/**
+ * \typedef PHYSFS_EnumFilesCallback
+ * \brief Function signature for callbacks that enumerate files.
+ *
+ * \warning As of PhysicsFS 2.1, Use PHYSFS_EnumerateCallback with
+ * PHYSFS_enumerate() instead; it gives you more control over the process.
+ *
+ * These are used to report a list of directory entries to an original caller,
+ * one file/dir/symlink per callback. All strings are UTF-8 encoded.
+ * Functions should not try to modify or free any string's memory.
+ *
+ * These callbacks are used, starting in PhysicsFS 1.1, as an alternative to
+ * functions that would return lists that need to be cleaned up with
+ * PHYSFS_freeList(). The callback means that the library doesn't need to
+ * allocate an entire list and all the strings up front.
+ *
+ * Be aware that promised data ordering in the list versions are not
+ * necessarily so in the callback versions. Check the documentation on
+ * specific APIs, but strings may not be sorted as you expect and you might
+ * get duplicate strings.
+ *
+ * \param data User-defined data pointer, passed through from the API
+ * that eventually called the callback.
+ * \param origdir A string containing the full path, in platform-independent
+ * notation, of the directory containing this file. In most
+ * cases, this is the directory on which you requested
+ * enumeration, passed in the callback for your convenience.
+ * \param fname The filename that is being enumerated. It may not be in
+ * alphabetical order compared to other callbacks that have
+ * fired, and it will not contain the full path. You can
+ * recreate the fullpath with $origdir/$fname ... The file
+ * can be a subdirectory, a file, a symlink, etc.
+ *
+ * \sa PHYSFS_enumerateFilesCallback
+ */
+typedef void (*PHYSFS_EnumFilesCallback)(void *data, const char *origdir,
+ const char *fname);
+
+
+/**
+ * \fn void PHYSFS_getCdRomDirsCallback(PHYSFS_StringCallback c, void *d)
+ * \brief Enumerate CD-ROM directories, using an application-defined callback.
+ *
+ * Internally, PHYSFS_getCdRomDirs() just calls this function and then builds
+ * a list before returning to the application, so functionality is identical
+ * except for how the information is represented to the application.
+ *
+ * Unlike PHYSFS_getCdRomDirs(), this function does not return an array.
+ * Rather, it calls a function specified by the application once per
+ * detected disc:
+ *
+ * \code
+ *
+ * static void foundDisc(void *data, const char *cddir)
+ * {
+ * printf("cdrom dir [%s] is available.\n", cddir);
+ * }
+ *
+ * // ...
+ * PHYSFS_getCdRomDirsCallback(foundDisc, NULL);
+ * \endcode
+ *
+ * This call may block while drives spin up. Be forewarned.
+ *
+ * \param c Callback function to notify about detected drives.
+ * \param d Application-defined data passed to callback. Can be NULL.
+ *
+ * \sa PHYSFS_StringCallback
+ * \sa PHYSFS_getCdRomDirs
+ */
+PHYSFS_DECL void PHYSFS_getCdRomDirsCallback(PHYSFS_StringCallback c, void *d);
+
+
+/**
+ * \fn void PHYSFS_getSearchPathCallback(PHYSFS_StringCallback c, void *d)
+ * \brief Enumerate the search path, using an application-defined callback.
+ *
+ * Internally, PHYSFS_getSearchPath() just calls this function and then builds
+ * a list before returning to the application, so functionality is identical
+ * except for how the information is represented to the application.
+ *
+ * Unlike PHYSFS_getSearchPath(), this function does not return an array.
+ * Rather, it calls a function specified by the application once per
+ * element of the search path:
+ *
+ * \code
+ *
+ * static void printSearchPath(void *data, const char *pathItem)
+ * {
+ * printf("[%s] is in the search path.\n", pathItem);
+ * }
+ *
+ * // ...
+ * PHYSFS_getSearchPathCallback(printSearchPath, NULL);
+ * \endcode
+ *
+ * Elements of the search path are reported in order search priority, so the
+ * first archive/dir that would be examined when looking for a file is the
+ * first element passed through the callback.
+ *
+ * \param c Callback function to notify about search path elements.
+ * \param d Application-defined data passed to callback. Can be NULL.
+ *
+ * \sa PHYSFS_StringCallback
+ * \sa PHYSFS_getSearchPath
+ */
+PHYSFS_DECL void PHYSFS_getSearchPathCallback(PHYSFS_StringCallback c, void *d);
+
+
+/**
+ * \fn void PHYSFS_enumerateFilesCallback(const char *dir, PHYSFS_EnumFilesCallback c, void *d)
+ * \brief Get a file listing of a search path's directory, using an application-defined callback.
+ *
+ * \deprecated As of PhysicsFS 2.1, use PHYSFS_enumerate() instead. This
+ * function has no way to report errors (or to have the callback signal an
+ * error or request a stop), so if data will be lost, your callback has no
+ * way to direct the process, and your calling app has no way to know.
+ *
+ * As of PhysicsFS 2.1, this function just wraps PHYSFS_enumerate() and
+ * ignores errors. Consider using PHYSFS_enumerate() or
+ * PHYSFS_enumerateFiles() instead.
+ *
+ * \sa PHYSFS_enumerate
+ * \sa PHYSFS_enumerateFiles
+ * \sa PHYSFS_EnumFilesCallback
+ */
+PHYSFS_DECL void PHYSFS_enumerateFilesCallback(const char *dir,
+ PHYSFS_EnumFilesCallback c,
+ void *d) PHYSFS_DEPRECATED;
+
+/**
+ * \fn void PHYSFS_utf8FromUcs4(const PHYSFS_uint32 *src, char *dst, PHYSFS_uint64 len)
+ * \brief Convert a UCS-4 string to a UTF-8 string.
+ *
+ * \warning This function will not report an error if there are invalid UCS-4
+ * values in the source string. It will replace them with a '?'
+ * character and continue on.
+ *
+ * UCS-4 (aka UTF-32) strings are 32-bits per character: \c wchar_t on Unix.
+ *
+ * To ensure that the destination buffer is large enough for the conversion,
+ * please allocate a buffer that is the same size as the source buffer. UTF-8
+ * never uses more than 32-bits per character, so while it may shrink a UCS-4
+ * string, it will never expand it.
+ *
+ * Strings that don't fit in the destination buffer will be truncated, but
+ * will always be null-terminated and never have an incomplete UTF-8
+ * sequence at the end. If the buffer length is 0, this function does nothing.
+ *
+ * \param src Null-terminated source string in UCS-4 format.
+ * \param dst Buffer to store converted UTF-8 string.
+ * \param len Size, in bytes, of destination buffer.
+ */
+PHYSFS_DECL void PHYSFS_utf8FromUcs4(const PHYSFS_uint32 *src, char *dst,
+ PHYSFS_uint64 len);
+
+/**
+ * \fn void PHYSFS_utf8ToUcs4(const char *src, PHYSFS_uint32 *dst, PHYSFS_uint64 len)
+ * \brief Convert a UTF-8 string to a UCS-4 string.
+ *
+ * \warning This function will not report an error if there are invalid UTF-8
+ * sequences in the source string. It will replace them with a '?'
+ * character and continue on.
+ *
+ * UCS-4 (aka UTF-32) strings are 32-bits per character: \c wchar_t on Unix.
+ *
+ * To ensure that the destination buffer is large enough for the conversion,
+ * please allocate a buffer that is four times the size of the source buffer.
+ * UTF-8 uses from one to four bytes per character, but UCS-4 always uses
+ * four, so an entirely low-ASCII string will quadruple in size!
+ *
+ * Strings that don't fit in the destination buffer will be truncated, but
+ * will always be null-terminated and never have an incomplete UCS-4
+ * sequence at the end. If the buffer length is 0, this function does nothing.
+ *
+ * \param src Null-terminated source string in UTF-8 format.
+ * \param dst Buffer to store converted UCS-4 string.
+ * \param len Size, in bytes, of destination buffer.
+ */
+PHYSFS_DECL void PHYSFS_utf8ToUcs4(const char *src, PHYSFS_uint32 *dst,
+ PHYSFS_uint64 len);
+
+/**
+ * \fn void PHYSFS_utf8FromUcs2(const PHYSFS_uint16 *src, char *dst, PHYSFS_uint64 len)
+ * \brief Convert a UCS-2 string to a UTF-8 string.
+ *
+ * \warning you almost certainly should use PHYSFS_utf8FromUtf16(), which
+ * became available in PhysicsFS 2.1, unless you know what you're doing.
+ *
+ * \warning This function will not report an error if there are invalid UCS-2
+ * values in the source string. It will replace them with a '?'
+ * character and continue on.
+ *
+ * UCS-2 strings are 16-bits per character: \c TCHAR on Windows, when building
+ * with Unicode support. Please note that modern versions of Windows use
+ * UTF-16, which is an extended form of UCS-2, and not UCS-2 itself. You
+ * almost certainly want PHYSFS_utf8FromUtf16() instead.
+ *
+ * To ensure that the destination buffer is large enough for the conversion,
+ * please allocate a buffer that is double the size of the source buffer.
+ * UTF-8 never uses more than 32-bits per character, so while it may shrink
+ * a UCS-2 string, it may also expand it.
+ *
+ * Strings that don't fit in the destination buffer will be truncated, but
+ * will always be null-terminated and never have an incomplete UTF-8
+ * sequence at the end. If the buffer length is 0, this function does nothing.
+ *
+ * \param src Null-terminated source string in UCS-2 format.
+ * \param dst Buffer to store converted UTF-8 string.
+ * \param len Size, in bytes, of destination buffer.
+ *
+ * \sa PHYSFS_utf8FromUtf16
+ */
+PHYSFS_DECL void PHYSFS_utf8FromUcs2(const PHYSFS_uint16 *src, char *dst,
+ PHYSFS_uint64 len);
+
+/**
+ * \fn PHYSFS_utf8ToUcs2(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len)
+ * \brief Convert a UTF-8 string to a UCS-2 string.
+ *
+ * \warning you almost certainly should use PHYSFS_utf8ToUtf16(), which
+ * became available in PhysicsFS 2.1, unless you know what you're doing.
+ *
+ * \warning This function will not report an error if there are invalid UTF-8
+ * sequences in the source string. It will replace them with a '?'
+ * character and continue on.
+ *
+ * UCS-2 strings are 16-bits per character: \c TCHAR on Windows, when building
+ * with Unicode support. Please note that modern versions of Windows use
+ * UTF-16, which is an extended form of UCS-2, and not UCS-2 itself. You
+ * almost certainly want PHYSFS_utf8ToUtf16() instead, but you need to
+ * understand how that changes things, too.
+ *
+ * To ensure that the destination buffer is large enough for the conversion,
+ * please allocate a buffer that is double the size of the source buffer.
+ * UTF-8 uses from one to four bytes per character, but UCS-2 always uses
+ * two, so an entirely low-ASCII string will double in size!
+ *
+ * Strings that don't fit in the destination buffer will be truncated, but
+ * will always be null-terminated and never have an incomplete UCS-2
+ * sequence at the end. If the buffer length is 0, this function does nothing.
+ *
+ * \param src Null-terminated source string in UTF-8 format.
+ * \param dst Buffer to store converted UCS-2 string.
+ * \param len Size, in bytes, of destination buffer.
+ *
+ * \sa PHYSFS_utf8ToUtf16
+ */
+PHYSFS_DECL void PHYSFS_utf8ToUcs2(const char *src, PHYSFS_uint16 *dst,
+ PHYSFS_uint64 len);
+
+/**
+ * \fn void PHYSFS_utf8FromLatin1(const char *src, char *dst, PHYSFS_uint64 len)
+ * \brief Convert a UTF-8 string to a Latin1 string.
+ *
+ * Latin1 strings are 8-bits per character: a popular "high ASCII" encoding.
+ *
+ * To ensure that the destination buffer is large enough for the conversion,
+ * please allocate a buffer that is double the size of the source buffer.
+ * UTF-8 expands latin1 codepoints over 127 from 1 to 2 bytes, so the string
+ * may grow in some cases.
+ *
+ * Strings that don't fit in the destination buffer will be truncated, but
+ * will always be null-terminated and never have an incomplete UTF-8
+ * sequence at the end. If the buffer length is 0, this function does nothing.
+ *
+ * Please note that we do not supply a UTF-8 to Latin1 converter, since Latin1
+ * can't express most Unicode codepoints. It's a legacy encoding; you should
+ * be converting away from it at all times.
+ *
+ * \param src Null-terminated source string in Latin1 format.
+ * \param dst Buffer to store converted UTF-8 string.
+ * \param len Size, in bytes, of destination buffer.
+ */
+PHYSFS_DECL void PHYSFS_utf8FromLatin1(const char *src, char *dst,
+ PHYSFS_uint64 len);
+
+/* Everything above this line is part of the PhysicsFS 2.0 API. */
+
+/**
+ * \fn int PHYSFS_caseFold(const PHYSFS_uint32 from, PHYSFS_uint32 *to)
+ * \brief "Fold" a Unicode codepoint to a lowercase equivalent.
+ *
+ * (This is for limited, hardcore use. If you don't immediately see a need
+ * for it, you can probably ignore this forever.)
+ *
+ * This will convert a Unicode codepoint into its lowercase equivalent.
+ * Bogus codepoints and codepoints without a lowercase equivalent will
+ * be returned unconverted.
+ *
+ * Note that you might get multiple codepoints in return! The German Eszett,
+ * for example, will fold down to two lowercase latin 's' codepoints. The
+ * theory is that if you fold two strings, one with an Eszett and one with
+ * "SS" down, they will match.
+ *
+ * \warning Anyone that is a student of Unicode knows about the "Turkish I"
+ * problem. This API does not handle it. Assume this one letter
+ * in all of Unicode will definitely fold sort of incorrectly. If
+ * you don't know what this is about, you can probably ignore this
+ * problem for most of the planet, but perfection is impossible.
+ *
+ * \param from The codepoint to fold.
+ * \param to Buffer to store the folded codepoint values into. This should
+ * point to space for at least 3 PHYSFS_uint32 slots.
+ * \return The number of codepoints the folding produced. Between 1 and 3.
+ */
+PHYSFS_DECL int PHYSFS_caseFold(const PHYSFS_uint32 from, PHYSFS_uint32 *to);
+
+
+/**
+ * \fn int PHYSFS_utf8stricmp(const char *str1, const char *str2)
+ * \brief Case-insensitive compare of two UTF-8 strings.
+ *
+ * This is a strcasecmp/stricmp replacement that expects both strings
+ * to be in UTF-8 encoding. It will do "case folding" to decide if the
+ * Unicode codepoints in the strings match.
+ *
+ * If both strings are exclusively low-ASCII characters, this will do the
+ * right thing, as that is also valid UTF-8. If there are any high-ASCII
+ * chars, this will not do what you expect!
+ *
+ * It will report which string is "greater than" the other, but be aware that
+ * this doesn't necessarily mean anything: 'a' may be "less than" 'b', but
+ * a Japanese kuten has no meaningful alphabetically relationship to
+ * a Greek lambda, but being able to assign a reliable "value" makes sorting
+ * algorithms possible, if not entirely sane. Most cases should treat the
+ * return value as "equal" or "not equal".
+ *
+ * Like stricmp, this expects both strings to be NULL-terminated.
+ *
+ * \param str1 First string to compare.
+ * \param str2 Second string to compare.
+ * \return -1 if str1 is "less than" str2, 1 if "greater than", 0 if equal.
+ */
+PHYSFS_DECL int PHYSFS_utf8stricmp(const char *str1, const char *str2);
+
+/**
+ * \fn int PHYSFS_utf16stricmp(const PHYSFS_uint16 *str1, const PHYSFS_uint16 *str2)
+ * \brief Case-insensitive compare of two UTF-16 strings.
+ *
+ * This is a strcasecmp/stricmp replacement that expects both strings
+ * to be in UTF-16 encoding. It will do "case folding" to decide if the
+ * Unicode codepoints in the strings match.
+ *
+ * It will report which string is "greater than" the other, but be aware that
+ * this doesn't necessarily mean anything: 'a' may be "less than" 'b', but
+ * a Japanese kuten has no meaningful alphabetically relationship to
+ * a Greek lambda, but being able to assign a reliable "value" makes sorting
+ * algorithms possible, if not entirely sane. Most cases should treat the
+ * return value as "equal" or "not equal".
+ *
+ * Like stricmp, this expects both strings to be NULL-terminated.
+ *
+ * \param str1 First string to compare.
+ * \param str2 Second string to compare.
+ * \return -1 if str1 is "less than" str2, 1 if "greater than", 0 if equal.
+ */
+PHYSFS_DECL int PHYSFS_utf16stricmp(const PHYSFS_uint16 *str1,
+ const PHYSFS_uint16 *str2);
+
+/**
+ * \fn int PHYSFS_ucs4stricmp(const PHYSFS_uint32 *str1, const PHYSFS_uint32 *str2)
+ * \brief Case-insensitive compare of two UCS-4 strings.
+ *
+ * This is a strcasecmp/stricmp replacement that expects both strings
+ * to be in UCS-4 (aka UTF-32) encoding. It will do "case folding" to decide
+ * if the Unicode codepoints in the strings match.
+ *
+ * It will report which string is "greater than" the other, but be aware that
+ * this doesn't necessarily mean anything: 'a' may be "less than" 'b', but
+ * a Japanese kuten has no meaningful alphabetically relationship to
+ * a Greek lambda, but being able to assign a reliable "value" makes sorting
+ * algorithms possible, if not entirely sane. Most cases should treat the
+ * return value as "equal" or "not equal".
+ *
+ * Like stricmp, this expects both strings to be NULL-terminated.
+ *
+ * \param str1 First string to compare.
+ * \param str2 Second string to compare.
+ * \return -1 if str1 is "less than" str2, 1 if "greater than", 0 if equal.
+ */
+PHYSFS_DECL int PHYSFS_ucs4stricmp(const PHYSFS_uint32 *str1,
+ const PHYSFS_uint32 *str2);
+
+
+/**
+ * \typedef PHYSFS_EnumerateCallback
+ * \brief Possible return values from PHYSFS_EnumerateCallback.
+ *
+ * These values dictate if an enumeration callback should continue to fire,
+ * or stop (and why it is stopping).
+ *
+ * \sa PHYSFS_EnumerateCallback
+ * \sa PHYSFS_enumerate
+ */
+typedef enum PHYSFS_EnumerateCallbackResult
+{
+ PHYSFS_ENUM_ERROR = -1, /**< Stop enumerating, report error to app. */
+ PHYSFS_ENUM_STOP = 0, /**< Stop enumerating, report success to app. */
+ PHYSFS_ENUM_OK = 1 /**< Keep enumerating, no problems */
+} PHYSFS_EnumerateCallbackResult;
+
+/**
+ * \typedef PHYSFS_EnumerateCallback
+ * \brief Function signature for callbacks that enumerate and return results.
+ *
+ * This is the same thing as PHYSFS_EnumFilesCallback from PhysicsFS 2.0,
+ * except it can return a result from the callback: namely: if you're looking
+ * for something specific, once you find it, you can tell PhysicsFS to stop
+ * enumerating further. This is used with PHYSFS_enumerate(), which we
+ * hopefully got right this time. :)
+ *
+ * \param data User-defined data pointer, passed through from the API
+ * that eventually called the callback.
+ * \param origdir A string containing the full path, in platform-independent
+ * notation, of the directory containing this file. In most
+ * cases, this is the directory on which you requested
+ * enumeration, passed in the callback for your convenience.
+ * \param fname The filename that is being enumerated. It may not be in
+ * alphabetical order compared to other callbacks that have
+ * fired, and it will not contain the full path. You can
+ * recreate the fullpath with $origdir/$fname ... The file
+ * can be a subdirectory, a file, a symlink, etc.
+ * \return A value from PHYSFS_EnumerateCallbackResult.
+ * All other values are (currently) undefined; don't use them.
+ *
+ * \sa PHYSFS_enumerate
+ * \sa PHYSFS_EnumerateCallbackResult
+ */
+typedef PHYSFS_EnumerateCallbackResult (*PHYSFS_EnumerateCallback)(void *data,
+ const char *origdir, const char *fname);
+
+/**
+ * \fn int PHYSFS_enumerate(const char *dir, PHYSFS_EnumerateCallback c, void *d)
+ * \brief Get a file listing of a search path's directory, using an application-defined callback, with errors reported.
+ *
+ * Internally, PHYSFS_enumerateFiles() just calls this function and then builds
+ * a list before returning to the application, so functionality is identical
+ * except for how the information is represented to the application.
+ *
+ * Unlike PHYSFS_enumerateFiles(), this function does not return an array.
+ * Rather, it calls a function specified by the application once per
+ * element of the search path:
+ *
+ * \code
+ *
+ * static PHYSFS_EnumerateCallbackResult printDir(void *data, const char *origdir, const char *fname)
+ * {
+ * printf(" * We've got [%s] in [%s].\n", fname, origdir);
+ * return PHYSFS_ENUM_OK; // give me more data, please.
+ * }
+ *
+ * // ...
+ * PHYSFS_enumerate("/some/path", printDir, NULL);
+ * \endcode
+ *
+ * Items sent to the callback are not guaranteed to be in any order whatsoever.
+ * There is no sorting done at this level, and if you need that, you should
+ * probably use PHYSFS_enumerateFiles() instead, which guarantees
+ * alphabetical sorting. This form reports whatever is discovered in each
+ * archive before moving on to the next. Even within one archive, we can't
+ * guarantee what order it will discover data. Any sorting you find in
+ * these callbacks is just pure luck. Do not rely on it. As this walks
+ * the entire list of archives, you may receive duplicate filenames.
+ *
+ * This API and the callbacks themselves are capable of reporting errors.
+ * Prior to this API, callbacks had to accept every enumerated item, even if
+ * they were only looking for a specific thing and wanted to stop after that,
+ * or had a serious error and couldn't alert anyone. Furthermore, if
+ * PhysicsFS itself had a problem (disk error or whatnot), it couldn't report
+ * it to the calling app, it would just have to skip items or stop
+ * enumerating outright, and the caller wouldn't know it had lost some data
+ * along the way.
+ *
+ * Now the caller can be sure it got a complete data set, and its callback has
+ * control if it wants enumeration to stop early. See the documentation for
+ * PHYSFS_EnumerateCallback for details on how your callback should behave.
+ *
+ * \param dir Directory, in platform-independent notation, to enumerate.
+ * \param c Callback function to notify about search path elements.
+ * \param d Application-defined data passed to callback. Can be NULL.
+ * \return non-zero on success, zero on failure. Use
+ * PHYSFS_getLastErrorCode() to obtain the specific error. If the
+ * callback returns PHYSFS_ENUM_STOP to stop early, this will be
+ * considered success. Callbacks returning PHYSFS_ENUM_ERROR will
+ * make this function return zero and set the error code to
+ * PHYSFS_ERR_APP_CALLBACK.
+ *
+ * \sa PHYSFS_EnumerateCallback
+ * \sa PHYSFS_enumerateFiles
+ */
+PHYSFS_DECL int PHYSFS_enumerate(const char *dir, PHYSFS_EnumerateCallback c,
+ void *d);
+
+
+/**
+ * \fn int PHYSFS_unmount(const char *oldDir)
+ * \brief Remove a directory or archive from the search path.
+ *
+ * This is functionally equivalent to PHYSFS_removeFromSearchPath(), but that
+ * function is deprecated to keep the vocabulary paired with PHYSFS_mount().
+ *
+ * This must be a (case-sensitive) match to a dir or archive already in the
+ * search path, specified in platform-dependent notation.
+ *
+ * This call will fail (and fail to remove from the path) if the element still
+ * has files open in it.
+ *
+ * \warning This function wants the path to the archive or directory that was
+ * mounted (the same string used for the "newDir" argument of
+ * PHYSFS_addToSearchPath or any of the mount functions), not the
+ * path where it is mounted in the tree (the "mountPoint" argument
+ * to any of the mount functions).
+ *
+ * \param oldDir dir/archive to remove.
+ * \return nonzero on success, zero on failure. Use
+ * PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_mount
+ */
+PHYSFS_DECL int PHYSFS_unmount(const char *oldDir);
+
+
+/**
+ * \fn const PHYSFS_Allocator *PHYSFS_getAllocator(void)
+ * \brief Discover the current allocator.
+ *
+ * (This is for limited, hardcore use. If you don't immediately see a need
+ * for it, you can probably ignore this forever.)
+ *
+ * This function exposes the function pointers that make up the currently used
+ * allocator. This can be useful for apps that want to access PhysicsFS's
+ * internal, default allocation routines, as well as for external code that
+ * wants to share the same allocator, even if the application specified their
+ * own.
+ *
+ * This call is only valid between PHYSFS_init() and PHYSFS_deinit() calls;
+ * it will return NULL if the library isn't initialized. As we can't
+ * guarantee the state of the internal allocators unless the library is
+ * initialized, you shouldn't use any allocator returned here after a call
+ * to PHYSFS_deinit().
+ *
+ * Do not call the returned allocator's Init() or Deinit() methods under any
+ * circumstances.
+ *
+ * If you aren't immediately sure what to do with this function, you can
+ * safely ignore it altogether.
+ *
+ * \return Current allocator, as set by PHYSFS_setAllocator(), or PhysicsFS's
+ * internal, default allocator if no application defined allocator
+ * is currently set. Will return NULL if the library is not
+ * initialized.
+ *
+ * \sa PHYSFS_Allocator
+ * \sa PHYSFS_setAllocator
+ */
+PHYSFS_DECL const PHYSFS_Allocator *PHYSFS_getAllocator(void);
+
+
+/**
+ * \enum PHYSFS_FileType
+ * \brief Type of a File
+ *
+ * Possible types of a file.
+ *
+ * \sa PHYSFS_stat
+ */
+typedef enum PHYSFS_FileType
+{
+ PHYSFS_FILETYPE_REGULAR, /**< a normal file */
+ PHYSFS_FILETYPE_DIRECTORY, /**< a directory */
+ PHYSFS_FILETYPE_SYMLINK, /**< a symlink */
+ PHYSFS_FILETYPE_OTHER /**< something completely different like a device */
+} PHYSFS_FileType;
+
+/**
+ * \struct PHYSFS_Stat
+ * \brief Meta data for a file or directory
+ *
+ * Container for various meta data about a file in the virtual file system.
+ * PHYSFS_stat() uses this structure for returning the information. The time
+ * data will be either the number of seconds since the Unix epoch (midnight,
+ * Jan 1, 1970), or -1 if the information isn't available or applicable.
+ * The (filesize) field is measured in bytes.
+ * The (readonly) field tells you whether the archive thinks a file is
+ * not writable, but tends to be only an estimate (for example, your write
+ * dir might overlap with a .zip file, meaning you _can_ successfully open
+ * that path for writing, as it gets created elsewhere.
+ *
+ * \sa PHYSFS_stat
+ * \sa PHYSFS_FileType
+ */
+typedef struct PHYSFS_Stat
+{
+ PHYSFS_sint64 filesize; /**< size in bytes, -1 for non-files and unknown */
+ PHYSFS_sint64 modtime; /**< last modification time */
+ PHYSFS_sint64 createtime; /**< like modtime, but for file creation time */
+ PHYSFS_sint64 accesstime; /**< like modtime, but for file access time */
+ PHYSFS_FileType filetype; /**< File? Directory? Symlink? */
+ int readonly; /**< non-zero if read only, zero if writable. */
+} PHYSFS_Stat;
+
+/**
+ * \fn int PHYSFS_stat(const char *fname, PHYSFS_Stat *stat)
+ * \brief Get various information about a directory or a file.
+ *
+ * Obtain various information about a file or directory from the meta data.
+ *
+ * This function will never follow symbolic links. If you haven't enabled
+ * symlinks with PHYSFS_permitSymbolicLinks(), stat'ing a symlink will be
+ * treated like stat'ing a non-existant file. If symlinks are enabled,
+ * stat'ing a symlink will give you information on the link itself and not
+ * what it points to.
+ *
+ * \param fname filename to check, in platform-indepedent notation.
+ * \param stat pointer to structure to fill in with data about (fname).
+ * \return non-zero on success, zero on failure. On failure, (stat)'s
+ * contents are undefined.
+ *
+ * \sa PHYSFS_Stat
+ */
+PHYSFS_DECL int PHYSFS_stat(const char *fname, PHYSFS_Stat *stat);
+
+
+/**
+ * \fn void PHYSFS_utf8FromUtf16(const PHYSFS_uint16 *src, char *dst, PHYSFS_uint64 len)
+ * \brief Convert a UTF-16 string to a UTF-8 string.
+ *
+ * \warning This function will not report an error if there are invalid UTF-16
+ * sequences in the source string. It will replace them with a '?'
+ * character and continue on.
+ *
+ * UTF-16 strings are 16-bits per character (except some chars, which are
+ * 32-bits): \c TCHAR on Windows, when building with Unicode support. Modern
+ * Windows releases use UTF-16. Windows releases before 2000 used TCHAR, but
+ * only handled UCS-2. UTF-16 _is_ UCS-2, except for the characters that
+ * are 4 bytes, which aren't representable in UCS-2 at all anyhow. If you
+ * aren't sure, you should be using UTF-16 at this point on Windows.
+ *
+ * To ensure that the destination buffer is large enough for the conversion,
+ * please allocate a buffer that is double the size of the source buffer.
+ * UTF-8 never uses more than 32-bits per character, so while it may shrink
+ * a UTF-16 string, it may also expand it.
+ *
+ * Strings that don't fit in the destination buffer will be truncated, but
+ * will always be null-terminated and never have an incomplete UTF-8
+ * sequence at the end. If the buffer length is 0, this function does nothing.
+ *
+ * \param src Null-terminated source string in UTF-16 format.
+ * \param dst Buffer to store converted UTF-8 string.
+ * \param len Size, in bytes, of destination buffer.
+ */
+PHYSFS_DECL void PHYSFS_utf8FromUtf16(const PHYSFS_uint16 *src, char *dst,
+ PHYSFS_uint64 len);
+
+/**
+ * \fn PHYSFS_utf8ToUtf16(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len)
+ * \brief Convert a UTF-8 string to a UTF-16 string.
+ *
+ * \warning This function will not report an error if there are invalid UTF-8
+ * sequences in the source string. It will replace them with a '?'
+ * character and continue on.
+ *
+ * UTF-16 strings are 16-bits per character (except some chars, which are
+ * 32-bits): \c TCHAR on Windows, when building with Unicode support. Modern
+ * Windows releases use UTF-16. Windows releases before 2000 used TCHAR, but
+ * only handled UCS-2. UTF-16 _is_ UCS-2, except for the characters that
+ * are 4 bytes, which aren't representable in UCS-2 at all anyhow. If you
+ * aren't sure, you should be using UTF-16 at this point on Windows.
+ *
+ * To ensure that the destination buffer is large enough for the conversion,
+ * please allocate a buffer that is double the size of the source buffer.
+ * UTF-8 uses from one to four bytes per character, but UTF-16 always uses
+ * two to four, so an entirely low-ASCII string will double in size! The
+ * UTF-16 characters that would take four bytes also take four bytes in UTF-8,
+ * so you don't need to allocate 4x the space just in case: double will do.
+ *
+ * Strings that don't fit in the destination buffer will be truncated, but
+ * will always be null-terminated and never have an incomplete UTF-16
+ * surrogate pair at the end. If the buffer length is 0, this function does
+ * nothing.
+ *
+ * \param src Null-terminated source string in UTF-8 format.
+ * \param dst Buffer to store converted UTF-16 string.
+ * \param len Size, in bytes, of destination buffer.
+ *
+ * \sa PHYSFS_utf8ToUtf16
+ */
+PHYSFS_DECL void PHYSFS_utf8ToUtf16(const char *src, PHYSFS_uint16 *dst,
+ PHYSFS_uint64 len);
+
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_readBytes(PHYSFS_File *handle, void *buffer, PHYSFS_uint64 len)
+ * \brief Read bytes from a PhysicsFS filehandle
+ *
+ * The file must be opened for reading.
+ *
+ * \param handle handle returned from PHYSFS_openRead().
+ * \param buffer buffer of at least (len) bytes to store read data into.
+ * \param len number of bytes being read from (handle).
+ * \return number of bytes read. This may be less than (len); this does not
+ * signify an error, necessarily (a short read may mean EOF).
+ * PHYSFS_getLastErrorCode() can shed light on the reason this might
+ * be < (len), as can PHYSFS_eof(). -1 if complete failure.
+ *
+ * \sa PHYSFS_eof
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_readBytes(PHYSFS_File *handle, void *buffer,
+ PHYSFS_uint64 len);
+
+/**
+ * \fn PHYSFS_sint64 PHYSFS_writeBytes(PHYSFS_File *handle, const void *buffer, PHYSFS_uint64 len)
+ * \brief Write data to a PhysicsFS filehandle
+ *
+ * The file must be opened for writing.
+ *
+ * Please note that while (len) is an unsigned 64-bit integer, you are limited
+ * to 63 bits (9223372036854775807 bytes), so we can return a negative value
+ * on error. If length is greater than 0x7FFFFFFFFFFFFFFF, this function will
+ * immediately fail. For systems without a 64-bit datatype, you are limited
+ * to 31 bits (0x7FFFFFFF, or 2147483647 bytes). We trust most things won't
+ * need to do multiple gigabytes of i/o in one call anyhow, but why limit
+ * things?
+ *
+ * \param handle retval from PHYSFS_openWrite() or PHYSFS_openAppend().
+ * \param buffer buffer of (len) bytes to write to (handle).
+ * \param len number of bytes being written to (handle).
+ * \return number of bytes written. This may be less than (len); in the case
+ * of an error, the system may try to write as many bytes as possible,
+ * so an incomplete write might occur. PHYSFS_getLastErrorCode() can
+ * shed light on the reason this might be < (len). -1 if complete
+ * failure.
+ */
+PHYSFS_DECL PHYSFS_sint64 PHYSFS_writeBytes(PHYSFS_File *handle,
+ const void *buffer,
+ PHYSFS_uint64 len);
+
+
+/**
+ * \struct PHYSFS_Io
+ * \brief An abstract i/o interface.
+ *
+ * \warning This is advanced, hardcore stuff. You don't need this unless you
+ * really know what you're doing. Most apps will not need this.
+ *
+ * Historically, PhysicsFS provided access to the physical filesystem and
+ * archives within that filesystem. However, sometimes you need more power
+ * than this. Perhaps you need to provide an archive that is entirely
+ * contained in RAM, or you need to bridge some other file i/o API to
+ * PhysicsFS, or you need to translate the bits (perhaps you have a
+ * a standard .zip file that's encrypted, and you need to decrypt on the fly
+ * for the unsuspecting zip archiver).
+ *
+ * A PHYSFS_Io is the interface that Archivers use to get archive data.
+ * Historically, this has mapped to file i/o to the physical filesystem, but
+ * as of PhysicsFS 2.1, applications can provide their own i/o implementations
+ * at runtime.
+ *
+ * This interface isn't necessarily a good universal fit for i/o. There are a
+ * few requirements of note:
+ *
+ * - They only do blocking i/o (at least, for now).
+ * - They need to be able to duplicate. If you have a file handle from
+ * fopen(), you need to be able to create a unique clone of it (so we
+ * have two handles to the same file that can both seek/read/etc without
+ * stepping on each other).
+ * - They need to know the size of their entire data set.
+ * - They need to be able to seek and rewind on demand.
+ *
+ * ...in short, you're probably not going to write an HTTP implementation.
+ *
+ * Thread safety: PHYSFS_Io implementations are not guaranteed to be thread
+ * safe in themselves. Under the hood where PhysicsFS uses them, the library
+ * provides its own locks. If you plan to use them directly from separate
+ * threads, you should either use mutexes to protect them, or don't use the
+ * same PHYSFS_Io from two threads at the same time.
+ *
+ * \sa PHYSFS_mountIo
+ */
+typedef struct PHYSFS_Io
+{
+ /**
+ * \brief Binary compatibility information.
+ *
+ * This must be set to zero at this time. Future versions of this
+ * struct will increment this field, so we know what a given
+ * implementation supports. We'll presumably keep supporting older
+ * versions as we offer new features, though.
+ */
+ PHYSFS_uint32 version;
+
+ /**
+ * \brief Instance data for this struct.
+ *
+ * Each instance has a pointer associated with it that can be used to
+ * store anything it likes. This pointer is per-instance of the stream,
+ * so presumably it will change when calling duplicate(). This can be
+ * deallocated during the destroy() method.
+ */
+ void *opaque;
+
+ /**
+ * \brief Read more data.
+ *
+ * Read (len) bytes from the interface, at the current i/o position, and
+ * store them in (buffer). The current i/o position should move ahead
+ * by the number of bytes successfully read.
+ *
+ * You don't have to implement this; set it to NULL if not implemented.
+ * This will only be used if the file is opened for reading. If set to
+ * NULL, a default implementation that immediately reports failure will
+ * be used.
+ *
+ * \param io The i/o instance to read from.
+ * \param buf The buffer to store data into. It must be at least
+ * (len) bytes long and can't be NULL.
+ * \param len The number of bytes to read from the interface.
+ * \return number of bytes read from file, 0 on EOF, -1 if complete
+ * failure.
+ */
+ PHYSFS_sint64 (*read)(struct PHYSFS_Io *io, void *buf, PHYSFS_uint64 len);
+
+ /**
+ * \brief Write more data.
+ *
+ * Write (len) bytes from (buffer) to the interface at the current i/o
+ * position. The current i/o position should move ahead by the number of
+ * bytes successfully written.
+ *
+ * You don't have to implement this; set it to NULL if not implemented.
+ * This will only be used if the file is opened for writing. If set to
+ * NULL, a default implementation that immediately reports failure will
+ * be used.
+ *
+ * You are allowed to buffer; a write can succeed here and then later
+ * fail when flushing. Note that PHYSFS_setBuffer() may be operating a
+ * level above your i/o, so you should usually not implement your
+ * own buffering routines.
+ *
+ * \param io The i/o instance to write to.
+ * \param buffer The buffer to read data from. It must be at least
+ * (len) bytes long and can't be NULL.
+ * \param len The number of bytes to read from (buffer).
+ * \return number of bytes written to file, -1 if complete failure.
+ */
+ PHYSFS_sint64 (*write)(struct PHYSFS_Io *io, const void *buffer,
+ PHYSFS_uint64 len);
+
+ /**
+ * \brief Move i/o position to a given byte offset from start.
+ *
+ * This method moves the i/o position, so the next read/write will
+ * be of the byte at (offset) offset. Seeks past the end of file should
+ * be treated as an error condition.
+ *
+ * \param io The i/o instance to seek.
+ * \param offset The new byte offset for the i/o position.
+ * \return non-zero on success, zero on error.
+ */
+ int (*seek)(struct PHYSFS_Io *io, PHYSFS_uint64 offset);
+
+ /**
+ * \brief Report current i/o position.
+ *
+ * Return bytes offset, or -1 if you aren't able to determine. A failure
+ * will almost certainly be fatal to further use of this stream, so you
+ * may not leave this unimplemented.
+ *
+ * \param io The i/o instance to query.
+ * \return The current byte offset for the i/o position, -1 if unknown.
+ */
+ PHYSFS_sint64 (*tell)(struct PHYSFS_Io *io);
+
+ /**
+ * \brief Determine size of the i/o instance's dataset.
+ *
+ * Return number of bytes available in the file, or -1 if you
+ * aren't able to determine. A failure will almost certainly be fatal
+ * to further use of this stream, so you may not leave this unimplemented.
+ *
+ * \param io The i/o instance to query.
+ * \return Total size, in bytes, of the dataset.
+ */
+ PHYSFS_sint64 (*length)(struct PHYSFS_Io *io);
+
+ /**
+ * \brief Duplicate this i/o instance.
+ *
+ * This needs to result in a full copy of this PHYSFS_Io, that can live
+ * completely independently. The copy needs to be able to perform all
+ * its operations without altering the original, including either object
+ * being destroyed separately (so, for example: they can't share a file
+ * handle; they each need their own).
+ *
+ * If you can't duplicate a handle, it's legal to return NULL, but you
+ * almost certainly need this functionality if you want to use this to
+ * PHYSFS_Io to back an archive.
+ *
+ * \param io The i/o instance to duplicate.
+ * \return A new value for a stream's (opaque) field, or NULL on error.
+ */
+ struct PHYSFS_Io *(*duplicate)(struct PHYSFS_Io *io);
+
+ /**
+ * \brief Flush resources to media, or wherever.
+ *
+ * This is the chance to report failure for writes that had claimed
+ * success earlier, but still had a chance to actually fail. This method
+ * can be NULL if flushing isn't necessary.
+ *
+ * This function may be called before destroy(), as it can report failure
+ * and destroy() can not. It may be called at other times, too.
+ *
+ * \param io The i/o instance to flush.
+ * \return Zero on error, non-zero on success.
+ */
+ int (*flush)(struct PHYSFS_Io *io);
+
+ /**
+ * \brief Cleanup and deallocate i/o instance.
+ *
+ * Free associated resources, including (opaque) if applicable.
+ *
+ * This function must always succeed: as such, it returns void. The
+ * system may call your flush() method before this. You may report
+ * failure there if necessary. This method may still be called if
+ * flush() fails, in which case you'll have to abandon unflushed data
+ * and other failing conditions and clean up.
+ *
+ * Once this method is called for a given instance, the system will assume
+ * it is unsafe to touch that instance again and will discard any
+ * references to it.
+ *
+ * \param s The i/o instance to destroy.
+ */
+ void (*destroy)(struct PHYSFS_Io *io);
+} PHYSFS_Io;
+
+
+/**
+ * \fn int PHYSFS_mountIo(PHYSFS_Io *io, const char *newDir, const char *mountPoint, int appendToPath)
+ * \brief Add an archive, built on a PHYSFS_Io, to the search path.
+ *
+ * \warning Unless you have some special, low-level need, you should be using
+ * PHYSFS_mount() instead of this.
+ *
+ * This function operates just like PHYSFS_mount(), but takes a PHYSFS_Io
+ * instead of a pathname. Behind the scenes, PHYSFS_mount() calls this
+ * function with a physical-filesystem-based PHYSFS_Io.
+ *
+ * (newDir) must be a unique string to identify this archive. It is used
+ * to optimize archiver selection (if you name it XXXXX.zip, we might try
+ * the ZIP archiver first, for example, or directly choose an archiver that
+ * can only trust the data is valid by filename extension). It doesn't
+ * need to refer to a real file at all. If the filename extension isn't
+ * helpful, the system will try every archiver until one works or none
+ * of them do. This filename must be unique, as the system won't allow you
+ * to have two archives with the same name.
+ *
+ * (io) must remain until the archive is unmounted. When the archive is
+ * unmounted, the system will call (io)->destroy(io), which will give you
+ * a chance to free your resources.
+ *
+ * If this function fails, (io)->destroy(io) is not called.
+ *
+ * \param io i/o instance for archive to add to the path.
+ * \param newDir Filename that can represent this stream.
+ * \param mountPoint Location in the interpolated tree that this archive
+ * will be "mounted", in platform-independent notation.
+ * NULL or "" is equivalent to "/".
+ * \param appendToPath nonzero to append to search path, zero to prepend.
+ * \return nonzero if added to path, zero on failure (bogus archive, stream
+ * i/o issue, etc). Use PHYSFS_getLastErrorCode() to obtain
+ * the specific error.
+ *
+ * \sa PHYSFS_unmount
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_getMountPoint
+ */
+PHYSFS_DECL int PHYSFS_mountIo(PHYSFS_Io *io, const char *newDir,
+ const char *mountPoint, int appendToPath);
+
+
+/**
+ * \fn int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len, void (*del)(void *), const char *newDir, const char *mountPoint, int appendToPath)
+ * \brief Add an archive, contained in a memory buffer, to the search path.
+ *
+ * \warning Unless you have some special, low-level need, you should be using
+ * PHYSFS_mount() instead of this.
+ *
+ * This function operates just like PHYSFS_mount(), but takes a memory buffer
+ * instead of a pathname. This buffer contains all the data of the archive,
+ * and is used instead of a real file in the physical filesystem.
+ *
+ * (newDir) must be a unique string to identify this archive. It is used
+ * to optimize archiver selection (if you name it XXXXX.zip, we might try
+ * the ZIP archiver first, for example, or directly choose an archiver that
+ * can only trust the data is valid by filename extension). It doesn't
+ * need to refer to a real file at all. If the filename extension isn't
+ * helpful, the system will try every archiver until one works or none
+ * of them do. This filename must be unique, as the system won't allow you
+ * to have two archives with the same name.
+ *
+ * (ptr) must remain until the archive is unmounted. When the archive is
+ * unmounted, the system will call (del)(ptr), which will notify you that
+ * the system is done with the buffer, and give you a chance to free your
+ * resources. (del) can be NULL, in which case the system will make no
+ * attempt to free the buffer.
+ *
+ * If this function fails, (del) is not called.
+ *
+ * \param buf Address of the memory buffer containing the archive data.
+ * \param len Size of memory buffer, in bytes.
+ * \param del A callback that triggers upon unmount. Can be NULL.
+ * \param newDir Filename that can represent this stream.
+ * \param mountPoint Location in the interpolated tree that this archive
+ * will be "mounted", in platform-independent notation.
+ * NULL or "" is equivalent to "/".
+ * \param appendToPath nonzero to append to search path, zero to prepend.
+ * \return nonzero if added to path, zero on failure (bogus archive, etc).
+ * Use PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_unmount
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_getMountPoint
+ */
+PHYSFS_DECL int PHYSFS_mountMemory(const void *buf, PHYSFS_uint64 len,
+ void (*del)(void *), const char *newDir,
+ const char *mountPoint, int appendToPath);
+
+
+/**
+ * \fn int PHYSFS_mountHandle(PHYSFS_File *file, const char *newDir, const char *mountPoint, int appendToPath)
+ * \brief Add an archive, contained in a PHYSFS_File handle, to the search path.
+ *
+ * \warning Unless you have some special, low-level need, you should be using
+ * PHYSFS_mount() instead of this.
+ *
+ * \warning Archives-in-archives may be very slow! While a PHYSFS_File can
+ * seek even when the data is compressed, it may do so by rewinding
+ * to the start and decompressing everything before the seek point.
+ * Normal archive usage may do a lot of seeking behind the scenes.
+ * As such, you might find normal archive usage extremely painful
+ * if mounted this way. Plan accordingly: if you, say, have a
+ * self-extracting .zip file, and want to mount something in it,
+ * compress the contents of the inner archive and make sure the outer
+ * .zip file doesn't compress the inner archive too.
+ *
+ * This function operates just like PHYSFS_mount(), but takes a PHYSFS_File
+ * handle instead of a pathname. This handle contains all the data of the
+ * archive, and is used instead of a real file in the physical filesystem.
+ * The PHYSFS_File may be backed by a real file in the physical filesystem,
+ * but isn't necessarily. The most popular use for this is likely to mount
+ * archives stored inside other archives.
+ *
+ * (newDir) must be a unique string to identify this archive. It is used
+ * to optimize archiver selection (if you name it XXXXX.zip, we might try
+ * the ZIP archiver first, for example, or directly choose an archiver that
+ * can only trust the data is valid by filename extension). It doesn't
+ * need to refer to a real file at all. If the filename extension isn't
+ * helpful, the system will try every archiver until one works or none
+ * of them do. This filename must be unique, as the system won't allow you
+ * to have two archives with the same name.
+ *
+ * (file) must remain until the archive is unmounted. When the archive is
+ * unmounted, the system will call PHYSFS_close(file). If you need this
+ * handle to survive, you will have to wrap this in a PHYSFS_Io and use
+ * PHYSFS_mountIo() instead.
+ *
+ * If this function fails, PHYSFS_close(file) is not called.
+ *
+ * \param file The PHYSFS_File handle containing archive data.
+ * \param newDir Filename that can represent this stream.
+ * \param mountPoint Location in the interpolated tree that this archive
+ * will be "mounted", in platform-independent notation.
+ * NULL or "" is equivalent to "/".
+ * \param appendToPath nonzero to append to search path, zero to prepend.
+ * \return nonzero if added to path, zero on failure (bogus archive, etc).
+ * Use PHYSFS_getLastErrorCode() to obtain the specific error.
+ *
+ * \sa PHYSFS_unmount
+ * \sa PHYSFS_getSearchPath
+ * \sa PHYSFS_getMountPoint
+ */
+PHYSFS_DECL int PHYSFS_mountHandle(PHYSFS_File *file, const char *newDir,
+ const char *mountPoint, int appendToPath);
+
+
+/**
+ * \enum PHYSFS_ErrorCode
+ * \brief Values that represent specific causes of failure.
+ *
+ * Most of the time, you should only concern yourself with whether a given
+ * operation failed or not, but there may be occasions where you plan to
+ * handle a specific failure case gracefully, so we provide specific error
+ * codes.
+ *
+ * Most of these errors are a little vague, and most aren't things you can
+ * fix...if there's a permission error, for example, all you can really do
+ * is pass that information on to the user and let them figure out how to
+ * handle it. In most these cases, your program should only care that it
+ * failed to accomplish its goals, and not care specifically why.
+ *
+ * \sa PHYSFS_getLastErrorCode
+ * \sa PHYSFS_getErrorByCode
+ */
+typedef enum PHYSFS_ErrorCode
+{
+ PHYSFS_ERR_OK, /**< Success; no error. */
+ PHYSFS_ERR_OTHER_ERROR, /**< Error not otherwise covered here. */
+ PHYSFS_ERR_OUT_OF_MEMORY, /**< Memory allocation failed. */
+ PHYSFS_ERR_NOT_INITIALIZED, /**< PhysicsFS is not initialized. */
+ PHYSFS_ERR_IS_INITIALIZED, /**< PhysicsFS is already initialized. */
+ PHYSFS_ERR_ARGV0_IS_NULL, /**< Needed argv[0], but it is NULL. */
+ PHYSFS_ERR_UNSUPPORTED, /**< Operation or feature unsupported. */
+ PHYSFS_ERR_PAST_EOF, /**< Attempted to access past end of file. */
+ PHYSFS_ERR_FILES_STILL_OPEN, /**< Files still open. */
+ PHYSFS_ERR_INVALID_ARGUMENT, /**< Bad parameter passed to an function. */
+ PHYSFS_ERR_NOT_MOUNTED, /**< Requested archive/dir not mounted. */
+ PHYSFS_ERR_NOT_FOUND, /**< File (or whatever) not found. */
+ PHYSFS_ERR_SYMLINK_FORBIDDEN,/**< Symlink seen when not permitted. */
+ PHYSFS_ERR_NO_WRITE_DIR, /**< No write dir has been specified. */
+ PHYSFS_ERR_OPEN_FOR_READING, /**< Wrote to a file opened for reading. */
+ PHYSFS_ERR_OPEN_FOR_WRITING, /**< Read from a file opened for writing. */
+ PHYSFS_ERR_NOT_A_FILE, /**< Needed a file, got a directory (etc). */
+ PHYSFS_ERR_READ_ONLY, /**< Wrote to a read-only filesystem. */
+ PHYSFS_ERR_CORRUPT, /**< Corrupted data encountered. */
+ PHYSFS_ERR_SYMLINK_LOOP, /**< Infinite symbolic link loop. */
+ PHYSFS_ERR_IO, /**< i/o error (hardware failure, etc). */
+ PHYSFS_ERR_PERMISSION, /**< Permission denied. */
+ PHYSFS_ERR_NO_SPACE, /**< No space (disk full, over quota, etc) */
+ PHYSFS_ERR_BAD_FILENAME, /**< Filename is bogus/insecure. */
+ PHYSFS_ERR_BUSY, /**< Tried to modify a file the OS needs. */
+ PHYSFS_ERR_DIR_NOT_EMPTY, /**< Tried to delete dir with files in it. */
+ PHYSFS_ERR_OS_ERROR, /**< Unspecified OS-level error. */
+ PHYSFS_ERR_DUPLICATE, /**< Duplicate entry. */
+ PHYSFS_ERR_BAD_PASSWORD, /**< Bad password. */
+ PHYSFS_ERR_APP_CALLBACK /**< Application callback reported error. */
+} PHYSFS_ErrorCode;
+
+
+/**
+ * \fn PHYSFS_ErrorCode PHYSFS_getLastErrorCode(void)
+ * \brief Get machine-readable error information.
+ *
+ * Get the last PhysicsFS error message as an integer value. This will return
+ * PHYSFS_ERR_OK if there's been no error since the last call to this
+ * function. Each thread has a unique error state associated with it, but
+ * each time a new error message is set, it will overwrite the previous one
+ * associated with that thread. It is safe to call this function at anytime,
+ * even before PHYSFS_init().
+ *
+ * PHYSFS_getLastError() and PHYSFS_getLastErrorCode() both reset the same
+ * thread-specific error state. Calling one will wipe out the other's
+ * data. If you need both, call PHYSFS_getLastErrorCode(), then pass that
+ * value to PHYSFS_getErrorByCode().
+ *
+ * Generally, applications should only concern themselves with whether a
+ * given function failed; however, if you require more specifics, you can
+ * try this function to glean information, if there's some specific problem
+ * you're expecting and plan to handle. But with most things that involve
+ * file systems, the best course of action is usually to give up, report the
+ * problem to the user, and let them figure out what should be done about it.
+ * For that, you might prefer PHYSFS_getErrorByCode() instead.
+ *
+ * \return Enumeration value that represents last reported error.
+ *
+ * \sa PHYSFS_getErrorByCode
+ */
+PHYSFS_DECL PHYSFS_ErrorCode PHYSFS_getLastErrorCode(void);
+
+
+/**
+ * \fn const char *PHYSFS_getErrorByCode(PHYSFS_ErrorCode code)
+ * \brief Get human-readable description string for a given error code.
+ *
+ * Get a static string, in UTF-8 format, that represents an English
+ * description of a given error code.
+ *
+ * This string is guaranteed to never change (although we may add new strings
+ * for new error codes in later versions of PhysicsFS), so you can use it
+ * for keying a localization dictionary.
+ *
+ * It is safe to call this function at anytime, even before PHYSFS_init().
+ *
+ * These strings are meant to be passed on directly to the user.
+ * Generally, applications should only concern themselves with whether a
+ * given function failed, but not care about the specifics much.
+ *
+ * Do not attempt to free the returned strings; they are read-only and you
+ * don't own their memory pages.
+ *
+ * \param code Error code to convert to a string.
+ * \return READ ONLY string of requested error message, NULL if this
+ * is not a valid PhysicsFS error code. Always check for NULL if
+ * you might be looking up an error code that didn't exist in an
+ * earlier version of PhysicsFS.
+ *
+ * \sa PHYSFS_getLastErrorCode
+ */
+PHYSFS_DECL const char *PHYSFS_getErrorByCode(PHYSFS_ErrorCode code);
+
+/**
+ * \fn void PHYSFS_setErrorCode(PHYSFS_ErrorCode code)
+ * \brief Set the current thread's error code.
+ *
+ * This lets you set the value that will be returned by the next call to
+ * PHYSFS_getLastErrorCode(). This will replace any existing error code,
+ * whether set by your application or internally by PhysicsFS.
+ *
+ * Error codes are stored per-thread; what you set here will not be
+ * accessible to another thread.
+ *
+ * Any call into PhysicsFS may change the current error code, so any code you
+ * set here is somewhat fragile, and thus you shouldn't build any serious
+ * error reporting framework on this function. The primary goal of this
+ * function is to allow PHYSFS_Io implementations to set the error state,
+ * which generally will be passed back to your application when PhysicsFS
+ * makes a PHYSFS_Io call that fails internally.
+ *
+ * This function doesn't care if the error code is a value known to PhysicsFS
+ * or not (but PHYSFS_getErrorByCode() will return NULL for unknown values).
+ * The value will be reported unmolested by PHYSFS_getLastErrorCode().
+ *
+ * \param code Error code to become the current thread's new error state.
+ *
+ * \sa PHYSFS_getLastErrorCode
+ * \sa PHYSFS_getErrorByCode
+ */
+PHYSFS_DECL void PHYSFS_setErrorCode(PHYSFS_ErrorCode code);
+
+
+/**
+ * \fn const char *PHYSFS_getPrefDir(const char *org, const char *app)
+ * \brief Get the user-and-app-specific path where files can be written.
+ *
+ * Helper function.
+ *
+ * Get the "pref dir". This is meant to be where users can write personal
+ * files (preferences and save games, etc) that are specific to your
+ * application. This directory is unique per user, per application.
+ *
+ * This function will decide the appropriate location in the native filesystem,
+ * create the directory if necessary, and return a string in
+ * platform-dependent notation, suitable for passing to PHYSFS_setWriteDir().
+ *
+ * On Windows, this might look like:
+ * "C:\\Users\\bob\\AppData\\Roaming\\My Company\\My Program Name"
+ *
+ * On Linux, this might look like:
+ * "/home/bob/.local/share/My Program Name"
+ *
+ * On Mac OS X, this might look like:
+ * "/Users/bob/Library/Application Support/My Program Name"
+ *
+ * (etc.)
+ *
+ * You should probably use the pref dir for your write dir, and also put it
+ * near the beginning of your search path. Older versions of PhysicsFS
+ * offered only PHYSFS_getUserDir() and left you to figure out where the
+ * files should go under that tree. This finds the correct location
+ * for whatever platform, which not only changes between operating systems,
+ * but also versions of the same operating system.
+ *
+ * You specify the name of your organization (if it's not a real organization,
+ * your name or an Internet domain you own might do) and the name of your
+ * application. These should be proper names.
+ *
+ * Both the (org) and (app) strings may become part of a directory name, so
+ * please follow these rules:
+ *
+ * - Try to use the same org string (including case-sensitivity) for
+ * all your applications that use this function.
+ * - Always use a unique app string for each one, and make sure it never
+ * changes for an app once you've decided on it.
+ * - Unicode characters are legal, as long as it's UTF-8 encoded, but...
+ * - ...only use letters, numbers, and spaces. Avoid punctuation like
+ * "Game Name 2: Bad Guy's Revenge!" ... "Game Name 2" is sufficient.
+ *
+ * The pointer returned by this function remains valid until you call this
+ * function again, or call PHYSFS_deinit(). This is not necessarily a fast
+ * call, though, so you should call this once at startup and copy the string
+ * if you need it.
+ *
+ * You should assume the path returned by this function is the only safe
+ * place to write files (and that PHYSFS_getUserDir() and PHYSFS_getBaseDir(),
+ * while they might be writable, or even parents of the returned path, aren't
+ * where you should be writing things).
+ *
+ * \param org The name of your organization.
+ * \param app The name of your application.
+ * \return READ ONLY string of user dir in platform-dependent notation. NULL
+ * if there's a problem (creating directory failed, etc).
+ *
+ * \sa PHYSFS_getBaseDir
+ * \sa PHYSFS_getUserDir
+ */
+PHYSFS_DECL const char *PHYSFS_getPrefDir(const char *org, const char *app);
+
+
+/**
+ * \struct PHYSFS_Archiver
+ * \brief Abstract interface to provide support for user-defined archives.
+ *
+ * \warning This is advanced, hardcore stuff. You don't need this unless you
+ * really know what you're doing. Most apps will not need this.
+ *
+ * Historically, PhysicsFS provided a means to mount various archive file
+ * formats, and physical directories in the native filesystem. However,
+ * applications have been limited to the file formats provided by the
+ * library. This interface allows an application to provide their own
+ * archive file types.
+ *
+ * Conceptually, a PHYSFS_Archiver provides directory entries, while
+ * PHYSFS_Io provides data streams for those directory entries. The most
+ * obvious use of PHYSFS_Archiver is to provide support for an archive
+ * file type that isn't provided by PhysicsFS directly: perhaps some
+ * proprietary format that only your application needs to understand.
+ *
+ * Internally, all the built-in archive support uses this interface, so the
+ * best examples for building a PHYSFS_Archiver is the source code to
+ * PhysicsFS itself.
+ *
+ * An archiver is added to the system with PHYSFS_registerArchiver(), and then
+ * it will be available for use automatically with PHYSFS_mount(); if a
+ * given archive can be handled with your archiver, it will be given control
+ * as appropriate.
+ *
+ * These methods deal with dir handles. You have one instance of your
+ * archiver, and it generates a unique, opaque handle for each opened
+ * archive in its openArchive() method. Since the lifetime of an Archiver
+ * (not an archive) is generally the entire lifetime of the process, and it's
+ * assumed to be a singleton, we do not provide any instance data for the
+ * archiver itself; the app can just use some static variables if necessary.
+ *
+ * Symlinks should always be followed (except in stat()); PhysicsFS will
+ * use the stat() method to check for symlinks and make a judgement on
+ * whether to continue to call other methods based on that.
+ *
+ * Archivers, when necessary, should set the PhysicsFS error state with
+ * PHYSFS_setErrorCode() before returning. PhysicsFS will pass these errors
+ * back to the application unmolested in most cases.
+ *
+ * Thread safety: PHYSFS_Archiver implementations are not guaranteed to be
+ * thread safe in themselves. PhysicsFS provides thread safety when it calls
+ * into a given archiver inside the library, but it does not promise that
+ * using the same PHYSFS_File from two threads at once is thread-safe; as
+ * such, your PHYSFS_Archiver can assume that locking is handled for you
+ * so long as the PHYSFS_Io you return from PHYSFS_open* doesn't change any
+ * of your Archiver state, as the PHYSFS_Io won't be as aggressively
+ * protected.
+ *
+ * \sa PHYSFS_registerArchiver
+ * \sa PHYSFS_deregisterArchiver
+ * \sa PHYSFS_supportedArchiveTypes
+ */
+typedef struct PHYSFS_Archiver
+{
+ /**
+ * \brief Binary compatibility information.
+ *
+ * This must be set to zero at this time. Future versions of this
+ * struct will increment this field, so we know what a given
+ * implementation supports. We'll presumably keep supporting older
+ * versions as we offer new features, though.
+ */
+ PHYSFS_uint32 version;
+
+ /**
+ * \brief Basic info about this archiver.
+ *
+ * This is used to identify your archive, and is returned in
+ * PHYSFS_supportedArchiveTypes().
+ */
+ PHYSFS_ArchiveInfo info;
+
+ /**
+ * \brief Open an archive provided by (io).
+ *
+ * This is where resources are allocated and data is parsed when mounting
+ * an archive.
+ * (name) is a filename associated with (io), but doesn't necessarily
+ * map to anything, let alone a real filename. This possibly-
+ * meaningless name is in platform-dependent notation.
+ * (forWrite) is non-zero if this is to be used for
+ * the write directory, and zero if this is to be used for an
+ * element of the search path.
+ * (claimed) should be set to 1 if this is definitely an archive your
+ * archiver implementation can handle, even if it fails. We use to
+ * decide if we should stop trying other archivers if you fail to open
+ * it. For example: the .zip archiver will set this to 1 for something
+ * that's got a .zip file signature, even if it failed because the file
+ * was also truncated. No sense in trying other archivers here, we
+ * already tried to handle it with the appropriate implementation!.
+ * Return NULL on failure and set (claimed) appropriately. If no archiver
+ * opened the archive or set (claimed), PHYSFS_mount() will report
+ * PHYSFS_ERR_UNSUPPORTED. Otherwise, it will report the error from the
+ * archiver that claimed the data through (claimed).
+ * Return non-NULL on success. The pointer returned will be
+ * passed as the "opaque" parameter for later calls.
+ */
+ void *(*openArchive)(PHYSFS_Io *io, const char *name,
+ int forWrite, int *claimed);
+
+ /**
+ * \brief List all files in (dirname).
+ *
+ * Each file is passed to (cb), where a copy is made if appropriate, so
+ * you can dispose of it upon return from the callback. (dirname) is in
+ * platform-independent notation.
+ * If you have a failure, call PHYSFS_SetErrorCode() with whatever code
+ * seem appropriate and return PHYSFS_ENUM_ERROR.
+ * If the callback returns PHYSFS_ENUM_ERROR, please call
+ * PHYSFS_SetErrorCode(PHYSFS_ERR_APP_CALLBACK) and then return
+ * PHYSFS_ENUM_ERROR as well. Don't call the callback again in any
+ * circumstances.
+ * If the callback returns PHYSFS_ENUM_STOP, stop enumerating and return
+ * PHYSFS_ENUM_STOP as well. Don't call the callback again in any
+ * circumstances. Don't set an error code in this case.
+ * Callbacks are only supposed to return a value from
+ * PHYSFS_EnumerateCallbackResult. Any other result has undefined
+ * behavior.
+ * As long as the callback returned PHYSFS_ENUM_OK and you haven't
+ * experienced any errors of your own, keep enumerating until you're done
+ * and then return PHYSFS_ENUM_OK without setting an error code.
+ *
+ * \warning PHYSFS_enumerate returns zero or non-zero (success or failure),
+ * so be aware this function pointer returns different values!
+ */
+ PHYSFS_EnumerateCallbackResult (*enumerate)(void *opaque,
+ const char *dirname, PHYSFS_EnumerateCallback cb,
+ const char *origdir, void *callbackdata);
+
+ /**
+ * \brief Open a file in this archive for reading.
+ *
+ * This filename, (fnm), is in platform-independent notation.
+ * Fail if the file does not exist.
+ * Returns NULL on failure, and calls PHYSFS_setErrorCode().
+ * Returns non-NULL on success. The pointer returned will be
+ * passed as the "opaque" parameter for later file calls.
+ */
+ PHYSFS_Io *(*openRead)(void *opaque, const char *fnm);
+
+ /**
+ * \brief Open a file in this archive for writing.
+ *
+ * If the file does not exist, it should be created. If it exists,
+ * it should be truncated to zero bytes. The writing offset should
+ * be the start of the file.
+ * If the archive is read-only, this operation should fail.
+ * This filename is in platform-independent notation.
+ * Returns NULL on failure, and calls PHYSFS_setErrorCode().
+ * Returns non-NULL on success. The pointer returned will be
+ * passed as the "opaque" parameter for later file calls.
+ */
+ PHYSFS_Io *(*openWrite)(void *opaque, const char *filename);
+
+ /**
+ * \brief Open a file in this archive for appending.
+ *
+ * If the file does not exist, it should be created. The writing
+ * offset should be the end of the file.
+ * If the archive is read-only, this operation should fail.
+ * This filename is in platform-independent notation.
+ * Returns NULL on failure, and calls PHYSFS_setErrorCode().
+ * Returns non-NULL on success. The pointer returned will be
+ * passed as the "opaque" parameter for later file calls.
+ */
+ PHYSFS_Io *(*openAppend)(void *opaque, const char *filename);
+
+ /**
+ * \brief Delete a file or directory in the archive.
+ *
+ * This same call is used for both files and directories; there is not a
+ * separate rmdir() call. Directories are only meant to be removed if
+ * they are empty.
+ * If the archive is read-only, this operation should fail.
+ *
+ * Return non-zero on success, zero on failure.
+ * This filename is in platform-independent notation.
+ * On failure, call PHYSFS_setErrorCode().
+ */
+ int (*remove)(void *opaque, const char *filename);
+
+ /**
+ * \brief Create a directory in the archive.
+ *
+ * If the application is trying to make multiple dirs, PhysicsFS
+ * will split them up into multiple calls before passing them to
+ * your driver.
+ * If the archive is read-only, this operation should fail.
+ * Return non-zero on success, zero on failure.
+ * This filename is in platform-independent notation.
+ * On failure, call PHYSFS_setErrorCode().
+ */
+ int (*mkdir)(void *opaque, const char *filename);
+
+ /**
+ * \brief Obtain basic file metadata.
+ *
+ * On success, fill in all the fields in (stat), using
+ * reasonable defaults for fields that apply to your archive.
+ *
+ * Returns non-zero on success, zero on failure.
+ * This filename is in platform-independent notation.
+ * On failure, call PHYSFS_setErrorCode().
+ */
+ int (*stat)(void *opaque, const char *fn, PHYSFS_Stat *stat);
+
+ /**
+ * \brief Destruct a previously-opened archive.
+ *
+ * Close this archive, and free any associated memory,
+ * including the original PHYSFS_Io and (opaque) itself, if
+ * applicable. Implementation can assume that it won't be called if
+ * there are still files open from this archive.
+ */
+ void (*closeArchive)(void *opaque);
+} PHYSFS_Archiver;
+
+/**
+ * \fn int PHYSFS_registerArchiver(const PHYSFS_Archiver *archiver)
+ * \brief Add a new archiver to the system.
+ *
+ * \warning This is advanced, hardcore stuff. You don't need this unless you
+ * really know what you're doing. Most apps will not need this.
+ *
+ * If you want to provide your own archiver (for example, a custom archive
+ * file format, or some virtual thing you want to make look like a filesystem
+ * that you can access through the usual PhysicsFS APIs), this is where you
+ * start. Once an archiver is successfully registered, then you can use
+ * PHYSFS_mount() to add archives that your archiver supports to the
+ * search path, or perhaps use it as the write dir. Internally, PhysicsFS
+ * uses this function to register its own built-in archivers, like .zip
+ * support, etc.
+ *
+ * You may not have two archivers that handle the same extension. If you are
+ * going to have a clash, you can deregister the other archiver (including
+ * built-in ones) with PHYSFS_deregisterArchiver().
+ *
+ * The data in (archiver) is copied; you may free this pointer when this
+ * function returns.
+ *
+ * Once this function returns successfully, PhysicsFS will be able to support
+ * archives of this type until you deregister the archiver again.
+ *
+ * \param archiver The archiver to register.
+ * \return Zero on error, non-zero on success.
+ *
+ * \sa PHYSFS_Archiver
+ * \sa PHYSFS_deregisterArchiver
+ */
+PHYSFS_DECL int PHYSFS_registerArchiver(const PHYSFS_Archiver *archiver);
+
+/**
+ * \fn int PHYSFS_deregisterArchiver(const char *ext)
+ * \brief Remove an archiver from the system.
+ *
+ * If for some reason, you only need your previously-registered archiver to
+ * live for a portion of your app's lifetime, you can remove it from the
+ * system once you're done with it through this function.
+ *
+ * This fails if there are any archives still open that use this archiver.
+ *
+ * This function can also remove internally-supplied archivers, like .zip
+ * support or whatnot. This could be useful in some situations, like
+ * disabling support for them outright or overriding them with your own
+ * implementation. Once an internal archiver is disabled like this,
+ * PhysicsFS provides no mechanism to recover them, short of calling
+ * PHYSFS_deinit() and PHYSFS_init() again.
+ *
+ * PHYSFS_deinit() will automatically deregister all archivers, so you don't
+ * need to explicitly deregister yours if you otherwise shut down cleanly.
+ *
+ * \param ext Filename extension that the archiver handles.
+ * \return Zero on error, non-zero on success.
+ *
+ * \sa PHYSFS_Archiver
+ * \sa PHYSFS_registerArchiver
+ */
+PHYSFS_DECL int PHYSFS_deregisterArchiver(const char *ext);
+
+
+/* Everything above this line is part of the PhysicsFS 2.1 API. */
+
+
+/**
+ * \fn int PHYSFS_setRoot(const char *archive, const char *subdir)
+ * \brief Make a subdirectory of an archive its root directory.
+ *
+ * This lets you narrow down the accessible files in a specific archive. For
+ * example, if you have x.zip with a file in y/z.txt, mounted to /a, if you
+ * call PHYSFS_setRoot("x.zip", "/y"), then the call
+ * PHYSFS_openRead("/a/z.txt") will succeed.
+ *
+ * You can change an archive's root at any time, altering the interpolated
+ * file tree (depending on where paths shift, a different archive may be
+ * providing various files). If you set the root to NULL or "/", the
+ * archive will be treated as if no special root was set (as if the archive
+ * was just mounted normally).
+ *
+ * Changing the root only affects future operations on pathnames; a file
+ * that was opened from a path that changed due to a setRoot will not be
+ * affected.
+ *
+ * Setting a new root is not limited to archives in the search path; you may
+ * set one on the write dir, too, which might be useful if you have files
+ * open for write and thus can't change the write dir at the moment.
+ *
+ * It is not an error to set a subdirectory that does not exist to be the
+ * root of an archive; however, no files will be visible in this case. If
+ * the missing directories end up getting created (a mkdir to the physical
+ * filesystem, etc) then this will be reflected in the interpolated tree.
+ *
+ * \param archive dir/archive on which to change root.
+ * \param subdir new subdirectory to make the root of this archive.
+ * \return nonzero on success, zero on failure. Use
+ * PHYSFS_getLastErrorCode() to obtain the specific error.
+ */
+PHYSFS_DECL int PHYSFS_setRoot(const char *archive, const char *subdir);
+
+
+/* Everything above this line is part of the PhysicsFS 3.1 API. */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !defined _INCLUDE_PHYSFS_H_ */
+
+/* end of physfs.h ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_7z.c b/third-party/physfs/src/physfs_archiver_7z.c
new file mode 100644
index 0000000..44be3c9
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_7z.c
@@ -0,0 +1,433 @@
+/*
+ * 7zip support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file was written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_7Z
+
+#include "physfs_lzmasdk.h"
+
+typedef struct
+{
+ ISeekInStream seekStream; /* lzma sdk i/o interface (lower level). */
+ PHYSFS_Io *io; /* physfs i/o interface for this archive. */
+ CLookToRead lookStream; /* lzma sdk i/o interface (higher level). */
+} SZIPLookToRead;
+
+/* One SZIPentry is kept for each file in an open 7zip archive. */
+typedef struct
+{
+ __PHYSFS_DirTreeEntry tree; /* manages directory tree */
+ PHYSFS_uint32 dbidx; /* index into lzma sdk database */
+} SZIPentry;
+
+/* One SZIPinfo is kept for each open 7zip archive. */
+typedef struct
+{
+ __PHYSFS_DirTree tree; /* manages directory tree. */
+ PHYSFS_Io *io; /* physfs i/o interface for this archive. */
+ CSzArEx db; /* lzma sdk archive database object. */
+} SZIPinfo;
+
+
+static PHYSFS_ErrorCode szipErrorCode(const SRes rc)
+{
+ switch (rc)
+ {
+ case SZ_OK: return PHYSFS_ERR_OK;
+ case SZ_ERROR_DATA: return PHYSFS_ERR_CORRUPT;
+ case SZ_ERROR_MEM: return PHYSFS_ERR_OUT_OF_MEMORY;
+ case SZ_ERROR_CRC: return PHYSFS_ERR_CORRUPT;
+ case SZ_ERROR_UNSUPPORTED: return PHYSFS_ERR_UNSUPPORTED;
+ case SZ_ERROR_INPUT_EOF: return PHYSFS_ERR_CORRUPT;
+ case SZ_ERROR_OUTPUT_EOF: return PHYSFS_ERR_IO;
+ case SZ_ERROR_READ: return PHYSFS_ERR_IO;
+ case SZ_ERROR_WRITE: return PHYSFS_ERR_IO;
+ case SZ_ERROR_ARCHIVE: return PHYSFS_ERR_CORRUPT;
+ case SZ_ERROR_NO_ARCHIVE: return PHYSFS_ERR_UNSUPPORTED;
+ default: break;
+ } /* switch */
+
+ return PHYSFS_ERR_OTHER_ERROR;
+} /* szipErrorCode */
+
+
+/* LZMA SDK's ISzAlloc interface ... */
+
+static void *SZIP_ISzAlloc_Alloc(void *p, size_t size)
+{
+ return allocator.Malloc(size ? size : 1);
+} /* SZIP_ISzAlloc_Alloc */
+
+static void SZIP_ISzAlloc_Free(void *p, void *address)
+{
+ if (address)
+ allocator.Free(address);
+} /* SZIP_ISzAlloc_Free */
+
+static ISzAlloc SZIP_SzAlloc = {
+ SZIP_ISzAlloc_Alloc, SZIP_ISzAlloc_Free
+};
+
+
+/* we implement ISeekInStream, and then wrap that in LZMA SDK's CLookToRead,
+ which implements the higher-level ILookInStream on top of that, handling
+ buffering and such for us. */
+
+/* LZMA SDK's ISeekInStream interface ... */
+
+static SRes SZIP_ISeekInStream_Read(void *p, void *buf, size_t *size)
+{
+ SZIPLookToRead *stream = (SZIPLookToRead *) p;
+ PHYSFS_Io *io = stream->io;
+ const PHYSFS_uint64 len = (PHYSFS_uint64) *size;
+ const PHYSFS_sint64 rc = (len == 0) ? 0 : io->read(io, buf, len);
+
+ if (rc < 0)
+ {
+ *size = 0;
+ return SZ_ERROR_READ;
+ } /* if */
+
+ *size = (size_t) rc;
+ return SZ_OK;
+} /* SZIP_ISeekInStream_Read */
+
+static SRes SZIP_ISeekInStream_Seek(void *p, Int64 *pos, ESzSeek origin)
+{
+ SZIPLookToRead *stream = (SZIPLookToRead *) p;
+ PHYSFS_Io *io = stream->io;
+ PHYSFS_sint64 base;
+ PHYSFS_uint64 newpos;
+
+ switch (origin)
+ {
+ case SZ_SEEK_SET:
+ base = 0;
+ break;
+
+ case SZ_SEEK_CUR:
+ base = io->tell(io);
+ break;
+
+ case SZ_SEEK_END:
+ base = io->length(io);
+ break;
+
+ default:
+ return SZ_ERROR_FAIL;
+ } /* switch */
+
+ if (base < 0)
+ return SZ_ERROR_FAIL;
+ else if ((*pos < 0) && (((Int64) base) < -*pos))
+ return SZ_ERROR_FAIL;
+
+ newpos = (PHYSFS_uint64) (((Int64) base) + *pos);
+ if (!io->seek(io, newpos))
+ return SZ_ERROR_FAIL;
+
+ *pos = (Int64) newpos;
+ return SZ_OK;
+} /* SZIP_ISeekInStream_Seek */
+
+
+static void szipInitStream(SZIPLookToRead *stream, PHYSFS_Io *io)
+{
+ stream->seekStream.Read = SZIP_ISeekInStream_Read;
+ stream->seekStream.Seek = SZIP_ISeekInStream_Seek;
+
+ stream->io = io;
+
+ /* !!! FIXME: can we use lookahead? Is there value to it? */
+ LookToRead_Init(&stream->lookStream);
+ LookToRead_CreateVTable(&stream->lookStream, False);
+ stream->lookStream.realStream = &stream->seekStream;
+} /* szipInitStream */
+
+
+/* Do this in a separate function so we can smallAlloc without looping. */
+static int szipLoadEntry(SZIPinfo *info, const PHYSFS_uint32 idx)
+{
+ const size_t utf16len = SzArEx_GetFileNameUtf16(&info->db, idx, NULL);
+ const size_t utf16buflen = utf16len * 2;
+ PHYSFS_uint16 *utf16 = (PHYSFS_uint16 *) __PHYSFS_smallAlloc(utf16buflen);
+ const size_t utf8buflen = utf16len * 4;
+ char *utf8 = (char *) __PHYSFS_smallAlloc(utf8buflen);
+ int retval = 0;
+
+ if (utf16 && utf8)
+ {
+ const int isdir = SzArEx_IsDir(&info->db, idx) != 0;
+ SZIPentry *entry;
+ SzArEx_GetFileNameUtf16(&info->db, idx, (UInt16 *) utf16);
+ PHYSFS_utf8FromUtf16(utf16, utf8, utf8buflen);
+ entry = (SZIPentry*) __PHYSFS_DirTreeAdd(&info->tree, utf8, isdir);
+ retval = (entry != NULL);
+ if (retval)
+ entry->dbidx = idx;
+ } /* if */
+
+ __PHYSFS_smallFree(utf8);
+ __PHYSFS_smallFree(utf16);
+
+ return retval;
+} /* szipLoadEntry */
+
+
+static int szipLoadEntries(SZIPinfo *info)
+{
+ int retval = 0;
+
+ if (__PHYSFS_DirTreeInit(&info->tree, sizeof (SZIPentry), 1, 0))
+ {
+ const PHYSFS_uint32 count = info->db.NumFiles;
+ PHYSFS_uint32 i;
+ for (i = 0; i < count; i++)
+ BAIL_IF_ERRPASS(!szipLoadEntry(info, i), 0);
+ retval = 1;
+ } /* if */
+
+ return retval;
+} /* szipLoadEntries */
+
+
+static void SZIP_closeArchive(void *opaque)
+{
+ SZIPinfo *info = (SZIPinfo *) opaque;
+ if (info)
+ {
+ if (info->io)
+ info->io->destroy(info->io);
+ SzArEx_Free(&info->db, &SZIP_SzAlloc);
+ __PHYSFS_DirTreeDeinit(&info->tree);
+ allocator.Free(info);
+ } /* if */
+} /* SZIP_closeArchive */
+
+
+static void *SZIP_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ static const PHYSFS_uint8 wantedsig[] = { '7','z',0xBC,0xAF,0x27,0x1C };
+ SZIPLookToRead stream;
+ ISzAlloc *alloc = &SZIP_SzAlloc;
+ SZIPinfo *info = NULL;
+ SRes rc;
+ PHYSFS_uint8 sig[6];
+ PHYSFS_sint64 pos;
+
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+ pos = io->tell(io);
+ BAIL_IF_ERRPASS(pos == -1, NULL);
+ BAIL_IF_ERRPASS(io->read(io, sig, 6) != 6, NULL);
+ *claimed = (memcmp(sig, wantedsig, 6) == 0);
+ BAIL_IF_ERRPASS(!io->seek(io, pos), NULL);
+
+ info = (SZIPinfo *) allocator.Malloc(sizeof (SZIPinfo));
+ BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ memset(info, '\0', sizeof (*info));
+
+ SzArEx_Init(&info->db);
+
+ info->io = io;
+
+ szipInitStream(&stream, io);
+ rc = SzArEx_Open(&info->db, &stream.lookStream.s, alloc, alloc);
+ GOTO_IF(rc != SZ_OK, szipErrorCode(rc), failed);
+
+ GOTO_IF_ERRPASS(!szipLoadEntries(info), failed);
+
+ return info;
+
+failed:
+ info->io = NULL; /* don't let cleanup destroy the PHYSFS_Io. */
+ SZIP_closeArchive(info);
+ return NULL;
+} /* SZIP_openArchive */
+
+
+static PHYSFS_Io *SZIP_openRead(void *opaque, const char *path)
+{
+ /* !!! FIXME: the current lzma sdk C API only allows you to decompress
+ !!! FIXME: the entire file at once, which isn't ideal. Fix this in the
+ !!! FIXME: SDK and then convert this all to a streaming interface. */
+
+ SZIPinfo *info = (SZIPinfo *) opaque;
+ SZIPentry *entry = (SZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path);
+ ISzAlloc *alloc = &SZIP_SzAlloc;
+ SZIPLookToRead stream;
+ PHYSFS_Io *retval = NULL;
+ PHYSFS_Io *io = NULL;
+ UInt32 blockIndex = 0xFFFFFFFF;
+ Byte *outBuffer = NULL;
+ size_t outBufferSize = 0;
+ size_t offset = 0;
+ size_t outSizeProcessed = 0;
+ void *buf = NULL;
+ SRes rc;
+
+ BAIL_IF_ERRPASS(!entry, NULL);
+ BAIL_IF(entry->tree.isdir, PHYSFS_ERR_NOT_A_FILE, NULL);
+
+ io = info->io->duplicate(info->io);
+ GOTO_IF_ERRPASS(!io, SZIP_openRead_failed);
+
+ szipInitStream(&stream, io);
+
+ rc = SzArEx_Extract(&info->db, &stream.lookStream.s, entry->dbidx,
+ &blockIndex, &outBuffer, &outBufferSize, &offset,
+ &outSizeProcessed, alloc, alloc);
+ GOTO_IF(rc != SZ_OK, szipErrorCode(rc), SZIP_openRead_failed);
+ GOTO_IF(outBuffer == NULL, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed);
+
+ io->destroy(io);
+ io = NULL;
+
+ buf = allocator.Malloc(outSizeProcessed ? outSizeProcessed : 1);
+ GOTO_IF(buf == NULL, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed);
+
+ if (outSizeProcessed > 0)
+ memcpy(buf, outBuffer + offset, outSizeProcessed);
+
+ alloc->Free(alloc, outBuffer);
+ outBuffer = NULL;
+
+ retval = __PHYSFS_createMemoryIo(buf, outSizeProcessed, allocator.Free);
+ GOTO_IF_ERRPASS(!retval, SZIP_openRead_failed);
+
+ return retval;
+
+SZIP_openRead_failed:
+ if (io != NULL)
+ io->destroy(io);
+
+ if (buf)
+ allocator.Free(buf);
+
+ if (outBuffer)
+ alloc->Free(alloc, outBuffer);
+
+ return NULL;
+} /* SZIP_openRead */
+
+
+static PHYSFS_Io *SZIP_openWrite(void *opaque, const char *filename)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, NULL);
+} /* SZIP_openWrite */
+
+
+static PHYSFS_Io *SZIP_openAppend(void *opaque, const char *filename)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, NULL);
+} /* SZIP_openAppend */
+
+
+static int SZIP_remove(void *opaque, const char *name)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, 0);
+} /* SZIP_remove */
+
+
+static int SZIP_mkdir(void *opaque, const char *name)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, 0);
+} /* SZIP_mkdir */
+
+
+static inline PHYSFS_uint64 lzmasdkTimeToPhysfsTime(const CNtfsFileTime *t)
+{
+ const PHYSFS_uint64 winEpochToUnixEpoch = __PHYSFS_UI64(0x019DB1DED53E8000);
+ const PHYSFS_uint64 nanosecToMillisec = __PHYSFS_UI64(10000000);
+ const PHYSFS_uint64 quad = (((PHYSFS_uint64) t->High) << 32) | t->Low;
+ return (quad - winEpochToUnixEpoch) / nanosecToMillisec;
+} /* lzmasdkTimeToPhysfsTime */
+
+
+static int SZIP_stat(void *opaque, const char *path, PHYSFS_Stat *stat)
+{
+ SZIPinfo *info = (SZIPinfo *) opaque;
+ SZIPentry *entry;
+ PHYSFS_uint32 idx;
+
+ entry = (SZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path);
+ BAIL_IF_ERRPASS(!entry, 0);
+ idx = entry->dbidx;
+
+ if (entry->tree.isdir)
+ {
+ stat->filesize = -1;
+ stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+ } /* if */
+ else
+ {
+ stat->filesize = (PHYSFS_sint64) SzArEx_GetFileSize(&info->db, idx);
+ stat->filetype = PHYSFS_FILETYPE_REGULAR;
+ } /* else */
+
+ if (info->db.MTime.Vals != NULL)
+ stat->modtime = lzmasdkTimeToPhysfsTime(&info->db.MTime.Vals[idx]);
+ else if (info->db.CTime.Vals != NULL)
+ stat->modtime = lzmasdkTimeToPhysfsTime(&info->db.CTime.Vals[idx]);
+ else
+ stat->modtime = -1;
+
+ if (info->db.CTime.Vals != NULL)
+ stat->createtime = lzmasdkTimeToPhysfsTime(&info->db.CTime.Vals[idx]);
+ else if (info->db.MTime.Vals != NULL)
+ stat->createtime = lzmasdkTimeToPhysfsTime(&info->db.MTime.Vals[idx]);
+ else
+ stat->createtime = -1;
+
+ stat->accesstime = -1;
+ stat->readonly = 1;
+
+ return 1;
+} /* SZIP_stat */
+
+
+void SZIP_global_init(void)
+{
+ /* this just needs to calculate some things, so it only ever
+ has to run once, even after a deinit. */
+ static int generatedTable = 0;
+ if (!generatedTable)
+ {
+ generatedTable = 1;
+ CrcGenerateTable();
+ } /* if */
+} /* SZIP_global_init */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_7Z =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "7Z",
+ "7zip archives",
+ "Ryan C. Gordon ",
+ "https://icculus.org/physfs/",
+ 0, /* supportsSymlinks */
+ },
+ SZIP_openArchive,
+ __PHYSFS_DirTreeEnumerate,
+ SZIP_openRead,
+ SZIP_openWrite,
+ SZIP_openAppend,
+ SZIP_remove,
+ SZIP_mkdir,
+ SZIP_stat,
+ SZIP_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_7Z */
+
+/* end of physfs_archiver_7z.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_dir.c b/third-party/physfs/src/physfs_archiver_dir.c
new file mode 100644
index 0000000..61c0da3
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_dir.c
@@ -0,0 +1,196 @@
+/*
+ * Standard directory I/O support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+/* There's no PHYSFS_Io interface here. Use __PHYSFS_createNativeIo(). */
+
+
+
+static char *cvtToDependent(const char *prepend, const char *path,
+ char *buf, const size_t buflen)
+{
+ BAIL_IF(buf == NULL, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ snprintf(buf, buflen, "%s%s", prepend ? prepend : "", path);
+
+ #if !__PHYSFS_STANDARD_DIRSEP
+ assert(__PHYSFS_platformDirSeparator != '/');
+ {
+ char *p;
+ for (p = strchr(buf, '/'); p != NULL; p = strchr(p + 1, '/'))
+ *p = __PHYSFS_platformDirSeparator;
+ } /* if */
+ #endif
+
+ return buf;
+} /* cvtToDependent */
+
+
+#define CVT_TO_DEPENDENT(buf, pre, dir) { \
+ const size_t len = ((pre) ? strlen((char *) pre) : 0) + strlen(dir) + 1; \
+ buf = cvtToDependent((char*)pre,dir,(char*)__PHYSFS_smallAlloc(len),len); \
+}
+
+
+
+static void *DIR_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ PHYSFS_Stat st;
+ const char dirsep = __PHYSFS_platformDirSeparator;
+ char *retval = NULL;
+ const size_t namelen = strlen(name);
+ const size_t seplen = 1;
+
+ assert(io == NULL); /* shouldn't create an Io for these. */
+ BAIL_IF_ERRPASS(!__PHYSFS_platformStat(name, &st, 1), NULL);
+
+ if (st.filetype != PHYSFS_FILETYPE_DIRECTORY)
+ BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ *claimed = 1;
+ retval = allocator.Malloc(namelen + seplen + 1);
+ BAIL_IF(retval == NULL, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+
+ strcpy(retval, name);
+
+ /* make sure there's a dir separator at the end of the string */
+ if (retval[namelen - 1] != dirsep)
+ {
+ retval[namelen] = dirsep;
+ retval[namelen + 1] = '\0';
+ } /* if */
+
+ return retval;
+} /* DIR_openArchive */
+
+
+static PHYSFS_EnumerateCallbackResult DIR_enumerate(void *opaque,
+ const char *dname, PHYSFS_EnumerateCallback cb,
+ const char *origdir, void *callbackdata)
+{
+ char *d;
+ PHYSFS_EnumerateCallbackResult retval;
+ CVT_TO_DEPENDENT(d, opaque, dname);
+ BAIL_IF_ERRPASS(!d, PHYSFS_ENUM_ERROR);
+ retval = __PHYSFS_platformEnumerate(d, cb, origdir, callbackdata);
+ __PHYSFS_smallFree(d);
+ return retval;
+} /* DIR_enumerate */
+
+
+static PHYSFS_Io *doOpen(void *opaque, const char *name, const int mode)
+{
+ PHYSFS_Io *io = NULL;
+ char *f = NULL;
+
+ CVT_TO_DEPENDENT(f, opaque, name);
+ BAIL_IF_ERRPASS(!f, NULL);
+
+ io = __PHYSFS_createNativeIo(f, mode);
+ if (io == NULL)
+ {
+ const PHYSFS_ErrorCode err = PHYSFS_getLastErrorCode();
+ PHYSFS_Stat statbuf;
+ __PHYSFS_platformStat(f, &statbuf, 0); /* !!! FIXME: why are we stating here? */
+ PHYSFS_setErrorCode(err);
+ } /* if */
+
+ __PHYSFS_smallFree(f);
+
+ return io;
+} /* doOpen */
+
+
+static PHYSFS_Io *DIR_openRead(void *opaque, const char *filename)
+{
+ return doOpen(opaque, filename, 'r');
+} /* DIR_openRead */
+
+
+static PHYSFS_Io *DIR_openWrite(void *opaque, const char *filename)
+{
+ return doOpen(opaque, filename, 'w');
+} /* DIR_openWrite */
+
+
+static PHYSFS_Io *DIR_openAppend(void *opaque, const char *filename)
+{
+ return doOpen(opaque, filename, 'a');
+} /* DIR_openAppend */
+
+
+static int DIR_remove(void *opaque, const char *name)
+{
+ int retval;
+ char *f;
+
+ CVT_TO_DEPENDENT(f, opaque, name);
+ BAIL_IF_ERRPASS(!f, 0);
+ retval = __PHYSFS_platformDelete(f);
+ __PHYSFS_smallFree(f);
+ return retval;
+} /* DIR_remove */
+
+
+static int DIR_mkdir(void *opaque, const char *name)
+{
+ int retval;
+ char *f;
+
+ CVT_TO_DEPENDENT(f, opaque, name);
+ BAIL_IF_ERRPASS(!f, 0);
+ retval = __PHYSFS_platformMkDir(f);
+ __PHYSFS_smallFree(f);
+ return retval;
+} /* DIR_mkdir */
+
+
+static void DIR_closeArchive(void *opaque)
+{
+ allocator.Free(opaque);
+} /* DIR_closeArchive */
+
+
+static int DIR_stat(void *opaque, const char *name, PHYSFS_Stat *stat)
+{
+ int retval = 0;
+ char *d;
+
+ CVT_TO_DEPENDENT(d, opaque, name);
+ BAIL_IF_ERRPASS(!d, 0);
+ retval = __PHYSFS_platformStat(d, stat, 0);
+ __PHYSFS_smallFree(d);
+ return retval;
+} /* DIR_stat */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_DIR =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "",
+ "Non-archive, direct filesystem I/O",
+ "Ryan C. Gordon ",
+ "https://icculus.org/physfs/",
+ 1, /* supportsSymlinks */
+ },
+ DIR_openArchive,
+ DIR_enumerate,
+ DIR_openRead,
+ DIR_openWrite,
+ DIR_openAppend,
+ DIR_remove,
+ DIR_mkdir,
+ DIR_stat,
+ DIR_closeArchive
+};
+
+/* end of physfs_archiver_dir.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_grp.c b/third-party/physfs/src/physfs_archiver_grp.c
new file mode 100644
index 0000000..9a2978a
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_grp.c
@@ -0,0 +1,116 @@
+/*
+ * GRP support routines for PhysicsFS.
+ *
+ * This driver handles BUILD engine archives ("groupfiles"). This format
+ * (but not this driver) was put together by Ken Silverman.
+ *
+ * The format is simple enough. In Ken's words:
+ *
+ * What's the .GRP file format?
+ *
+ * The ".grp" file format is just a collection of a lot of files stored
+ * into 1 big one. I tried to make the format as simple as possible: The
+ * first 12 bytes contains my name, "KenSilverman". The next 4 bytes is
+ * the number of files that were compacted into the group file. Then for
+ * each file, there is a 16 byte structure, where the first 12 bytes are
+ * the filename, and the last 4 bytes are the file's size. The rest of
+ * the group file is just the raw data packed one after the other in the
+ * same order as the list of files.
+ *
+ * (That info is from http://www.advsys.net/ken/build.htm ...)
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_GRP
+
+static int grpLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc)
+{
+ PHYSFS_uint32 pos = 16 + (16 * count); /* past sig+metadata. */
+ PHYSFS_uint32 i;
+
+ for (i = 0; i < count; i++)
+ {
+ char *ptr;
+ char name[13];
+ PHYSFS_uint32 size;
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 12), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0);
+
+ name[12] = '\0'; /* name isn't null-terminated in file. */
+ if ((ptr = strchr(name, ' ')) != NULL)
+ *ptr = '\0'; /* trim extra spaces. */
+
+ size = PHYSFS_swapULE32(size);
+ BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
+
+ pos += size;
+ } /* for */
+
+ return 1;
+} /* grpLoadEntries */
+
+
+static void *GRP_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ PHYSFS_uint8 buf[12];
+ PHYSFS_uint32 count = 0;
+ void *unpkarc = NULL;
+
+ assert(io != NULL); /* shouldn't ever happen. */
+
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, sizeof (buf)), NULL);
+ if (memcmp(buf, "KenSilverman", sizeof (buf)) != 0)
+ BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ *claimed = 1;
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof(count)), NULL);
+ count = PHYSFS_swapULE32(count);
+
+ unpkarc = UNPK_openArchive(io, 0, 1);
+ BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+ if (!grpLoadEntries(io, count, unpkarc))
+ {
+ UNPK_abandonArchive(unpkarc);
+ return NULL;
+ } /* if */
+
+ return unpkarc;
+} /* GRP_openArchive */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_GRP =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "GRP",
+ "Build engine Groupfile format",
+ "Ryan C. Gordon ",
+ "https://icculus.org/physfs/",
+ 0, /* supportsSymlinks */
+ },
+ GRP_openArchive,
+ UNPK_enumerate,
+ UNPK_openRead,
+ UNPK_openWrite,
+ UNPK_openAppend,
+ UNPK_remove,
+ UNPK_mkdir,
+ UNPK_stat,
+ UNPK_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_GRP */
+
+/* end of physfs_archiver_grp.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_hog.c b/third-party/physfs/src/physfs_archiver_hog.c
new file mode 100644
index 0000000..a818478
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_hog.c
@@ -0,0 +1,170 @@
+/*
+ * HOG support routines for PhysicsFS.
+ *
+ * This driver handles Descent I/II/III HOG archives.
+ *
+ * The Descent I/II format is very simple:
+ *
+ * The file always starts with the 3-byte signature "DHF" (Descent
+ * HOG file). After that the files of a HOG are just attached after
+ * another, divided by a 17 bytes header, which specifies the name
+ * and length (in bytes) of the forthcoming file! So you just read
+ * the header with its information of how big the following file is,
+ * and then skip exact that number of bytes to get to the next file
+ * in that HOG.
+ *
+ * char sig[3] = {'D', 'H', 'F'}; // "DHF"=Descent HOG File
+ *
+ * struct {
+ * char file_name[13]; // Filename, padded to 13 bytes with 0s
+ * int file_size; // filesize in bytes
+ * char data[file_size]; // The file data
+ * } FILE_STRUCT; // Repeated until the end of the file.
+ *
+ * (That info is from http://www.descent2.com/ddn/specs/hog/)
+ *
+ * Descent 3 moved to HOG2 format, which starts with the chars "HOG2",
+ * then 32-bits for the number of contained files, 32 bits for the offset
+ * to the first file's data, then 56 bytes of 0xFF (reserved?). Then for
+ * each file, there's 36 bytes for filename (null-terminated, rest of bytes
+ * are garbage), 32-bits unknown/reserved (always zero?), 32-bits of length
+ * of file data, 32-bits of time since Unix epoch. Then immediately following,
+ * for each file is their uncompressed content, you can find its offset
+ * by starting at the initial data offset and adding the filesize of each
+ * prior file.
+ *
+ * This information was found at:
+ * https://web.archive.org/web/20020213004051/http://descent-3.com/ddn/specs/hog/
+ *
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Bradley Bell and Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_HOG
+
+static int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val)
+{
+ PHYSFS_uint32 v;
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0);
+ *val = PHYSFS_swapULE32(v);
+ return 1;
+} /* readui32 */
+
+static int hog1LoadEntries(PHYSFS_Io *io, void *arc)
+{
+ const PHYSFS_uint64 iolen = io->length(io);
+ PHYSFS_uint32 pos = 3;
+
+ while (pos < iolen)
+ {
+ PHYSFS_uint32 size;
+ char name[13];
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 13), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &size), 0);
+ name[12] = '\0'; /* just in case. */
+ pos += 13 + 4;
+
+ BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
+ pos += size;
+
+ /* skip over entry */
+ BAIL_IF_ERRPASS(!io->seek(io, pos), 0);
+ } /* while */
+
+ return 1;
+} /* hogLoadEntries */
+
+static int hog2LoadEntries(PHYSFS_Io *io, void *arc)
+{
+ PHYSFS_uint32 numfiles;
+ PHYSFS_uint32 pos;
+ PHYSFS_uint32 i;
+
+ BAIL_IF_ERRPASS(!readui32(io, &numfiles), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &pos), 0);
+ BAIL_IF_ERRPASS(!io->seek(io, 68), 0); /* skip to end of header. */
+
+ for (i = 0; i < numfiles; i++) {
+ char name[37];
+ PHYSFS_uint32 reserved;
+ PHYSFS_uint32 size;
+ PHYSFS_uint32 mtime;
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 36), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &reserved), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &size), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &mtime), 0);
+ name[36] = '\0'; /* just in case */
+ BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, mtime, mtime, pos, size), 0);
+ pos += size;
+ }
+
+ return 1;
+} /* hog2LoadEntries */
+
+
+static void *HOG_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ PHYSFS_uint8 buf[3];
+ void *unpkarc = NULL;
+ int hog1 = 0;
+
+ assert(io != NULL); /* shouldn't ever happen. */
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 3), NULL);
+
+ if (memcmp(buf, "DHF", 3) == 0)
+ hog1 = 1; /* original HOG (Descent 1 and 2) archive */
+ else
+ {
+ BAIL_IF(memcmp(buf, "HOG", 3) != 0, PHYSFS_ERR_UNSUPPORTED, NULL); /* Not HOG2 */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 1), NULL);
+ BAIL_IF(buf[0] != '2', PHYSFS_ERR_UNSUPPORTED, NULL); /* Not HOG2 */
+ } /* else */
+
+ *claimed = 1;
+
+ unpkarc = UNPK_openArchive(io, 0, 1);
+ BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+ if (!(hog1 ? hog1LoadEntries(io, unpkarc) : hog2LoadEntries(io, unpkarc)))
+ {
+ UNPK_abandonArchive(unpkarc);
+ return NULL;
+ } /* if */
+
+ return unpkarc;
+} /* HOG_openArchive */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_HOG =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "HOG",
+ "Descent I/II/III HOG file format",
+ "Bradley Bell ",
+ "https://icculus.org/physfs/",
+ 0, /* supportsSymlinks */
+ },
+ HOG_openArchive,
+ UNPK_enumerate,
+ UNPK_openRead,
+ UNPK_openWrite,
+ UNPK_openAppend,
+ UNPK_remove,
+ UNPK_mkdir,
+ UNPK_stat,
+ UNPK_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_HOG */
+
+/* end of physfs_archiver_hog.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_iso9660.c b/third-party/physfs/src/physfs_archiver_iso9660.c
new file mode 100644
index 0000000..e92d677
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_iso9660.c
@@ -0,0 +1,387 @@
+/*
+ * ISO9660 support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file originally written by Christoph Nelles, but was largely
+ * rewritten by Ryan C. Gordon (so please harass Ryan about bugs and not
+ * Christoph).
+ */
+
+/*
+ * Handles CD-ROM disk images (and raw CD-ROM devices).
+ *
+ * Not supported:
+ * - Rock Ridge (needed for sparse files, device nodes and symlinks, etc).
+ * - Non 2048 Sectors
+ * - TRANS.TBL (maps 8.3 filenames on old discs to long filenames).
+ * - Multiextents (4gb max file size without it).
+ * - UDF
+ *
+ * Deviations from the standard
+ * - Ignores mandatory sort order
+ * - Allows various invalid file names
+ *
+ * Problems
+ * - Ambiguities in the standard
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_ISO9660
+
+#include
+
+/* ISO9660 often stores values in both big and little endian formats: little
+ first, followed by big. While technically there might be different values
+ in each, we just always use the littleendian ones and swap ourselves. The
+ fields aren't aligned anyhow, so you have to serialize them in any case
+ to avoid crashes on many CPU archs in any case. */
+
+static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet,
+ const char *base, const PHYSFS_uint64 dirstart,
+ const PHYSFS_uint64 dirend, void *unpkarc);
+
+static int iso9660AddEntry(PHYSFS_Io *io, const int joliet, const int isdir,
+ const char *base, PHYSFS_uint8 *fname,
+ const int fnamelen, const PHYSFS_sint64 ts,
+ const PHYSFS_uint64 pos, const PHYSFS_uint64 len,
+ void *unpkarc)
+{
+ char *fullpath;
+ char *fnamecpy;
+ size_t baselen;
+ size_t fullpathlen;
+ void *entry;
+ int i;
+
+ if (fnamelen == 1 && ((fname[0] == 0) || (fname[0] == 1)))
+ return 1; /* Magic that represents "." and "..", ignore */
+
+ BAIL_IF(fnamelen == 0, PHYSFS_ERR_CORRUPT, 0);
+ assert(fnamelen > 0);
+ assert(fnamelen <= 255);
+ BAIL_IF(joliet && (fnamelen % 2), PHYSFS_ERR_CORRUPT, 0);
+
+ /* Joliet is UCS-2, so at most UTF-8 will double the byte size */
+ baselen = strlen(base);
+ fullpathlen = baselen + (fnamelen * (joliet ? 2 : 1)) + 2;
+ fullpath = (char *) __PHYSFS_smallAlloc(fullpathlen);
+ BAIL_IF(!fullpath, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ fnamecpy = fullpath;
+ if (baselen > 0)
+ {
+ snprintf(fullpath, fullpathlen, "%s/", base);
+ fnamecpy += baselen + 1;
+ fullpathlen -= baselen - 1;
+ } /* if */
+
+ if (joliet)
+ {
+ PHYSFS_uint16 *ucs2 = (PHYSFS_uint16 *) fname;
+ int total = fnamelen / 2;
+ for (i = 0; i < total; i++)
+ ucs2[i] = PHYSFS_swapUBE16(ucs2[i]);
+ ucs2[total] = '\0';
+ PHYSFS_utf8FromUcs2(ucs2, fnamecpy, fullpathlen);
+ } /* if */
+ else
+ {
+ for (i = 0; i < fnamelen; i++)
+ {
+ /* We assume the filenames are low-ASCII; consider the archive
+ corrupt if we see something above 127, since we don't know the
+ encoding. (We can change this later if we find out these exist
+ and are intended to be, say, latin-1 or UTF-8 encoding). */
+ BAIL_IF(fname[i] > 127, PHYSFS_ERR_CORRUPT, 0);
+ fnamecpy[i] = fname[i];
+ } /* for */
+ fnamecpy[fnamelen] = '\0';
+
+ if (!isdir)
+ {
+ /* find last SEPARATOR2 */
+ char *ptr = strrchr(fnamecpy, ';');
+ if (ptr && (ptr != fnamecpy))
+ *(ptr--) = '\0';
+ else
+ ptr = fnamecpy + (fnamelen - 1);
+
+ /* chop out any trailing '.', as done in all implementations */
+ if (*ptr == '.')
+ *ptr = '\0';
+ } /* if */
+ } /* else */
+
+ entry = UNPK_addEntry(unpkarc, fullpath, isdir, ts, ts, pos, len);
+ if ((entry) && (isdir))
+ {
+ if (!iso9660LoadEntries(io, joliet, fullpath, pos, pos + len, unpkarc))
+ entry = NULL; /* so we report a failure later. */
+ } /* if */
+
+ __PHYSFS_smallFree(fullpath);
+ return entry != NULL;
+} /* iso9660AddEntry */
+
+static int iso9660LoadEntries(PHYSFS_Io *io, const int joliet,
+ const char *base, const PHYSFS_uint64 dirstart,
+ const PHYSFS_uint64 dirend, void *unpkarc)
+{
+ PHYSFS_uint64 readpos = dirstart;
+
+ while (1)
+ {
+ PHYSFS_uint8 recordlen;
+ PHYSFS_uint8 extattrlen;
+ PHYSFS_uint32 extent;
+ PHYSFS_uint32 datalen;
+ PHYSFS_uint8 ignore[4];
+ PHYSFS_uint8 year, month, day, hour, minute, second, offset;
+ PHYSFS_uint8 flags;
+ PHYSFS_uint8 fnamelen;
+ PHYSFS_uint8 fname[256];
+ PHYSFS_sint64 timestamp;
+ struct tm t;
+ int isdir;
+ int multiextent;
+
+ BAIL_IF_ERRPASS(!io->seek(io, readpos), 0);
+
+ /* recordlen = 0 -> no more entries or fill entry */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &recordlen, 1), 0);
+ if (recordlen > 0)
+ readpos += recordlen; /* ready to seek to next record. */
+ else
+ {
+ PHYSFS_uint64 nextpos;
+
+ /* if we are in the last sector of the directory & it's 0 -> end */
+ if ((dirend - 2048) <= (readpos - 1))
+ break; /* finished */
+
+ /* else skip to the next sector & continue; */
+ nextpos = (((readpos - 1) / 2048) + 1) * 2048;
+
+ /* whoops, can't make forward progress! */
+ BAIL_IF(nextpos == readpos, PHYSFS_ERR_CORRUPT, 0);
+
+ readpos = nextpos;
+ continue; /* start back at upper loop. */
+ } /* else */
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extattrlen, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extent, 4), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* extent be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &datalen, 4), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* datalen be */
+
+ /* record timestamp */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &year, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &month, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &day, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &hour, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &minute, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &second, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &offset, 1), 0);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &flags, 1), 0);
+ isdir = (flags & (1 << 1)) != 0;
+ multiextent = (flags & (1 << 7)) != 0;
+ BAIL_IF(multiextent, PHYSFS_ERR_UNSUPPORTED, 0); /* !!! FIXME */
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* unit size */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* interleave gap */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seqnum le */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seqnum be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &fnamelen, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, fname, fnamelen), 0);
+
+ t.tm_sec = second;
+ t.tm_min = minute;
+ t.tm_hour = hour;
+ t.tm_mday = day;
+ t.tm_mon = month - 1;
+ t.tm_year = year;
+ t.tm_wday = 0;
+ t.tm_yday = 0;
+ t.tm_isdst = -1;
+ timestamp = (PHYSFS_sint64) mktime(&t);
+
+ extent += extattrlen; /* skip extended attribute record. */
+
+ /* infinite loop, corrupt file? */
+ BAIL_IF((extent * 2048) == dirstart, PHYSFS_ERR_CORRUPT, 0);
+
+ if (!iso9660AddEntry(io, joliet, isdir, base, fname, fnamelen,
+ timestamp, extent * 2048, datalen, unpkarc))
+ {
+ return 0;
+ } /* if */
+ } /* while */
+
+ return 1;
+} /* iso9660LoadEntries */
+
+
+static int parseVolumeDescriptor(PHYSFS_Io *io, PHYSFS_uint64 *_rootpos,
+ PHYSFS_uint64 *_rootlen, int *_joliet,
+ int *_claimed)
+{
+ PHYSFS_uint64 pos = 32768; /* start at the Primary Volume Descriptor */
+ int found = 0;
+ int done = 0;
+
+ *_joliet = 0;
+
+ while (!done)
+ {
+ PHYSFS_uint8 type;
+ PHYSFS_uint8 identifier[5];
+ PHYSFS_uint8 version;
+ PHYSFS_uint8 flags;
+ PHYSFS_uint8 escapeseqs[32];
+ PHYSFS_uint8 ignore[32];
+ PHYSFS_uint16 blocksize;
+ PHYSFS_uint32 extent;
+ PHYSFS_uint32 datalen;
+
+ BAIL_IF_ERRPASS(!io->seek(io, pos), 0);
+ pos += 2048; /* each volume descriptor is 2048 bytes */
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &type, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, identifier, 5), 0);
+
+ if (memcmp(identifier, "CD001", 5) != 0) /* maybe not an iso? */
+ {
+ BAIL_IF(!*_claimed, PHYSFS_ERR_UNSUPPORTED, 0);
+ continue; /* just skip this one */
+ } /* if */
+
+ *_claimed = 1; /* okay, this is probably an iso. */
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &version, 1), 0); /* version */
+ BAIL_IF(version != 1, PHYSFS_ERR_UNSUPPORTED, 0);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &flags, 1), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 32), 0); /* system id */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 32), 0); /* volume id */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 8), 0); /* reserved */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* space le */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* space be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, escapeseqs, 32), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* setsize le */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* setsize be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seq num le */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* seq num be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &blocksize, 2), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 2), 0); /* blocklen be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtablen le */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtablen be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtabpos le */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* optpthtabpos le */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* pthtabpos be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* optpthtabpos be */
+
+ /* root directory record... */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* len */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 1), 0); /* attr len */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &extent, 4), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* extent be */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &datalen, 4), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), 0); /* datalen be */
+
+ /* !!! FIXME: deal with this properly. */
+ blocksize = PHYSFS_swapULE32(blocksize);
+ BAIL_IF(blocksize && (blocksize != 2048), PHYSFS_ERR_UNSUPPORTED, 0);
+
+ switch (type)
+ {
+ case 1: /* Primary Volume Descriptor */
+ case 2: /* Supplementary Volume Descriptor */
+ if (found < type)
+ {
+ *_rootpos = PHYSFS_swapULE32(extent) * 2048;
+ *_rootlen = PHYSFS_swapULE32(datalen);
+ found = type;
+
+ if (found == 2) /* possible Joliet volume */
+ {
+ const PHYSFS_uint8 *s = escapeseqs;
+ *_joliet = !(flags & 1) &&
+ (s[0] == 0x25) && (s[1] == 0x2F) &&
+ ((s[2] == 0x40) || (s[2] == 0x43) || (s[2] == 0x45));
+ } /* if */
+ } /* if */
+ break;
+
+ case 255: /* type 255 terminates the volume descriptor list */
+ done = 1;
+ break;
+
+ default:
+ break; /* skip unknown types. */
+ } /* switch */
+ } /* while */
+
+ BAIL_IF(!found, PHYSFS_ERR_CORRUPT, 0);
+
+ return 1;
+} /* parseVolumeDescriptor */
+
+
+static void *ISO9660_openArchive(PHYSFS_Io *io, const char *filename,
+ int forWriting, int *claimed)
+{
+ PHYSFS_uint64 rootpos = 0;
+ PHYSFS_uint64 len = 0;
+ int joliet = 0;
+ void *unpkarc = NULL;
+
+ assert(io != NULL); /* shouldn't ever happen. */
+
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+
+ if (!parseVolumeDescriptor(io, &rootpos, &len, &joliet, claimed))
+ return NULL;
+
+ /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
+ unpkarc = UNPK_openArchive(io, 1, 0);
+ BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+ if (!iso9660LoadEntries(io, joliet, "", rootpos, rootpos + len, unpkarc))
+ {
+ UNPK_abandonArchive(unpkarc);
+ return NULL;
+ } /* if */
+
+ return unpkarc;
+} /* ISO9660_openArchive */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660 =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "ISO",
+ "ISO9660 image file",
+ "Ryan C. Gordon ",
+ "https://icculus.org/physfs/",
+ 0, /* supportsSymlinks */
+ },
+ ISO9660_openArchive,
+ UNPK_enumerate,
+ UNPK_openRead,
+ UNPK_openWrite,
+ UNPK_openAppend,
+ UNPK_remove,
+ UNPK_mkdir,
+ UNPK_stat,
+ UNPK_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_ISO9660 */
+
+/* end of physfs_archiver_iso9660.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_mvl.c b/third-party/physfs/src/physfs_archiver_mvl.c
new file mode 100644
index 0000000..7a5c432
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_mvl.c
@@ -0,0 +1,110 @@
+/*
+ * MVL support routines for PhysicsFS.
+ *
+ * This driver handles Descent II Movielib archives.
+ *
+ * The file format of MVL is quite easy...
+ *
+ * //MVL File format - Written by Heiko Herrmann
+ * char sig[4] = {'D','M', 'V', 'L'}; // "DMVL"=Descent MoVie Library
+ *
+ * int num_files; // the number of files in this MVL
+ *
+ * struct {
+ * char file_name[13]; // Filename, padded to 13 bytes with 0s
+ * int file_size; // filesize in bytes
+ * }DIR_STRUCT[num_files];
+ *
+ * struct {
+ * char data[file_size]; // The file data
+ * }FILE_STRUCT[num_files];
+ *
+ * (That info is from http://www.descent2.com/ddn/specs/mvl/)
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Bradley Bell.
+ * Based on grp.c by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_MVL
+
+static int mvlLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc)
+{
+ PHYSFS_uint32 pos = 8 + (17 * count); /* past sig+metadata. */
+ PHYSFS_uint32 i;
+
+ for (i = 0; i < count; i++)
+ {
+ PHYSFS_uint32 size;
+ char name[13];
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 13), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0);
+ name[12] = '\0'; /* just in case. */
+ size = PHYSFS_swapULE32(size);
+ BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
+ pos += size;
+ } /* for */
+
+ return 1;
+} /* mvlLoadEntries */
+
+
+static void *MVL_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ PHYSFS_uint8 buf[4];
+ PHYSFS_uint32 count = 0;
+ void *unpkarc;
+
+ assert(io != NULL); /* shouldn't ever happen. */
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, 4), NULL);
+ BAIL_IF(memcmp(buf, "DMVL", 4) != 0, PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ *claimed = 1;
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof(count)), NULL);
+ count = PHYSFS_swapULE32(count);
+
+ unpkarc = UNPK_openArchive(io, 0, 1);
+ BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+ if (!mvlLoadEntries(io, count, unpkarc))
+ {
+ UNPK_abandonArchive(unpkarc);
+ return NULL;
+ } /* if */
+
+ return unpkarc;
+} /* MVL_openArchive */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_MVL =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "MVL",
+ "Descent II Movielib format",
+ "Bradley Bell ",
+ "https://icculus.org/physfs/",
+ 0, /* supportsSymlinks */
+ },
+ MVL_openArchive,
+ UNPK_enumerate,
+ UNPK_openRead,
+ UNPK_openWrite,
+ UNPK_openAppend,
+ UNPK_remove,
+ UNPK_mkdir,
+ UNPK_stat,
+ UNPK_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_MVL */
+
+/* end of physfs_archiver_mvl.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_qpak.c b/third-party/physfs/src/physfs_archiver_qpak.c
new file mode 100644
index 0000000..ddca271
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_qpak.c
@@ -0,0 +1,127 @@
+/*
+ * QPAK support routines for PhysicsFS.
+ *
+ * This archiver handles the archive format utilized by Quake 1 and 2.
+ * Quake3-based games use the PkZip/Info-Zip format (which our
+ * physfs_archiver_zip.c handles).
+ *
+ * ========================================================================
+ *
+ * This format info (in more detail) comes from:
+ * https://web.archive.org/web/20040209101748/http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/pak.txt
+ *
+ * Quake PAK Format
+ *
+ * Header
+ * (4 bytes) signature = 'PACK'
+ * (4 bytes) directory offset
+ * (4 bytes) directory length
+ *
+ * Directory
+ * (56 bytes) file name
+ * (4 bytes) file position
+ * (4 bytes) file length
+ *
+ * ========================================================================
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_QPAK
+
+#define QPAK_SIG 0x4B434150 /* "PACK" in ASCII. */
+
+static int qpakLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc)
+{
+ PHYSFS_uint32 i;
+ for (i = 0; i < count; i++)
+ {
+ PHYSFS_uint32 size;
+ PHYSFS_uint32 pos;
+ char name[56];
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 56), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &pos, 4), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0);
+ size = PHYSFS_swapULE32(size);
+ pos = PHYSFS_swapULE32(pos);
+ BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
+ } /* for */
+
+ return 1;
+} /* qpakLoadEntries */
+
+
+static void *QPAK_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ PHYSFS_uint32 val = 0;
+ PHYSFS_uint32 pos = 0;
+ PHYSFS_uint32 count = 0;
+ void *unpkarc;
+
+ assert(io != NULL); /* shouldn't ever happen. */
+
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &val, 4), NULL);
+ if (PHYSFS_swapULE32(val) != QPAK_SIG)
+ BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ *claimed = 1;
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &val, 4), NULL);
+ pos = PHYSFS_swapULE32(val); /* directory table offset. */
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &val, 4), NULL);
+ count = PHYSFS_swapULE32(val);
+
+ /* corrupted archive? */
+ BAIL_IF((count % 64) != 0, PHYSFS_ERR_CORRUPT, NULL);
+ count /= 64;
+
+ BAIL_IF_ERRPASS(!io->seek(io, pos), NULL);
+
+ /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
+ unpkarc = UNPK_openArchive(io, 1, 0);
+ BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+ if (!qpakLoadEntries(io, count, unpkarc))
+ {
+ UNPK_abandonArchive(unpkarc);
+ return NULL;
+ } /* if */
+
+ return unpkarc;
+} /* QPAK_openArchive */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_QPAK =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "PAK",
+ "Quake I/II format",
+ "Ryan C. Gordon ",
+ "https://icculus.org/physfs/",
+ 0, /* supportsSymlinks */
+ },
+ QPAK_openArchive,
+ UNPK_enumerate,
+ UNPK_openRead,
+ UNPK_openWrite,
+ UNPK_openAppend,
+ UNPK_remove,
+ UNPK_mkdir,
+ UNPK_stat,
+ UNPK_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_QPAK */
+
+/* end of physfs_archiver_qpak.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_slb.c b/third-party/physfs/src/physfs_archiver_slb.c
new file mode 100644
index 0000000..58f3bc8
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_slb.c
@@ -0,0 +1,136 @@
+/*
+ * SLB support routines for PhysicsFS.
+ *
+ * This driver handles SLB archives ("slab files"). This uncompressed format
+ * is used in I-War / Independence War and Independence War: Defiance.
+ *
+ * The format begins with four zero bytes (version?), the file count and the
+ * location of the table of contents. Each ToC entry contains a 64-byte buffer
+ * containing a zero-terminated filename, the offset of the data, and its size.
+ * All the filenames begin with the separator character '\'.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Aleksi Nurmi, based on the GRP archiver by
+ * Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_SLB
+
+static int slbLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc)
+{
+ PHYSFS_uint32 i;
+ for (i = 0; i < count; i++)
+ {
+ PHYSFS_uint32 pos;
+ PHYSFS_uint32 size;
+ char name[64];
+ char backslash;
+ char *ptr;
+
+ /* don't include the '\' in the beginning */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &backslash, 1), 0);
+ BAIL_IF(backslash != '\\', PHYSFS_ERR_CORRUPT, 0);
+
+ /* read the rest of the buffer, 63 bytes */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 63), 0);
+ name[63] = '\0'; /* in case the name lacks the null terminator */
+
+ /* convert backslashes */
+ for (ptr = name; *ptr; ptr++)
+ {
+ if (*ptr == '\\')
+ *ptr = '/';
+ } /* for */
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &pos, 4), 0);
+ pos = PHYSFS_swapULE32(pos);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0);
+ size = PHYSFS_swapULE32(size);
+
+ BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
+ } /* for */
+
+ return 1;
+} /* slbLoadEntries */
+
+
+static void *SLB_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ PHYSFS_uint32 version;
+ PHYSFS_uint32 count;
+ PHYSFS_uint32 tocPos;
+ void *unpkarc;
+
+ /* There's no identifier on an SLB file, so we assume it's _not_ if the
+ file count or tocPos is zero. Beyond that, we'll assume it's
+ bogus/corrupt if the entries' filenames don't start with '\' or the
+ tocPos is past the end of the file (seek will fail). This probably
+ covers all meaningful cases where we would accidentally accept a non-SLB
+ file with this archiver. */
+
+ assert(io != NULL); /* shouldn't ever happen. */
+
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &version, sizeof (version)), NULL);
+ version = PHYSFS_swapULE32(version);
+ BAIL_IF(version != 0, PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof (count)), NULL);
+ count = PHYSFS_swapULE32(count);
+ BAIL_IF(!count, PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ /* offset of the table of contents */
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &tocPos, sizeof (tocPos)), NULL);
+ tocPos = PHYSFS_swapULE32(tocPos);
+ BAIL_IF(!tocPos, PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ /* seek to the table of contents */
+ BAIL_IF_ERRPASS(!io->seek(io, tocPos), NULL);
+
+ /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
+ unpkarc = UNPK_openArchive(io, 1, 0);
+ BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+ if (!slbLoadEntries(io, count, unpkarc))
+ {
+ UNPK_abandonArchive(unpkarc);
+ return NULL;
+ } /* if */
+
+ *claimed = 1; /* oh well. */
+
+ return unpkarc;
+} /* SLB_openArchive */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_SLB =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "SLB",
+ "I-War / Independence War Slab file",
+ "Aleksi Nurmi ",
+ "https://bitbucket.org/ahnurmi/",
+ 0, /* supportsSymlinks */
+ },
+ SLB_openArchive,
+ UNPK_enumerate,
+ UNPK_openRead,
+ UNPK_openWrite,
+ UNPK_openAppend,
+ UNPK_remove,
+ UNPK_mkdir,
+ UNPK_stat,
+ UNPK_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_SLB */
+
+/* end of physfs_archiver_slb.c ... */
diff --git a/third-party/physfs/src/physfs_archiver_unpacked.c b/third-party/physfs/src/physfs_archiver_unpacked.c
new file mode 100644
index 0000000..fbb12a0
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_unpacked.c
@@ -0,0 +1,305 @@
+/*
+ * High-level PhysicsFS archiver for simple unpacked file formats.
+ *
+ * This is a framework that basic archivers build on top of. It's for simple
+ * formats that can just hand back a list of files and the offsets of their
+ * uncompressed data. There are an alarming number of formats like this.
+ *
+ * RULES: Archive entries must be uncompressed. Dirs and files allowed, but no
+ * symlinks, etc. We can relax some of these rules as necessary.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+typedef struct
+{
+ __PHYSFS_DirTree tree;
+ PHYSFS_Io *io;
+} UNPKinfo;
+
+typedef struct
+{
+ __PHYSFS_DirTreeEntry tree;
+ PHYSFS_uint64 startPos;
+ PHYSFS_uint64 size;
+ PHYSFS_sint64 ctime;
+ PHYSFS_sint64 mtime;
+} UNPKentry;
+
+typedef struct
+{
+ PHYSFS_Io *io;
+ UNPKentry *entry;
+ PHYSFS_uint32 curPos;
+} UNPKfileinfo;
+
+
+void UNPK_closeArchive(void *opaque)
+{
+ UNPKinfo *info = ((UNPKinfo *) opaque);
+ if (info)
+ {
+ __PHYSFS_DirTreeDeinit(&info->tree);
+
+ if (info->io)
+ info->io->destroy(info->io);
+
+ allocator.Free(info);
+ } /* if */
+} /* UNPK_closeArchive */
+
+void UNPK_abandonArchive(void *opaque)
+{
+ UNPKinfo *info = ((UNPKinfo *) opaque);
+ if (info)
+ {
+ info->io = NULL;
+ UNPK_closeArchive(info);
+ } /* if */
+} /* UNPK_abandonArchive */
+
+static PHYSFS_sint64 UNPK_read(PHYSFS_Io *io, void *buffer, PHYSFS_uint64 len)
+{
+ UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque;
+ const UNPKentry *entry = finfo->entry;
+ const PHYSFS_uint64 bytesLeft = (PHYSFS_uint64)(entry->size-finfo->curPos);
+ PHYSFS_sint64 rc;
+
+ if (bytesLeft < len)
+ len = bytesLeft;
+
+ rc = finfo->io->read(finfo->io, buffer, len);
+ if (rc > 0)
+ finfo->curPos += (PHYSFS_uint32) rc;
+
+ return rc;
+} /* UNPK_read */
+
+
+static PHYSFS_sint64 UNPK_write(PHYSFS_Io *io, const void *b, PHYSFS_uint64 len)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, -1);
+} /* UNPK_write */
+
+
+static PHYSFS_sint64 UNPK_tell(PHYSFS_Io *io)
+{
+ return ((UNPKfileinfo *) io->opaque)->curPos;
+} /* UNPK_tell */
+
+
+static int UNPK_seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
+{
+ UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque;
+ const UNPKentry *entry = finfo->entry;
+ int rc;
+
+ BAIL_IF(offset >= entry->size, PHYSFS_ERR_PAST_EOF, 0);
+ rc = finfo->io->seek(finfo->io, entry->startPos + offset);
+ if (rc)
+ finfo->curPos = (PHYSFS_uint32) offset;
+
+ return rc;
+} /* UNPK_seek */
+
+
+static PHYSFS_sint64 UNPK_length(PHYSFS_Io *io)
+{
+ const UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque;
+ return ((PHYSFS_sint64) finfo->entry->size);
+} /* UNPK_length */
+
+
+static PHYSFS_Io *UNPK_duplicate(PHYSFS_Io *_io)
+{
+ UNPKfileinfo *origfinfo = (UNPKfileinfo *) _io->opaque;
+ PHYSFS_Io *io = NULL;
+ PHYSFS_Io *retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ UNPKfileinfo *finfo = (UNPKfileinfo *) allocator.Malloc(sizeof (UNPKfileinfo));
+ GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_duplicate_failed);
+ GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_duplicate_failed);
+
+ io = origfinfo->io->duplicate(origfinfo->io);
+ if (!io) goto UNPK_duplicate_failed;
+ finfo->io = io;
+ finfo->entry = origfinfo->entry;
+ finfo->curPos = 0;
+ memcpy(retval, _io, sizeof (PHYSFS_Io));
+ retval->opaque = finfo;
+ return retval;
+
+UNPK_duplicate_failed:
+ if (finfo != NULL) allocator.Free(finfo);
+ if (retval != NULL) allocator.Free(retval);
+ if (io != NULL) io->destroy(io);
+ return NULL;
+} /* UNPK_duplicate */
+
+static int UNPK_flush(PHYSFS_Io *io) { return 1; /* no write support. */ }
+
+static void UNPK_destroy(PHYSFS_Io *io)
+{
+ UNPKfileinfo *finfo = (UNPKfileinfo *) io->opaque;
+ finfo->io->destroy(finfo->io);
+ allocator.Free(finfo);
+ allocator.Free(io);
+} /* UNPK_destroy */
+
+
+static const PHYSFS_Io UNPK_Io =
+{
+ CURRENT_PHYSFS_IO_API_VERSION, NULL,
+ UNPK_read,
+ UNPK_write,
+ UNPK_seek,
+ UNPK_tell,
+ UNPK_length,
+ UNPK_duplicate,
+ UNPK_flush,
+ UNPK_destroy
+};
+
+
+static inline UNPKentry *findEntry(UNPKinfo *info, const char *path)
+{
+ return (UNPKentry *) __PHYSFS_DirTreeFind(&info->tree, path);
+} /* findEntry */
+
+
+PHYSFS_Io *UNPK_openRead(void *opaque, const char *name)
+{
+ PHYSFS_Io *retval = NULL;
+ UNPKinfo *info = (UNPKinfo *) opaque;
+ UNPKfileinfo *finfo = NULL;
+ UNPKentry *entry = findEntry(info, name);
+
+ BAIL_IF_ERRPASS(!entry, NULL);
+ BAIL_IF(entry->tree.isdir, PHYSFS_ERR_NOT_A_FILE, NULL);
+
+ retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_openRead_failed);
+
+ finfo = (UNPKfileinfo *) allocator.Malloc(sizeof (UNPKfileinfo));
+ GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, UNPK_openRead_failed);
+
+ finfo->io = info->io->duplicate(info->io);
+ GOTO_IF_ERRPASS(!finfo->io, UNPK_openRead_failed);
+
+ if (!finfo->io->seek(finfo->io, entry->startPos))
+ goto UNPK_openRead_failed;
+
+ finfo->curPos = 0;
+ finfo->entry = entry;
+
+ memcpy(retval, &UNPK_Io, sizeof (*retval));
+ retval->opaque = finfo;
+ return retval;
+
+UNPK_openRead_failed:
+ if (finfo != NULL)
+ {
+ if (finfo->io != NULL)
+ finfo->io->destroy(finfo->io);
+ allocator.Free(finfo);
+ } /* if */
+
+ if (retval != NULL)
+ allocator.Free(retval);
+
+ return NULL;
+} /* UNPK_openRead */
+
+
+PHYSFS_Io *UNPK_openWrite(void *opaque, const char *name)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, NULL);
+} /* UNPK_openWrite */
+
+
+PHYSFS_Io *UNPK_openAppend(void *opaque, const char *name)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, NULL);
+} /* UNPK_openAppend */
+
+
+int UNPK_remove(void *opaque, const char *name)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, 0);
+} /* UNPK_remove */
+
+
+int UNPK_mkdir(void *opaque, const char *name)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, 0);
+} /* UNPK_mkdir */
+
+
+int UNPK_stat(void *opaque, const char *path, PHYSFS_Stat *stat)
+{
+ UNPKinfo *info = (UNPKinfo *) opaque;
+ const UNPKentry *entry = findEntry(info, path);
+
+ BAIL_IF_ERRPASS(!entry, 0);
+
+ if (entry->tree.isdir)
+ {
+ stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+ stat->filesize = 0;
+ } /* if */
+ else
+ {
+ stat->filetype = PHYSFS_FILETYPE_REGULAR;
+ stat->filesize = entry->size;
+ } /* else */
+
+ stat->modtime = entry->mtime;
+ stat->createtime = entry->ctime;
+ stat->accesstime = -1;
+ stat->readonly = 1;
+
+ return 1;
+} /* UNPK_stat */
+
+
+void *UNPK_addEntry(void *opaque, char *name, const int isdir,
+ const PHYSFS_sint64 ctime, const PHYSFS_sint64 mtime,
+ const PHYSFS_uint64 pos, const PHYSFS_uint64 len)
+{
+ UNPKinfo *info = (UNPKinfo *) opaque;
+ UNPKentry *entry;
+
+ entry = (UNPKentry *) __PHYSFS_DirTreeAdd(&info->tree, name, isdir);
+ BAIL_IF_ERRPASS(!entry, NULL);
+
+ entry->startPos = isdir ? 0 : pos;
+ entry->size = isdir ? 0 : len;
+ entry->ctime = ctime;
+ entry->mtime = mtime;
+
+ return entry;
+} /* UNPK_addEntry */
+
+
+void *UNPK_openArchive(PHYSFS_Io *io, const int case_sensitive, const int only_usascii)
+{
+ UNPKinfo *info = (UNPKinfo *) allocator.Malloc(sizeof (UNPKinfo));
+ BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+
+ if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (UNPKentry), case_sensitive, only_usascii))
+ {
+ allocator.Free(info);
+ return NULL;
+ } /* if */
+
+ info->io = io;
+
+ return info;
+} /* UNPK_openArchive */
+
+/* end of physfs_archiver_unpacked.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_vdf.c b/third-party/physfs/src/physfs_archiver_vdf.c
new file mode 100644
index 0000000..6d3a23f
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_vdf.c
@@ -0,0 +1,169 @@
+/*
+ * VDF support routines for PhysicsFS.
+ *
+ * This driver handles Gothic I/II VDF archives.
+ * This format (but not this driver) was designed by Piranha Bytes for
+ * use wih the ZenGin engine.
+ *
+ * This file was written by Francesco Bertolaccini, based on the UNPK archiver
+ * by Ryan C. Gordon and the works of degenerated1123 and Nico Bendlin.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_VDF
+
+#include
+
+#define VDF_COMMENT_LENGTH 256
+#define VDF_SIGNATURE_LENGTH 16
+#define VDF_ENTRY_NAME_LENGTH 64
+#define VDF_ENTRY_DIR 0x80000000
+
+static const char* VDF_SIGNATURE_G1 = "PSVDSC_V2.00\r\n\r\n";
+static const char* VDF_SIGNATURE_G2 = "PSVDSC_V2.00\n\r\n\r";
+
+
+static inline int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val)
+{
+ PHYSFS_uint32 v;
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0);
+ *val = PHYSFS_swapULE32(v);
+ return 1;
+} /* readui32 */
+
+
+static PHYSFS_sint64 vdfDosTimeToEpoch(const PHYSFS_uint32 dostime)
+{
+ /* VDF stores timestamps as 32bit DOS dates: the seconds are counted in
+ 2-seconds intervals and the years are counted since 1 Jan. 1980 */
+ struct tm t;
+ memset(&t, '\0', sizeof (t));
+ t.tm_year = ((int) ((dostime >> 25) & 0x7F)) + 80; /* 1980 to 1900 */
+ t.tm_mon = ((int) ((dostime >> 21) & 0xF)) - 1; /* 1-12 to 0-11 */
+ t.tm_mday = (int) ((dostime >> 16) & 0x1F);
+ t.tm_hour = (int) ((dostime >> 11) & 0x1F);
+ t.tm_min = (int) ((dostime >> 5) & 0x3F);
+ t.tm_sec = ((int) ((dostime >> 0) & 0x1F)) * 2; /* 2 seconds to 1. */
+ return (PHYSFS_sint64) mktime(&t);
+} /* vdfDosTimeToEpoch */
+
+
+static int vdfLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count,
+ const PHYSFS_sint64 ts, void *arc)
+{
+ PHYSFS_uint32 i;
+
+ for (i = 0; i < count; i++)
+ {
+ char name[VDF_ENTRY_NAME_LENGTH + 1];
+ int namei;
+ PHYSFS_uint32 jump, size, type, attr;
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, sizeof (name) - 1), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &jump), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &size), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &type), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &attr), 0);
+
+ /* Trim whitespace off the end of the filename */
+ name[VDF_ENTRY_NAME_LENGTH] = '\0'; /* always null-terminated. */
+ for (namei = VDF_ENTRY_NAME_LENGTH - 1; namei >= 0; namei--)
+ {
+ /* We assume the filenames are low-ASCII; consider the archive
+ corrupt if we see something above 127, since we don't know the
+ encoding. (We can change this later if we find out these exist
+ and are intended to be, say, latin-1 or UTF-8 encoding). */
+ BAIL_IF(((PHYSFS_uint8) name[namei]) > 127, PHYSFS_ERR_CORRUPT, 0);
+
+ if (name[namei] == ' ')
+ name[namei] = '\0';
+ else
+ break;
+ } /* for */
+
+ BAIL_IF(!name[0], PHYSFS_ERR_CORRUPT, 0);
+ if (!(type & VDF_ENTRY_DIR)) {
+ BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, ts, ts, jump, size), 0);
+ }
+ } /* for */
+
+ return 1;
+} /* vdfLoadEntries */
+
+
+static void *VDF_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ PHYSFS_uint8 ignore[16];
+ PHYSFS_uint8 sig[VDF_SIGNATURE_LENGTH];
+ PHYSFS_uint32 count, timestamp, version, dataSize, rootCatOffset;
+ void *unpkarc;
+
+ assert(io != NULL); /* shouldn't ever happen. */
+
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+
+ /* skip the 256-byte comment field. */
+ BAIL_IF_ERRPASS(!io->seek(io, VDF_COMMENT_LENGTH), NULL);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, sig, sizeof (sig)), NULL);
+
+ if ((memcmp(sig, VDF_SIGNATURE_G1, VDF_SIGNATURE_LENGTH) != 0) &&
+ (memcmp(sig, VDF_SIGNATURE_G2, VDF_SIGNATURE_LENGTH) != 0))
+ {
+ BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+ } /* if */
+
+ *claimed = 1;
+
+ BAIL_IF_ERRPASS(!readui32(io, &count), NULL);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), NULL); /* numFiles */
+ BAIL_IF_ERRPASS(!readui32(io, ×tamp), NULL);
+ BAIL_IF_ERRPASS(!readui32(io, &dataSize), NULL); /* dataSize */
+ BAIL_IF_ERRPASS(!readui32(io, &rootCatOffset), NULL); /* rootCatOff */
+ BAIL_IF_ERRPASS(!readui32(io, &version), NULL);
+
+ BAIL_IF(version != 0x50, PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ BAIL_IF_ERRPASS(!io->seek(io, rootCatOffset), NULL);
+
+ /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
+ unpkarc = UNPK_openArchive(io, 1, 0);
+ BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+ if (!vdfLoadEntries(io, count, vdfDosTimeToEpoch(timestamp), unpkarc))
+ {
+ UNPK_abandonArchive(unpkarc);
+ return NULL;
+ } /* if */
+
+ return unpkarc;
+} /* VDF_openArchive */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_VDF =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "VDF",
+ "Gothic I/II engine format",
+ "Francesco Bertolaccini ",
+ "https://github.com/frabert",
+ 0, /* supportsSymlinks */
+ },
+ VDF_openArchive,
+ UNPK_enumerate,
+ UNPK_openRead,
+ UNPK_openWrite,
+ UNPK_openAppend,
+ UNPK_remove,
+ UNPK_mkdir,
+ UNPK_stat,
+ UNPK_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_VDF */
+
+/* end of physfs_archiver_vdf.c ... */
diff --git a/third-party/physfs/src/physfs_archiver_wad.c b/third-party/physfs/src/physfs_archiver_wad.c
new file mode 100644
index 0000000..d3ae045
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_wad.c
@@ -0,0 +1,135 @@
+/*
+ * WAD support routines for PhysicsFS.
+ *
+ * This driver handles DOOM engine archives ("wads").
+ * This format (but not this driver) was designed by id Software for use
+ * with the DOOM engine.
+ * The specs of the format are from the unofficial doom specs v1.666
+ * found here: http://www.gamers.org/dhs/helpdocs/dmsp1666.html
+ * The format of the archive: (from the specs)
+ *
+ * A WAD file has three parts:
+ * (1) a twelve-byte header
+ * (2) one or more "lumps"
+ * (3) a directory or "info table" that contains the names, offsets, and
+ * sizes of all the lumps in the WAD
+ *
+ * The header consists of three four-byte parts:
+ * (a) an ASCII string which must be either "IWAD" or "PWAD"
+ * (b) a uint32 which is the number of lumps in the wad
+ * (c) a uint32 which is the file offset to the start of
+ * the directory
+ *
+ * The directory has one 16-byte entry for every lump. Each entry consists
+ * of three parts:
+ *
+ * (a) a uint32, the file offset to the start of the lump
+ * (b) a uint32, the size of the lump in bytes
+ * (c) an 8-byte ASCII string, the name of the lump, padded with zeros.
+ * For example, the "DEMO1" entry in hexadecimal would be
+ * (44 45 4D 4F 31 00 00 00)
+ *
+ * Note that there is no way to tell if an opened WAD archive is a
+ * IWAD or PWAD with this archiver.
+ * I couldn't think of a way to provide that information, without being too
+ * hacky.
+ * I don't think it's really that important though.
+ *
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Travis Wells, based on the GRP archiver by
+ * Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_WAD
+
+static int wadLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count, void *arc)
+{
+ PHYSFS_uint32 i;
+ for (i = 0; i < count; i++)
+ {
+ PHYSFS_uint32 pos;
+ PHYSFS_uint32 size;
+ char name[9];
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &pos, 4), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &size, 4), 0);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, 8), 0);
+
+ name[8] = '\0'; /* name might not be null-terminated in file. */
+ size = PHYSFS_swapULE32(size);
+ pos = PHYSFS_swapULE32(pos);
+ BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, -1, -1, pos, size), 0);
+ } /* for */
+
+ return 1;
+} /* wadLoadEntries */
+
+
+static void *WAD_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ PHYSFS_uint8 buf[4];
+ PHYSFS_uint32 count;
+ PHYSFS_uint32 directoryOffset;
+ void *unpkarc;
+
+ assert(io != NULL); /* shouldn't ever happen. */
+
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, buf, sizeof (buf)), NULL);
+ if ((memcmp(buf, "IWAD", 4) != 0) && (memcmp(buf, "PWAD", 4) != 0))
+ BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
+
+ *claimed = 1;
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &count, sizeof (count)), NULL);
+ count = PHYSFS_swapULE32(count);
+
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &directoryOffset, 4), 0);
+ directoryOffset = PHYSFS_swapULE32(directoryOffset);
+
+ BAIL_IF_ERRPASS(!io->seek(io, directoryOffset), 0);
+
+ unpkarc = UNPK_openArchive(io, 0, 1);
+ BAIL_IF_ERRPASS(!unpkarc, NULL);
+
+ if (!wadLoadEntries(io, count, unpkarc))
+ {
+ UNPK_abandonArchive(unpkarc);
+ return NULL;
+ } /* if */
+
+ return unpkarc;
+} /* WAD_openArchive */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_WAD =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "WAD",
+ "DOOM engine format",
+ "Travis Wells ",
+ "http://www.3dmm2.com/doom/",
+ 0, /* supportsSymlinks */
+ },
+ WAD_openArchive,
+ UNPK_enumerate,
+ UNPK_openRead,
+ UNPK_openWrite,
+ UNPK_openAppend,
+ UNPK_remove,
+ UNPK_mkdir,
+ UNPK_stat,
+ UNPK_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_WAD */
+
+/* end of physfs_archiver_wad.c ... */
+
diff --git a/third-party/physfs/src/physfs_archiver_zip.c b/third-party/physfs/src/physfs_archiver_zip.c
new file mode 100644
index 0000000..7327a61
--- /dev/null
+++ b/third-party/physfs/src/physfs_archiver_zip.c
@@ -0,0 +1,1718 @@
+/*
+ * ZIP support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon, with some peeking at "unzip.c"
+ * by Gilles Vollant.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#if PHYSFS_SUPPORTS_ZIP
+
+#include
+#include
+
+#if (PHYSFS_BYTEORDER == PHYSFS_LIL_ENDIAN)
+#define MINIZ_LITTLE_ENDIAN 1
+#else
+#define MINIZ_LITTLE_ENDIAN 0
+#endif
+#include "physfs_miniz.h"
+
+/*
+ * A buffer of ZIP_READBUFSIZE is allocated for each compressed file opened,
+ * and is freed when you close the file; compressed data is read into
+ * this buffer, and then is decompressed into the buffer passed to
+ * PHYSFS_read().
+ *
+ * Uncompressed entries in a zipfile do not allocate this buffer; they just
+ * read data directly into the buffer passed to PHYSFS_read().
+ *
+ * Depending on your speed and memory requirements, you should tweak this
+ * value.
+ */
+#define ZIP_READBUFSIZE (16 * 1024)
+
+
+/*
+ * Entries are "unresolved" until they are first opened. At that time,
+ * local file headers parsed/validated, data offsets will be updated to look
+ * at the actual file data instead of the header, and symlinks will be
+ * followed and optimized. This means that we don't seek and read around the
+ * archive until forced to do so, and after the first time, we had to do
+ * less reading and parsing, which is very CD-ROM friendly.
+ */
+typedef enum
+{
+ ZIP_UNRESOLVED_FILE,
+ ZIP_UNRESOLVED_SYMLINK,
+ ZIP_RESOLVING,
+ ZIP_RESOLVED,
+ ZIP_DIRECTORY,
+ ZIP_BROKEN_FILE,
+ ZIP_BROKEN_SYMLINK
+} ZipResolveType;
+
+
+/*
+ * One ZIPentry is kept for each file in an open ZIP archive.
+ */
+typedef struct _ZIPentry
+{
+ __PHYSFS_DirTreeEntry tree; /* manages directory tree */
+ struct _ZIPentry *symlink; /* NULL or file we symlink to */
+ ZipResolveType resolved; /* Have we resolved file/symlink? */
+ PHYSFS_uint64 offset; /* offset of data in archive */
+ PHYSFS_uint16 version; /* version made by */
+ PHYSFS_uint16 version_needed; /* version needed to extract */
+ PHYSFS_uint16 general_bits; /* general purpose bits */
+ PHYSFS_uint16 compression_method; /* compression method */
+ PHYSFS_uint32 crc; /* crc-32 */
+ PHYSFS_uint64 compressed_size; /* compressed size */
+ PHYSFS_uint64 uncompressed_size; /* uncompressed size */
+ PHYSFS_sint64 last_mod_time; /* last file mod time */
+ PHYSFS_uint32 dos_mod_time; /* original MS-DOS style mod time */
+} ZIPentry;
+
+/*
+ * One ZIPinfo is kept for each open ZIP archive.
+ */
+typedef struct
+{
+ __PHYSFS_DirTree tree; /* manages directory tree. */
+ PHYSFS_Io *io; /* the i/o interface for this archive. */
+ int zip64; /* non-zero if this is a Zip64 archive. */
+ int has_crypto; /* non-zero if any entry uses encryption. */
+} ZIPinfo;
+
+/*
+ * One ZIPfileinfo is kept for each open file in a ZIP archive.
+ */
+typedef struct
+{
+ ZIPentry *entry; /* Info on file. */
+ PHYSFS_Io *io; /* physical file handle. */
+ PHYSFS_uint32 compressed_position; /* offset in compressed data. */
+ PHYSFS_uint32 uncompressed_position; /* tell() position. */
+ PHYSFS_uint8 *buffer; /* decompression buffer. */
+ PHYSFS_uint32 crypto_keys[3]; /* for "traditional" crypto. */
+ PHYSFS_uint32 initial_crypto_keys[3]; /* for "traditional" crypto. */
+ z_stream stream; /* zlib stream state. */
+} ZIPfileinfo;
+
+
+/* Magic numbers... */
+#define ZIP_LOCAL_FILE_SIG 0x04034b50
+#define ZIP_CENTRAL_DIR_SIG 0x02014b50
+#define ZIP_END_OF_CENTRAL_DIR_SIG 0x06054b50
+#define ZIP64_END_OF_CENTRAL_DIR_SIG 0x06064b50
+#define ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG 0x07064b50
+#define ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG 0x0001
+
+/* compression methods... */
+#define COMPMETH_NONE 0
+/* ...and others... */
+
+
+#define UNIX_FILETYPE_MASK 0170000
+#define UNIX_FILETYPE_SYMLINK 0120000
+
+#define ZIP_GENERAL_BITS_TRADITIONAL_CRYPTO (1 << 0)
+#define ZIP_GENERAL_BITS_IGNORE_LOCAL_HEADER (1 << 3)
+
+/* support for "traditional" PKWARE encryption. */
+static int zip_entry_is_tradional_crypto(const ZIPentry *entry)
+{
+ return (entry->general_bits & ZIP_GENERAL_BITS_TRADITIONAL_CRYPTO) != 0;
+} /* zip_entry_is_traditional_crypto */
+
+static int zip_entry_ignore_local_header(const ZIPentry *entry)
+{
+ return (entry->general_bits & ZIP_GENERAL_BITS_IGNORE_LOCAL_HEADER) != 0;
+} /* zip_entry_is_traditional_crypto */
+
+static PHYSFS_uint32 zip_crypto_crc32(const PHYSFS_uint32 crc, const PHYSFS_uint8 val)
+{
+ int i;
+ PHYSFS_uint32 xorval = (crc ^ ((PHYSFS_uint32) val)) & 0xFF;
+ for (i = 0; i < 8; i++)
+ xorval = ((xorval & 1) ? (0xEDB88320 ^ (xorval >> 1)) : (xorval >> 1));
+ return xorval ^ (crc >> 8);
+} /* zip_crc32 */
+
+static void zip_update_crypto_keys(PHYSFS_uint32 *keys, const PHYSFS_uint8 val)
+{
+ keys[0] = zip_crypto_crc32(keys[0], val);
+ keys[1] = keys[1] + (keys[0] & 0x000000FF);
+ keys[1] = (keys[1] * 134775813) + 1;
+ keys[2] = zip_crypto_crc32(keys[2], (PHYSFS_uint8) ((keys[1] >> 24) & 0xFF));
+} /* zip_update_crypto_keys */
+
+static PHYSFS_uint8 zip_decrypt_byte(const PHYSFS_uint32 *keys)
+{
+ const PHYSFS_uint16 tmp = keys[2] | 2;
+ return (PHYSFS_uint8) ((tmp * (tmp ^ 1)) >> 8);
+} /* zip_decrypt_byte */
+
+static PHYSFS_sint64 zip_read_decrypt(ZIPfileinfo *finfo, void *buf, PHYSFS_uint64 len)
+{
+ PHYSFS_Io *io = finfo->io;
+ const PHYSFS_sint64 br = io->read(io, buf, len);
+
+ /* Decompression the new data if necessary. */
+ if (zip_entry_is_tradional_crypto(finfo->entry) && (br > 0))
+ {
+ PHYSFS_uint32 *keys = finfo->crypto_keys;
+ PHYSFS_uint8 *ptr = (PHYSFS_uint8 *) buf;
+ PHYSFS_sint64 i;
+ for (i = 0; i < br; i++, ptr++)
+ {
+ const PHYSFS_uint8 ch = *ptr ^ zip_decrypt_byte(keys);
+ zip_update_crypto_keys(keys, ch);
+ *ptr = ch;
+ } /* for */
+ } /* if */
+
+ return br;
+} /* zip_read_decrypt */
+
+static int zip_prep_crypto_keys(ZIPfileinfo *finfo, const PHYSFS_uint8 *crypto_header, const PHYSFS_uint8 *password)
+{
+ /* It doesn't appear to be documented in PKWare's APPNOTE.TXT, but you
+ need to use a different byte in the header to verify the password
+ if general purpose bit 3 is set. Discovered this from Info-Zip.
+ That's what the (verifier) value is doing, below. */
+
+ PHYSFS_uint32 *keys = finfo->crypto_keys;
+ const ZIPentry *entry = finfo->entry;
+ const int usedate = zip_entry_ignore_local_header(entry);
+ const PHYSFS_uint8 verifier = (PHYSFS_uint8) ((usedate ? (entry->dos_mod_time >> 8) : (entry->crc >> 24)) & 0xFF);
+ PHYSFS_uint8 finalbyte = 0;
+ int i = 0;
+
+ /* initialize vector with defaults, then password, then header. */
+ keys[0] = 305419896;
+ keys[1] = 591751049;
+ keys[2] = 878082192;
+
+ while (*password)
+ zip_update_crypto_keys(keys, *(password++));
+
+ for (i = 0; i < 12; i++)
+ {
+ const PHYSFS_uint8 c = crypto_header[i] ^ zip_decrypt_byte(keys);
+ zip_update_crypto_keys(keys, c);
+ finalbyte = c;
+ } /* for */
+
+ /* you have a 1/256 chance of passing this test incorrectly. :/ */
+ if (finalbyte != verifier)
+ BAIL(PHYSFS_ERR_BAD_PASSWORD, 0);
+
+ /* save the initial vector for seeking purposes. Not secure!! */
+ memcpy(finfo->initial_crypto_keys, finfo->crypto_keys, 12);
+ return 1;
+} /* zip_prep_crypto_keys */
+
+
+/*
+ * Bridge physfs allocation functions to zlib's format...
+ */
+static voidpf zlibPhysfsAlloc(voidpf opaque, uInt items, uInt size)
+{
+ return ((PHYSFS_Allocator *) opaque)->Malloc(items * size);
+} /* zlibPhysfsAlloc */
+
+/*
+ * Bridge physfs allocation functions to zlib's format...
+ */
+static void zlibPhysfsFree(voidpf opaque, voidpf address)
+{
+ ((PHYSFS_Allocator *) opaque)->Free(address);
+} /* zlibPhysfsFree */
+
+
+/*
+ * Construct a new z_stream to a sane state.
+ */
+static void initializeZStream(z_stream *pstr)
+{
+ memset(pstr, '\0', sizeof (z_stream));
+ pstr->zalloc = zlibPhysfsAlloc;
+ pstr->zfree = zlibPhysfsFree;
+ pstr->opaque = &allocator;
+} /* initializeZStream */
+
+
+static PHYSFS_ErrorCode zlib_error_code(int rc)
+{
+ switch (rc)
+ {
+ case Z_OK: return PHYSFS_ERR_OK; /* not an error. */
+ case Z_STREAM_END: return PHYSFS_ERR_OK; /* not an error. */
+ case Z_ERRNO: return PHYSFS_ERR_IO;
+ case Z_MEM_ERROR: return PHYSFS_ERR_OUT_OF_MEMORY;
+ default: return PHYSFS_ERR_CORRUPT;
+ } /* switch */
+} /* zlib_error_string */
+
+
+/*
+ * Wrap all zlib calls in this, so the physfs error state is set appropriately.
+ */
+static int zlib_err(const int rc)
+{
+ PHYSFS_setErrorCode(zlib_error_code(rc));
+ return rc;
+} /* zlib_err */
+
+/*
+ * Read an unsigned 64-bit int and swap to native byte order.
+ */
+static int readui64(PHYSFS_Io *io, PHYSFS_uint64 *val)
+{
+ PHYSFS_uint64 v;
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0);
+ *val = PHYSFS_swapULE64(v);
+ return 1;
+} /* readui64 */
+
+/*
+ * Read an unsigned 32-bit int and swap to native byte order.
+ */
+static int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val)
+{
+ PHYSFS_uint32 v;
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0);
+ *val = PHYSFS_swapULE32(v);
+ return 1;
+} /* readui32 */
+
+
+/*
+ * Read an unsigned 16-bit int and swap to native byte order.
+ */
+static int readui16(PHYSFS_Io *io, PHYSFS_uint16 *val)
+{
+ PHYSFS_uint16 v;
+ BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0);
+ *val = PHYSFS_swapULE16(v);
+ return 1;
+} /* readui16 */
+
+
+static PHYSFS_sint64 ZIP_read(PHYSFS_Io *_io, void *buf, PHYSFS_uint64 len)
+{
+ ZIPfileinfo *finfo = (ZIPfileinfo *) _io->opaque;
+ ZIPentry *entry = finfo->entry;
+ PHYSFS_sint64 retval = 0;
+ PHYSFS_sint64 maxread = (PHYSFS_sint64) len;
+ PHYSFS_sint64 avail = entry->uncompressed_size -
+ finfo->uncompressed_position;
+
+ if (avail < maxread)
+ maxread = avail;
+
+ BAIL_IF_ERRPASS(maxread == 0, 0); /* quick rejection. */
+
+ if (entry->compression_method == COMPMETH_NONE)
+ retval = zip_read_decrypt(finfo, buf, maxread);
+ else
+ {
+ finfo->stream.next_out = buf;
+ finfo->stream.avail_out = (uInt) maxread;
+
+ while (retval < maxread)
+ {
+ const PHYSFS_uint32 before = (PHYSFS_uint32) finfo->stream.total_out;
+ int rc;
+
+ if (finfo->stream.avail_in == 0)
+ {
+ PHYSFS_sint64 br;
+
+ br = entry->compressed_size - finfo->compressed_position;
+ if (br > 0)
+ {
+ if (br > ZIP_READBUFSIZE)
+ br = ZIP_READBUFSIZE;
+
+ br = zip_read_decrypt(finfo, finfo->buffer, (PHYSFS_uint64) br);
+ if (br <= 0)
+ break;
+
+ finfo->compressed_position += (PHYSFS_uint32) br;
+ finfo->stream.next_in = finfo->buffer;
+ finfo->stream.avail_in = (unsigned int) br;
+ } /* if */
+ } /* if */
+
+ rc = zlib_err(inflate(&finfo->stream, Z_SYNC_FLUSH));
+ retval += (finfo->stream.total_out - before);
+
+ if (rc != Z_OK)
+ break;
+ } /* while */
+ } /* else */
+
+ if (retval > 0)
+ finfo->uncompressed_position += (PHYSFS_uint32) retval;
+
+ return retval;
+} /* ZIP_read */
+
+
+static PHYSFS_sint64 ZIP_write(PHYSFS_Io *io, const void *b, PHYSFS_uint64 len)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, -1);
+} /* ZIP_write */
+
+
+static PHYSFS_sint64 ZIP_tell(PHYSFS_Io *io)
+{
+ return ((ZIPfileinfo *) io->opaque)->uncompressed_position;
+} /* ZIP_tell */
+
+
+static int ZIP_seek(PHYSFS_Io *_io, PHYSFS_uint64 offset)
+{
+ ZIPfileinfo *finfo = (ZIPfileinfo *) _io->opaque;
+ ZIPentry *entry = finfo->entry;
+ PHYSFS_Io *io = finfo->io;
+ const int encrypted = zip_entry_is_tradional_crypto(entry);
+
+ BAIL_IF(offset > entry->uncompressed_size, PHYSFS_ERR_PAST_EOF, 0);
+
+ if (!encrypted && (entry->compression_method == COMPMETH_NONE))
+ {
+ PHYSFS_sint64 newpos = offset + entry->offset;
+ BAIL_IF_ERRPASS(!io->seek(io, newpos), 0);
+ finfo->uncompressed_position = (PHYSFS_uint32) offset;
+ } /* if */
+
+ else
+ {
+ /*
+ * If seeking backwards, we need to redecode the file
+ * from the start and throw away the compressed bits until we hit
+ * the offset we need. If seeking forward, we still need to
+ * decode, but we don't rewind first.
+ */
+ if (offset < finfo->uncompressed_position)
+ {
+ /* we do a copy so state is sane if inflateInit2() fails. */
+ z_stream str;
+ initializeZStream(&str);
+ if (zlib_err(inflateInit2(&str, -MAX_WBITS)) != Z_OK)
+ return 0;
+
+ if (!io->seek(io, entry->offset + (encrypted ? 12 : 0)))
+ return 0;
+
+ inflateEnd(&finfo->stream);
+ memcpy(&finfo->stream, &str, sizeof (z_stream));
+ finfo->uncompressed_position = finfo->compressed_position = 0;
+
+ if (encrypted)
+ memcpy(finfo->crypto_keys, finfo->initial_crypto_keys, 12);
+ } /* if */
+
+ while (finfo->uncompressed_position != offset)
+ {
+ PHYSFS_uint8 buf[512];
+ PHYSFS_uint32 maxread;
+
+ maxread = (PHYSFS_uint32) (offset - finfo->uncompressed_position);
+ if (maxread > sizeof (buf))
+ maxread = sizeof (buf);
+
+ if (ZIP_read(_io, buf, maxread) != maxread)
+ return 0;
+ } /* while */
+ } /* else */
+
+ return 1;
+} /* ZIP_seek */
+
+
+static PHYSFS_sint64 ZIP_length(PHYSFS_Io *io)
+{
+ const ZIPfileinfo *finfo = (ZIPfileinfo *) io->opaque;
+ return (PHYSFS_sint64) finfo->entry->uncompressed_size;
+} /* ZIP_length */
+
+
+static PHYSFS_Io *zip_get_io(PHYSFS_Io *io, ZIPinfo *inf, ZIPentry *entry);
+
+static PHYSFS_Io *ZIP_duplicate(PHYSFS_Io *io)
+{
+ ZIPfileinfo *origfinfo = (ZIPfileinfo *) io->opaque;
+ PHYSFS_Io *retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ ZIPfileinfo *finfo = (ZIPfileinfo *) allocator.Malloc(sizeof (ZIPfileinfo));
+ GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, failed);
+ GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, failed);
+ memset(finfo, '\0', sizeof (*finfo));
+
+ finfo->entry = origfinfo->entry;
+ finfo->io = zip_get_io(origfinfo->io, NULL, finfo->entry);
+ GOTO_IF_ERRPASS(!finfo->io, failed);
+
+ initializeZStream(&finfo->stream);
+ if (finfo->entry->compression_method != COMPMETH_NONE)
+ {
+ finfo->buffer = (PHYSFS_uint8 *) allocator.Malloc(ZIP_READBUFSIZE);
+ GOTO_IF(!finfo->buffer, PHYSFS_ERR_OUT_OF_MEMORY, failed);
+ if (zlib_err(inflateInit2(&finfo->stream, -MAX_WBITS)) != Z_OK)
+ goto failed;
+ } /* if */
+
+ memcpy(retval, io, sizeof (PHYSFS_Io));
+ retval->opaque = finfo;
+ return retval;
+
+failed:
+ if (finfo != NULL)
+ {
+ if (finfo->io != NULL)
+ finfo->io->destroy(finfo->io);
+
+ if (finfo->buffer != NULL)
+ {
+ allocator.Free(finfo->buffer);
+ inflateEnd(&finfo->stream);
+ } /* if */
+
+ allocator.Free(finfo);
+ } /* if */
+
+ if (retval != NULL)
+ allocator.Free(retval);
+
+ return NULL;
+} /* ZIP_duplicate */
+
+static int ZIP_flush(PHYSFS_Io *io) { return 1; /* no write support. */ }
+
+static void ZIP_destroy(PHYSFS_Io *io)
+{
+ ZIPfileinfo *finfo = (ZIPfileinfo *) io->opaque;
+ finfo->io->destroy(finfo->io);
+
+ if (finfo->entry->compression_method != COMPMETH_NONE)
+ inflateEnd(&finfo->stream);
+
+ if (finfo->buffer != NULL)
+ allocator.Free(finfo->buffer);
+
+ allocator.Free(finfo);
+ allocator.Free(io);
+} /* ZIP_destroy */
+
+
+static const PHYSFS_Io ZIP_Io =
+{
+ CURRENT_PHYSFS_IO_API_VERSION, NULL,
+ ZIP_read,
+ ZIP_write,
+ ZIP_seek,
+ ZIP_tell,
+ ZIP_length,
+ ZIP_duplicate,
+ ZIP_flush,
+ ZIP_destroy
+};
+
+
+
+static PHYSFS_sint64 zip_find_end_of_central_dir(PHYSFS_Io *io, PHYSFS_sint64 *len)
+{
+ PHYSFS_uint8 buf[256];
+ PHYSFS_uint8 extra[4] = { 0, 0, 0, 0 };
+ PHYSFS_sint32 i = 0;
+ PHYSFS_sint64 filelen;
+ PHYSFS_sint64 filepos;
+ PHYSFS_sint32 maxread;
+ PHYSFS_sint32 totalread = 0;
+ int found = 0;
+
+ filelen = io->length(io);
+ BAIL_IF_ERRPASS(filelen == -1, -1);
+
+ /*
+ * Jump to the end of the file and start reading backwards.
+ * The last thing in the file is the zipfile comment, which is variable
+ * length, and the field that specifies its size is before it in the
+ * file (argh!)...this means that we need to scan backwards until we
+ * hit the end-of-central-dir signature. We can then sanity check that
+ * the comment was as big as it should be to make sure we're in the
+ * right place. The comment length field is 16 bits, so we can stop
+ * searching for that signature after a little more than 64k at most,
+ * and call it a corrupted zipfile.
+ */
+
+ if (sizeof (buf) < filelen)
+ {
+ filepos = filelen - sizeof (buf);
+ maxread = sizeof (buf);
+ } /* if */
+ else
+ {
+ filepos = 0;
+ maxread = (PHYSFS_uint32) filelen;
+ } /* else */
+
+ while ((totalread < filelen) && (totalread < 65557))
+ {
+ BAIL_IF_ERRPASS(!io->seek(io, filepos), -1);
+
+ /* make sure we catch a signature between buffers. */
+ if (totalread != 0)
+ {
+ if (!__PHYSFS_readAll(io, buf, maxread - 4))
+ return -1;
+ memcpy(&buf[maxread - 4], extra, sizeof (extra));
+ totalread += maxread - 4;
+ } /* if */
+ else
+ {
+ if (!__PHYSFS_readAll(io, buf, maxread))
+ return -1;
+ totalread += maxread;
+ } /* else */
+
+ memcpy(extra, buf, sizeof (extra));
+
+ for (i = maxread - 4; i > 0; i--)
+ {
+ if ((buf[i + 0] == 0x50) &&
+ (buf[i + 1] == 0x4B) &&
+ (buf[i + 2] == 0x05) &&
+ (buf[i + 3] == 0x06) )
+ {
+ found = 1; /* that's the signature! */
+ break;
+ } /* if */
+ } /* for */
+
+ if (found)
+ break;
+
+ filepos -= (maxread - 4);
+ if (filepos < 0)
+ filepos = 0;
+ } /* while */
+
+ BAIL_IF(!found, PHYSFS_ERR_UNSUPPORTED, -1);
+
+ if (len != NULL)
+ *len = filelen;
+
+ return (filepos + i);
+} /* zip_find_end_of_central_dir */
+
+
+static int isZip(PHYSFS_Io *io)
+{
+ PHYSFS_uint32 sig = 0;
+ int retval = 0;
+
+ /*
+ * The first thing in a zip file might be the signature of the
+ * first local file record, so it makes for a quick determination.
+ */
+ if (readui32(io, &sig))
+ {
+ retval = (sig == ZIP_LOCAL_FILE_SIG);
+ if (!retval)
+ {
+ /*
+ * No sig...might be a ZIP with data at the start
+ * (a self-extracting executable, etc), so we'll have to do
+ * it the hard way...
+ */
+ retval = (zip_find_end_of_central_dir(io, NULL) != -1);
+ } /* if */
+ } /* if */
+
+ return retval;
+} /* isZip */
+
+
+/* Convert paths from old, buggy DOS zippers... */
+static void zip_convert_dos_path(const PHYSFS_uint16 entryversion, char *path)
+{
+ const PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((entryversion >> 8) & 0xFF);
+ if (hosttype == 0) /* FS_FAT_ */
+ {
+ while (*path)
+ {
+ if (*path == '\\')
+ *path = '/';
+ path++;
+ } /* while */
+ } /* if */
+} /* zip_convert_dos_path */
+
+
+static void zip_expand_symlink_path(char *path)
+{
+ char *ptr = path;
+ char *prevptr = path;
+
+ while (1)
+ {
+ ptr = strchr(ptr, '/');
+ if (ptr == NULL)
+ break;
+
+ if (*(ptr + 1) == '.')
+ {
+ if (*(ptr + 2) == '/')
+ {
+ /* current dir in middle of string: ditch it. */
+ memmove(ptr, ptr + 2, strlen(ptr + 2) + 1);
+ } /* else if */
+
+ else if (*(ptr + 2) == '\0')
+ {
+ /* current dir at end of string: ditch it. */
+ *ptr = '\0';
+ } /* else if */
+
+ else if (*(ptr + 2) == '.')
+ {
+ if (*(ptr + 3) == '/')
+ {
+ /* parent dir in middle: move back one, if possible. */
+ memmove(prevptr, ptr + 4, strlen(ptr + 4) + 1);
+ ptr = prevptr;
+ while (prevptr != path)
+ {
+ prevptr--;
+ if (*prevptr == '/')
+ {
+ prevptr++;
+ break;
+ } /* if */
+ } /* while */
+ } /* if */
+
+ if (*(ptr + 3) == '\0')
+ {
+ /* parent dir at end: move back one, if possible. */
+ *prevptr = '\0';
+ } /* if */
+ } /* if */
+ } /* if */
+ else
+ {
+ prevptr = ptr;
+ ptr++;
+ } /* else */
+ } /* while */
+} /* zip_expand_symlink_path */
+
+
+static inline ZIPentry *zip_find_entry(ZIPinfo *info, const char *path)
+{
+ return (ZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path);
+} /* zip_find_entry */
+
+/* (forward reference: zip_follow_symlink and zip_resolve call each other.) */
+static int zip_resolve(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry);
+
+/*
+ * Look for the entry named by (path). If it exists, resolve it, and return
+ * a pointer to that entry. If it's another symlink, keep resolving until you
+ * hit a real file and then return a pointer to the final non-symlink entry.
+ * If there's a problem, return NULL.
+ */
+static ZIPentry *zip_follow_symlink(PHYSFS_Io *io, ZIPinfo *info, char *path)
+{
+ ZIPentry *entry;
+
+ zip_expand_symlink_path(path);
+ entry = zip_find_entry(info, path);
+ if (entry != NULL)
+ {
+ if (!zip_resolve(io, info, entry)) /* recursive! */
+ entry = NULL;
+ else
+ {
+ if (entry->symlink != NULL)
+ entry = entry->symlink;
+ } /* else */
+ } /* if */
+
+ return entry;
+} /* zip_follow_symlink */
+
+
+static int zip_resolve_symlink(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry)
+{
+ const size_t size = (size_t) entry->uncompressed_size;
+ char *path = NULL;
+ int rc = 0;
+
+ /*
+ * We've already parsed the local file header of the symlink at this
+ * point. Now we need to read the actual link from the file data and
+ * follow it.
+ */
+
+ BAIL_IF_ERRPASS(!io->seek(io, entry->offset), 0);
+
+ path = (char *) __PHYSFS_smallAlloc(size + 1);
+ BAIL_IF(!path, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+
+ if (entry->compression_method == COMPMETH_NONE)
+ rc = __PHYSFS_readAll(io, path, size);
+
+ else /* symlink target path is compressed... */
+ {
+ z_stream stream;
+ const size_t complen = (size_t) entry->compressed_size;
+ PHYSFS_uint8 *compressed = (PHYSFS_uint8*) __PHYSFS_smallAlloc(complen);
+ if (compressed != NULL)
+ {
+ if (__PHYSFS_readAll(io, compressed, complen))
+ {
+ initializeZStream(&stream);
+ stream.next_in = compressed;
+ stream.avail_in = (unsigned int) complen;
+ stream.next_out = (unsigned char *) path;
+ stream.avail_out = (unsigned int) size;
+ if (zlib_err(inflateInit2(&stream, -MAX_WBITS)) == Z_OK)
+ {
+ rc = zlib_err(inflate(&stream, Z_FINISH));
+ inflateEnd(&stream);
+
+ /* both are acceptable outcomes... */
+ rc = ((rc == Z_OK) || (rc == Z_STREAM_END));
+ } /* if */
+ } /* if */
+ __PHYSFS_smallFree(compressed);
+ } /* if */
+ } /* else */
+
+ if (rc)
+ {
+ path[entry->uncompressed_size] = '\0'; /* null-terminate it. */
+ zip_convert_dos_path(entry->version, path);
+ entry->symlink = zip_follow_symlink(io, info, path);
+ } /* else */
+
+ __PHYSFS_smallFree(path);
+
+ return (entry->symlink != NULL);
+} /* zip_resolve_symlink */
+
+
+/*
+ * Parse the local file header of an entry, and update entry->offset.
+ */
+static int zip_parse_local(PHYSFS_Io *io, ZIPentry *entry)
+{
+ PHYSFS_uint32 ui32;
+ PHYSFS_uint16 ui16;
+ PHYSFS_uint16 fnamelen;
+ PHYSFS_uint16 extralen;
+
+ /*
+ * crc and (un)compressed_size are always zero if this is a "JAR"
+ * archive created with Sun's Java tools, apparently. We only
+ * consider this archive corrupted if those entries don't match and
+ * aren't zero. That seems to work well.
+ * We also ignore a mismatch if the value is 0xFFFFFFFF here, since it's
+ * possible that's a Zip64 thing.
+ */
+
+ /* !!! FIXME: apparently these are zero if general purpose bit 3 is set,
+ !!! FIXME: which is probably true for Jar files, fwiw, but we don't
+ !!! FIXME: care about these values anyhow. */
+
+ BAIL_IF_ERRPASS(!io->seek(io, entry->offset), 0);
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 != ZIP_LOCAL_FILE_SIG, PHYSFS_ERR_CORRUPT, 0);
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
+ /* Windows Explorer might rewrite the entire central directory, setting
+ this field to 2.0/MS-DOS for all files, so favor the local version,
+ which it leaves intact if it didn't alter that specific file. */
+ entry->version_needed = ui16;
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0); /* general bits. */
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
+ BAIL_IF(ui16 != entry->compression_method, PHYSFS_ERR_CORRUPT, 0);
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0); /* date/time */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 && (ui32 != entry->crc), PHYSFS_ERR_CORRUPT, 0);
+
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 && (ui32 != 0xFFFFFFFF) &&
+ (ui32 != entry->compressed_size), PHYSFS_ERR_CORRUPT, 0);
+
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 && (ui32 != 0xFFFFFFFF) &&
+ (ui32 != entry->uncompressed_size), PHYSFS_ERR_CORRUPT, 0);
+
+ BAIL_IF_ERRPASS(!readui16(io, &fnamelen), 0);
+ BAIL_IF_ERRPASS(!readui16(io, &extralen), 0);
+
+ entry->offset += fnamelen + extralen + 30;
+ return 1;
+} /* zip_parse_local */
+
+
+static int zip_resolve(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry)
+{
+ int retval = 1;
+ const ZipResolveType resolve_type = entry->resolved;
+
+ if (resolve_type == ZIP_DIRECTORY)
+ return 1; /* we're good. */
+
+ /* Don't bother if we've failed to resolve this entry before. */
+ BAIL_IF(resolve_type == ZIP_BROKEN_FILE, PHYSFS_ERR_CORRUPT, 0);
+ BAIL_IF(resolve_type == ZIP_BROKEN_SYMLINK, PHYSFS_ERR_CORRUPT, 0);
+
+ /* uhoh...infinite symlink loop! */
+ BAIL_IF(resolve_type == ZIP_RESOLVING, PHYSFS_ERR_SYMLINK_LOOP, 0);
+
+ /*
+ * We fix up the offset to point to the actual data on the
+ * first open, since we don't want to seek across the whole file on
+ * archive open (can be SLOW on large, CD-stored files), but we
+ * need to check the local file header...not just for corruption,
+ * but since it stores offset info the central directory does not.
+ */
+ if (resolve_type != ZIP_RESOLVED)
+ {
+ if (entry->tree.isdir) /* an ancestor dir that DirTree filled in? */
+ {
+ entry->resolved = ZIP_DIRECTORY;
+ return 1;
+ } /* if */
+
+ retval = zip_parse_local(io, entry);
+ if (retval)
+ {
+ /*
+ * If it's a symlink, find the original file. This will cause
+ * resolution of other entries (other symlinks and, eventually,
+ * the real file) if all goes well.
+ */
+ if (resolve_type == ZIP_UNRESOLVED_SYMLINK)
+ retval = zip_resolve_symlink(io, info, entry);
+ } /* if */
+
+ if (resolve_type == ZIP_UNRESOLVED_SYMLINK)
+ entry->resolved = ((retval) ? ZIP_RESOLVED : ZIP_BROKEN_SYMLINK);
+ else if (resolve_type == ZIP_UNRESOLVED_FILE)
+ entry->resolved = ((retval) ? ZIP_RESOLVED : ZIP_BROKEN_FILE);
+ } /* if */
+
+ return retval;
+} /* zip_resolve */
+
+
+static int zip_entry_is_symlink(const ZIPentry *entry)
+{
+ return ((entry->resolved == ZIP_UNRESOLVED_SYMLINK) ||
+ (entry->resolved == ZIP_BROKEN_SYMLINK) ||
+ (entry->symlink));
+} /* zip_entry_is_symlink */
+
+
+static int zip_version_does_symlinks(PHYSFS_uint32 version)
+{
+ int retval = 0;
+ PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((version >> 8) & 0xFF);
+
+ switch (hosttype)
+ {
+ /*
+ * These are the platforms that can NOT build an archive with
+ * symlinks, according to the Info-ZIP project.
+ */
+ case 0: /* FS_FAT_ */
+ case 1: /* AMIGA_ */
+ case 2: /* VMS_ */
+ case 4: /* VM_CSM_ */
+ case 6: /* FS_HPFS_ */
+ case 11: /* FS_NTFS_ */
+ case 14: /* FS_VFAT_ */
+ case 13: /* ACORN_ */
+ case 15: /* MVS_ */
+ case 18: /* THEOS_ */
+ break; /* do nothing. */
+
+ default: /* assume the rest to be unix-like. */
+ retval = 1;
+ break;
+ } /* switch */
+
+ return retval;
+} /* zip_version_does_symlinks */
+
+
+static inline int zip_has_symlink_attr(const ZIPentry *entry,
+ const PHYSFS_uint32 extern_attr)
+{
+ PHYSFS_uint16 xattr = ((extern_attr >> 16) & 0xFFFF);
+ return ( (zip_version_does_symlinks(entry->version)) &&
+ (entry->uncompressed_size > 0) &&
+ ((xattr & UNIX_FILETYPE_MASK) == UNIX_FILETYPE_SYMLINK) );
+} /* zip_has_symlink_attr */
+
+
+static PHYSFS_sint64 zip_dos_time_to_physfs_time(PHYSFS_uint32 dostime)
+{
+ PHYSFS_uint32 dosdate;
+ struct tm unixtime;
+ memset(&unixtime, '\0', sizeof (unixtime));
+
+ dosdate = (PHYSFS_uint32) ((dostime >> 16) & 0xFFFF);
+ dostime &= 0xFFFF;
+
+ /* dissect date */
+ unixtime.tm_year = ((dosdate >> 9) & 0x7F) + 80;
+ unixtime.tm_mon = ((dosdate >> 5) & 0x0F) - 1;
+ unixtime.tm_mday = ((dosdate ) & 0x1F);
+
+ /* dissect time */
+ unixtime.tm_hour = ((dostime >> 11) & 0x1F);
+ unixtime.tm_min = ((dostime >> 5) & 0x3F);
+ unixtime.tm_sec = ((dostime << 1) & 0x3E);
+
+ /* let mktime calculate daylight savings time. */
+ unixtime.tm_isdst = -1;
+
+ return ((PHYSFS_sint64) mktime(&unixtime));
+} /* zip_dos_time_to_physfs_time */
+
+
+static ZIPentry *zip_load_entry(ZIPinfo *info, const int zip64,
+ const PHYSFS_uint64 ofs_fixup)
+{
+ PHYSFS_Io *io = info->io;
+ ZIPentry entry;
+ ZIPentry *retval = NULL;
+ PHYSFS_uint16 fnamelen, extralen, commentlen;
+ PHYSFS_uint32 external_attr;
+ PHYSFS_uint32 starting_disk;
+ PHYSFS_uint64 offset;
+ PHYSFS_uint16 ui16;
+ PHYSFS_uint32 ui32;
+ PHYSFS_sint64 si64;
+ char *name = NULL;
+ int isdir = 0;
+
+ /* sanity check with central directory signature... */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL);
+ BAIL_IF(ui32 != ZIP_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, NULL);
+
+ memset(&entry, '\0', sizeof (entry));
+
+ /* Get the pertinent parts of the record... */
+ BAIL_IF_ERRPASS(!readui16(io, &entry.version), NULL);
+ BAIL_IF_ERRPASS(!readui16(io, &entry.version_needed), NULL);
+ BAIL_IF_ERRPASS(!readui16(io, &entry.general_bits), NULL); /* general bits */
+ BAIL_IF_ERRPASS(!readui16(io, &entry.compression_method), NULL);
+ BAIL_IF_ERRPASS(!readui32(io, &entry.dos_mod_time), NULL);
+ entry.last_mod_time = zip_dos_time_to_physfs_time(entry.dos_mod_time);
+ BAIL_IF_ERRPASS(!readui32(io, &entry.crc), NULL);
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL);
+ entry.compressed_size = (PHYSFS_uint64) ui32;
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL);
+ entry.uncompressed_size = (PHYSFS_uint64) ui32;
+ BAIL_IF_ERRPASS(!readui16(io, &fnamelen), NULL);
+ BAIL_IF_ERRPASS(!readui16(io, &extralen), NULL);
+ BAIL_IF_ERRPASS(!readui16(io, &commentlen), NULL);
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), NULL);
+ starting_disk = (PHYSFS_uint32) ui16;
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), NULL); /* internal file attribs */
+ BAIL_IF_ERRPASS(!readui32(io, &external_attr), NULL);
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), NULL);
+ offset = (PHYSFS_uint64) ui32;
+
+ name = (char *) __PHYSFS_smallAlloc(fnamelen + 1);
+ BAIL_IF(!name, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ if (!__PHYSFS_readAll(io, name, fnamelen))
+ {
+ __PHYSFS_smallFree(name);
+ return NULL;
+ } /* if */
+
+ if (name[fnamelen - 1] == '/')
+ {
+ name[fnamelen - 1] = '\0';
+ isdir = 1;
+ } /* if */
+ name[fnamelen] = '\0'; /* null-terminate the filename. */
+
+ zip_convert_dos_path(entry.version, name);
+
+ retval = (ZIPentry *) __PHYSFS_DirTreeAdd(&info->tree, name, isdir);
+ __PHYSFS_smallFree(name);
+
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+
+ /* It's okay to BAIL without freeing retval, because it's stored in the
+ __PHYSFS_DirTree and will be freed later anyhow. */
+ BAIL_IF(retval->last_mod_time != 0, PHYSFS_ERR_CORRUPT, NULL); /* dupe? */
+
+ /* Move the data we already read into place in the official object. */
+ memcpy(((PHYSFS_uint8 *) retval) + sizeof (__PHYSFS_DirTreeEntry),
+ ((PHYSFS_uint8 *) &entry) + sizeof (__PHYSFS_DirTreeEntry),
+ sizeof (*retval) - sizeof (__PHYSFS_DirTreeEntry));
+
+ retval->symlink = NULL; /* will be resolved later, if necessary. */
+
+ if (isdir)
+ retval->resolved = ZIP_DIRECTORY;
+ else
+ {
+ retval->resolved = (zip_has_symlink_attr(retval, external_attr)) ?
+ ZIP_UNRESOLVED_SYMLINK : ZIP_UNRESOLVED_FILE;
+ } /* else */
+
+ si64 = io->tell(io);
+ BAIL_IF_ERRPASS(si64 == -1, NULL);
+
+ /* If the actual sizes didn't fit in 32-bits, look for the Zip64
+ extended information extra field... */
+ if ( (zip64) &&
+ ((offset == 0xFFFFFFFF) ||
+ (starting_disk == 0xFFFFFFFF) ||
+ (retval->compressed_size == 0xFFFFFFFF) ||
+ (retval->uncompressed_size == 0xFFFFFFFF)) )
+ {
+ int found = 0;
+ PHYSFS_uint16 sig = 0;
+ PHYSFS_uint16 len = 0;
+ while (extralen > 4)
+ {
+ BAIL_IF_ERRPASS(!readui16(io, &sig), NULL);
+ BAIL_IF_ERRPASS(!readui16(io, &len), NULL);
+
+ si64 += 4 + len;
+ extralen -= 4 + len;
+ if (sig != ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG)
+ {
+ BAIL_IF_ERRPASS(!io->seek(io, si64), NULL);
+ continue;
+ } /* if */
+
+ found = 1;
+ break;
+ } /* while */
+
+ BAIL_IF(!found, PHYSFS_ERR_CORRUPT, NULL);
+
+ if (retval->uncompressed_size == 0xFFFFFFFF)
+ {
+ BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL);
+ BAIL_IF_ERRPASS(!readui64(io, &retval->uncompressed_size), NULL);
+ len -= 8;
+ } /* if */
+
+ if (retval->compressed_size == 0xFFFFFFFF)
+ {
+ BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL);
+ BAIL_IF_ERRPASS(!readui64(io, &retval->compressed_size), NULL);
+ len -= 8;
+ } /* if */
+
+ if (offset == 0xFFFFFFFF)
+ {
+ BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL);
+ BAIL_IF_ERRPASS(!readui64(io, &offset), NULL);
+ len -= 8;
+ } /* if */
+
+ if (starting_disk == 0xFFFFFFFF)
+ {
+ BAIL_IF(len < 8, PHYSFS_ERR_CORRUPT, NULL);
+ BAIL_IF_ERRPASS(!readui32(io, &starting_disk), NULL);
+ len -= 4;
+ } /* if */
+
+ BAIL_IF(len != 0, PHYSFS_ERR_CORRUPT, NULL);
+ } /* if */
+
+ BAIL_IF(starting_disk != 0, PHYSFS_ERR_CORRUPT, NULL);
+
+ retval->offset = offset + ofs_fixup;
+
+ /* seek to the start of the next entry in the central directory... */
+ BAIL_IF_ERRPASS(!io->seek(io, si64 + extralen + commentlen), NULL);
+
+ return retval; /* success. */
+} /* zip_load_entry */
+
+
+/* This leaves things allocated on error; the caller will clean up the mess. */
+static int zip_load_entries(ZIPinfo *info,
+ const PHYSFS_uint64 data_ofs,
+ const PHYSFS_uint64 central_ofs,
+ const PHYSFS_uint64 entry_count)
+{
+ PHYSFS_Io *io = info->io;
+ const int zip64 = info->zip64;
+ PHYSFS_uint64 i;
+
+ BAIL_IF_ERRPASS(!io->seek(io, central_ofs), 0);
+
+ for (i = 0; i < entry_count; i++)
+ {
+ ZIPentry *entry = zip_load_entry(info, zip64, data_ofs);
+ BAIL_IF_ERRPASS(!entry, 0);
+ if (zip_entry_is_tradional_crypto(entry))
+ info->has_crypto = 1;
+ } /* for */
+
+ return 1;
+} /* zip_load_entries */
+
+
+static PHYSFS_sint64 zip64_find_end_of_central_dir(PHYSFS_Io *io,
+ PHYSFS_sint64 _pos,
+ PHYSFS_uint64 offset)
+{
+ /*
+ * Naturally, the offset is useless to us; it is the offset from the
+ * start of file, which is meaningless if we've appended this .zip to
+ * a self-extracting .exe. We need to find this on our own. It should
+ * be directly before the locator record, but the record in question,
+ * like the original end-of-central-directory record, ends with a
+ * variable-length field. Unlike the original, which has to store the
+ * size of that variable-length field in a 16-bit int and thus has to be
+ * within 64k, the new one gets 64-bits.
+ *
+ * Fortunately, the only currently-specified record for that variable
+ * length block is some weird proprietary thing that deals with EBCDIC
+ * and tape backups or something. So we don't seek far.
+ */
+
+ PHYSFS_uint32 ui32;
+ const PHYSFS_uint64 pos = (PHYSFS_uint64) _pos;
+
+ assert(_pos > 0);
+
+ /* Try offset specified in the Zip64 end of central directory locator. */
+ /* This works if the entire PHYSFS_Io is the zip file. */
+ BAIL_IF_ERRPASS(!io->seek(io, offset), -1);
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), -1);
+ if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG)
+ return offset;
+
+ /* Try 56 bytes before the Zip64 end of central directory locator. */
+ /* This works if the record isn't variable length and is version 1. */
+ if (pos > 56)
+ {
+ BAIL_IF_ERRPASS(!io->seek(io, pos-56), -1);
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), -1);
+ if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG)
+ return pos-56;
+ } /* if */
+
+ /* Try 84 bytes before the Zip64 end of central directory locator. */
+ /* This works if the record isn't variable length and is version 2. */
+ if (pos > 84)
+ {
+ BAIL_IF_ERRPASS(!io->seek(io, pos-84), -1);
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), -1);
+ if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG)
+ return pos-84;
+ } /* if */
+
+ /* Ok, brute force: we know it's between (offset) and (pos) somewhere. */
+ /* Just try moving back at most 256k. Oh well. */
+ if ((offset < pos) && (pos > 4))
+ {
+ const size_t maxbuflen = 256 * 1024;
+ size_t len = (size_t) (pos - offset);
+ PHYSFS_uint8 *buf = NULL;
+ PHYSFS_sint32 i;
+
+ if (len > maxbuflen)
+ len = maxbuflen;
+
+ buf = (PHYSFS_uint8 *) __PHYSFS_smallAlloc(len);
+ BAIL_IF(!buf, PHYSFS_ERR_OUT_OF_MEMORY, -1);
+
+ if (!io->seek(io, pos - len) || !__PHYSFS_readAll(io, buf, len))
+ {
+ __PHYSFS_smallFree(buf);
+ return -1; /* error was set elsewhere. */
+ } /* if */
+
+ for (i = (PHYSFS_sint32) (len - 4); i >= 0; i--)
+ {
+ if ( (buf[i] == 0x50) && (buf[i+1] == 0x4b) &&
+ (buf[i+2] == 0x06) && (buf[i+3] == 0x06) )
+ {
+ __PHYSFS_smallFree(buf);
+ return pos - ((PHYSFS_sint64) (len - i));
+ } /* if */
+ } /* for */
+
+ __PHYSFS_smallFree(buf);
+ } /* if */
+
+ BAIL(PHYSFS_ERR_CORRUPT, -1); /* didn't find it. */
+} /* zip64_find_end_of_central_dir */
+
+
+static int zip64_parse_end_of_central_dir(ZIPinfo *info,
+ PHYSFS_uint64 *data_start,
+ PHYSFS_uint64 *dir_ofs,
+ PHYSFS_uint64 *entry_count,
+ PHYSFS_sint64 pos)
+{
+ PHYSFS_Io *io = info->io;
+ PHYSFS_uint64 ui64;
+ PHYSFS_uint32 ui32;
+ PHYSFS_uint16 ui16;
+
+ /* We should be positioned right past the locator signature. */
+
+ if ((pos < 0) || (!io->seek(io, pos)))
+ return 0;
+
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ if (ui32 != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG)
+ return -1; /* it's not a Zip64 archive. Not an error, though! */
+
+ info->zip64 = 1;
+
+ /* number of the disk with the start of the central directory. */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 != 0, PHYSFS_ERR_CORRUPT, 0);
+
+ /* offset of Zip64 end of central directory record. */
+ BAIL_IF_ERRPASS(!readui64(io, &ui64), 0);
+
+ /* total number of disks */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 != 1, PHYSFS_ERR_CORRUPT, 0);
+
+ pos = zip64_find_end_of_central_dir(io, pos, ui64);
+ if (pos < 0)
+ return 0; /* oh well. */
+
+ /*
+ * For self-extracting archives, etc, there's crapola in the file
+ * before the zipfile records; we calculate how much data there is
+ * prepended by determining how far the zip64-end-of-central-directory
+ * offset is from where it is supposed to be...the difference in bytes
+ * is how much arbitrary data is at the start of the physical file.
+ */
+ assert(((PHYSFS_uint64) pos) >= ui64);
+ *data_start = ((PHYSFS_uint64) pos) - ui64;
+
+ BAIL_IF_ERRPASS(!io->seek(io, pos), 0);
+
+ /* check signature again, just in case. */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 != ZIP64_END_OF_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, 0);
+
+ /* size of Zip64 end of central directory record. */
+ BAIL_IF_ERRPASS(!readui64(io, &ui64), 0);
+
+ /* version made by. */
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
+
+ /* version needed to extract. */
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
+
+ /* number of this disk. */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 != 0, PHYSFS_ERR_CORRUPT, 0);
+
+ /* number of disk with start of central directory record. */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 != 0, PHYSFS_ERR_CORRUPT, 0);
+
+ /* total number of entries in the central dir on this disk */
+ BAIL_IF_ERRPASS(!readui64(io, &ui64), 0);
+
+ /* total number of entries in the central dir */
+ BAIL_IF_ERRPASS(!readui64(io, entry_count), 0);
+ BAIL_IF(ui64 != *entry_count, PHYSFS_ERR_CORRUPT, 0);
+
+ /* size of the central directory */
+ BAIL_IF_ERRPASS(!readui64(io, &ui64), 0);
+
+ /* offset of central directory */
+ BAIL_IF_ERRPASS(!readui64(io, dir_ofs), 0);
+
+ /* Since we know the difference, fix up the central dir offset... */
+ *dir_ofs += *data_start;
+
+ /*
+ * There are more fields here, for encryption and feature-specific things,
+ * but we don't care about any of them at the moment.
+ */
+
+ return 1; /* made it. */
+} /* zip64_parse_end_of_central_dir */
+
+
+static int zip_parse_end_of_central_dir(ZIPinfo *info,
+ PHYSFS_uint64 *data_start,
+ PHYSFS_uint64 *dir_ofs,
+ PHYSFS_uint64 *entry_count)
+{
+ PHYSFS_Io *io = info->io;
+ PHYSFS_uint16 entryCount16;
+ PHYSFS_uint32 offset32;
+ PHYSFS_uint32 ui32;
+ PHYSFS_uint16 ui16;
+ PHYSFS_sint64 len;
+ PHYSFS_sint64 pos;
+ int rc;
+
+ /* find the end-of-central-dir record, and seek to it. */
+ pos = zip_find_end_of_central_dir(io, &len);
+ BAIL_IF_ERRPASS(pos == -1, 0);
+ BAIL_IF_ERRPASS(!io->seek(io, pos), 0);
+
+ /* check signature again, just in case. */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+ BAIL_IF(ui32 != ZIP_END_OF_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, 0);
+
+ /* Seek back to see if "Zip64 end of central directory locator" exists. */
+ /* this record is 20 bytes before end-of-central-dir */
+ rc = zip64_parse_end_of_central_dir(info, data_start, dir_ofs,
+ entry_count, pos - 20);
+
+ /* Error or success? Bounce out of here. Keep going if not zip64. */
+ if ((rc == 0) || (rc == 1))
+ return rc;
+
+ assert(rc == -1); /* no error, just not a Zip64 archive. */
+
+ /* Not Zip64? Seek back to where we were and keep processing. */
+ BAIL_IF_ERRPASS(!io->seek(io, pos + 4), 0);
+
+ /* number of this disk */
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
+ BAIL_IF(ui16 != 0, PHYSFS_ERR_CORRUPT, 0);
+
+ /* number of the disk with the start of the central directory */
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
+ BAIL_IF(ui16 != 0, PHYSFS_ERR_CORRUPT, 0);
+
+ /* total number of entries in the central dir on this disk */
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
+
+ /* total number of entries in the central dir */
+ BAIL_IF_ERRPASS(!readui16(io, &entryCount16), 0);
+ BAIL_IF(ui16 != entryCount16, PHYSFS_ERR_CORRUPT, 0);
+
+ *entry_count = entryCount16;
+
+ /* size of the central directory */
+ BAIL_IF_ERRPASS(!readui32(io, &ui32), 0);
+
+ /* offset of central directory */
+ BAIL_IF_ERRPASS(!readui32(io, &offset32), 0);
+ *dir_ofs = (PHYSFS_uint64) offset32;
+ BAIL_IF(((PHYSFS_uint64) pos) < (*dir_ofs + ui32), PHYSFS_ERR_CORRUPT, 0);
+
+ /*
+ * For self-extracting archives, etc, there's crapola in the file
+ * before the zipfile records; we calculate how much data there is
+ * prepended by determining how far the central directory offset is
+ * from where it is supposed to be (start of end-of-central-dir minus
+ * sizeof central dir)...the difference in bytes is how much arbitrary
+ * data is at the start of the physical file.
+ */
+ *data_start = (PHYSFS_uint64) (pos - (*dir_ofs + ui32));
+
+ /* Now that we know the difference, fix up the central dir offset... */
+ *dir_ofs += *data_start;
+
+ /* zipfile comment length */
+ BAIL_IF_ERRPASS(!readui16(io, &ui16), 0);
+
+ /*
+ * Make sure that the comment length matches to the end of file...
+ * If it doesn't, we're either in the wrong part of the file, or the
+ * file is corrupted, but we give up either way.
+ */
+ BAIL_IF((pos + 22 + ui16) != len, PHYSFS_ERR_CORRUPT, 0);
+
+ return 1; /* made it. */
+} /* zip_parse_end_of_central_dir */
+
+
+static void ZIP_closeArchive(void *opaque)
+{
+ ZIPinfo *info = (ZIPinfo *) (opaque);
+
+ if (!info)
+ return;
+
+ if (info->io)
+ info->io->destroy(info->io);
+
+ __PHYSFS_DirTreeDeinit(&info->tree);
+
+ allocator.Free(info);
+} /* ZIP_closeArchive */
+
+
+static void *ZIP_openArchive(PHYSFS_Io *io, const char *name,
+ int forWriting, int *claimed)
+{
+ ZIPinfo *info = NULL;
+ ZIPentry *root = NULL;
+ PHYSFS_uint64 dstart = 0; /* data start */
+ PHYSFS_uint64 cdir_ofs; /* central dir offset */
+ PHYSFS_uint64 count;
+
+ assert(io != NULL); /* shouldn't ever happen. */
+
+ BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
+ BAIL_IF_ERRPASS(!isZip(io), NULL);
+
+ *claimed = 1;
+
+ info = (ZIPinfo *) allocator.Malloc(sizeof (ZIPinfo));
+ BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ memset(info, '\0', sizeof (ZIPinfo));
+
+ info->io = io;
+
+ if (!zip_parse_end_of_central_dir(info, &dstart, &cdir_ofs, &count))
+ goto ZIP_openarchive_failed;
+ else if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (ZIPentry), 1, 0))
+ goto ZIP_openarchive_failed;
+
+ root = (ZIPentry *) info->tree.root;
+ root->resolved = ZIP_DIRECTORY;
+
+ if (!zip_load_entries(info, dstart, cdir_ofs, count))
+ goto ZIP_openarchive_failed;
+
+ assert(info->tree.root->sibling == NULL);
+ return info;
+
+ZIP_openarchive_failed:
+ info->io = NULL; /* don't let ZIP_closeArchive destroy (io). */
+ ZIP_closeArchive(info);
+ return NULL;
+} /* ZIP_openArchive */
+
+
+static PHYSFS_Io *zip_get_io(PHYSFS_Io *io, ZIPinfo *inf, ZIPentry *entry)
+{
+ int success;
+ PHYSFS_Io *retval = io->duplicate(io);
+ BAIL_IF_ERRPASS(!retval, NULL);
+
+ assert(!entry->tree.isdir); /* should have been checked before calling. */
+
+ /* (inf) can be NULL if we already resolved. */
+ success = (inf == NULL) || zip_resolve(retval, inf, entry);
+ if (success)
+ {
+ PHYSFS_sint64 offset;
+ offset = ((entry->symlink) ? entry->symlink->offset : entry->offset);
+ success = retval->seek(retval, offset);
+ } /* if */
+
+ if (!success)
+ {
+ retval->destroy(retval);
+ retval = NULL;
+ } /* if */
+
+ return retval;
+} /* zip_get_io */
+
+
+static PHYSFS_Io *ZIP_openRead(void *opaque, const char *filename)
+{
+ PHYSFS_Io *retval = NULL;
+ ZIPinfo *info = (ZIPinfo *) opaque;
+ ZIPentry *entry = zip_find_entry(info, filename);
+ ZIPfileinfo *finfo = NULL;
+ PHYSFS_Io *io = NULL;
+ PHYSFS_uint8 *password = NULL;
+
+ /* if not found, see if maybe "$PASSWORD" is appended. */
+ if ((!entry) && (info->has_crypto))
+ {
+ const char *ptr = strrchr(filename, '$');
+ if (ptr != NULL)
+ {
+ const size_t len = (size_t) (ptr - filename);
+ char *str = (char *) __PHYSFS_smallAlloc(len + 1);
+ BAIL_IF(!str, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ memcpy(str, filename, len);
+ str[len] = '\0';
+ entry = zip_find_entry(info, str);
+ __PHYSFS_smallFree(str);
+ password = (PHYSFS_uint8 *) (ptr + 1);
+ } /* if */
+ } /* if */
+
+ BAIL_IF_ERRPASS(!entry, NULL);
+
+ BAIL_IF_ERRPASS(!zip_resolve(info->io, info, entry), NULL);
+
+ BAIL_IF(entry->tree.isdir, PHYSFS_ERR_NOT_A_FILE, NULL);
+
+ retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
+ GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, ZIP_openRead_failed);
+
+ finfo = (ZIPfileinfo *) allocator.Malloc(sizeof (ZIPfileinfo));
+ GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, ZIP_openRead_failed);
+ memset(finfo, '\0', sizeof (ZIPfileinfo));
+
+ io = zip_get_io(info->io, info, entry);
+ GOTO_IF_ERRPASS(!io, ZIP_openRead_failed);
+ finfo->io = io;
+ finfo->entry = ((entry->symlink != NULL) ? entry->symlink : entry);
+ initializeZStream(&finfo->stream);
+
+ if (finfo->entry->compression_method != COMPMETH_NONE)
+ {
+ finfo->buffer = (PHYSFS_uint8 *) allocator.Malloc(ZIP_READBUFSIZE);
+ if (!finfo->buffer)
+ GOTO(PHYSFS_ERR_OUT_OF_MEMORY, ZIP_openRead_failed);
+ else if (zlib_err(inflateInit2(&finfo->stream, -MAX_WBITS)) != Z_OK)
+ goto ZIP_openRead_failed;
+ } /* if */
+
+ if (!zip_entry_is_tradional_crypto(entry))
+ GOTO_IF(password != NULL, PHYSFS_ERR_BAD_PASSWORD, ZIP_openRead_failed);
+ else
+ {
+ PHYSFS_uint8 crypto_header[12];
+ GOTO_IF(password == NULL, PHYSFS_ERR_BAD_PASSWORD, ZIP_openRead_failed);
+ if (io->read(io, crypto_header, 12) != 12)
+ goto ZIP_openRead_failed;
+ else if (!zip_prep_crypto_keys(finfo, crypto_header, password))
+ goto ZIP_openRead_failed;
+ } /* if */
+
+ memcpy(retval, &ZIP_Io, sizeof (PHYSFS_Io));
+ retval->opaque = finfo;
+
+ return retval;
+
+ZIP_openRead_failed:
+ if (finfo != NULL)
+ {
+ if (finfo->io != NULL)
+ finfo->io->destroy(finfo->io);
+
+ if (finfo->buffer != NULL)
+ {
+ allocator.Free(finfo->buffer);
+ inflateEnd(&finfo->stream);
+ } /* if */
+
+ allocator.Free(finfo);
+ } /* if */
+
+ if (retval != NULL)
+ allocator.Free(retval);
+
+ return NULL;
+} /* ZIP_openRead */
+
+
+static PHYSFS_Io *ZIP_openWrite(void *opaque, const char *filename)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, NULL);
+} /* ZIP_openWrite */
+
+
+static PHYSFS_Io *ZIP_openAppend(void *opaque, const char *filename)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, NULL);
+} /* ZIP_openAppend */
+
+
+static int ZIP_remove(void *opaque, const char *name)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, 0);
+} /* ZIP_remove */
+
+
+static int ZIP_mkdir(void *opaque, const char *name)
+{
+ BAIL(PHYSFS_ERR_READ_ONLY, 0);
+} /* ZIP_mkdir */
+
+
+static int ZIP_stat(void *opaque, const char *filename, PHYSFS_Stat *stat)
+{
+ ZIPinfo *info = (ZIPinfo *) opaque;
+ ZIPentry *entry = zip_find_entry(info, filename);
+
+ if (entry == NULL)
+ return 0;
+
+ else if (!zip_resolve(info->io, info, entry))
+ return 0;
+
+ else if (entry->resolved == ZIP_DIRECTORY)
+ {
+ stat->filesize = 0;
+ stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+ } /* if */
+
+ else if (zip_entry_is_symlink(entry))
+ {
+ stat->filesize = 0;
+ stat->filetype = PHYSFS_FILETYPE_SYMLINK;
+ } /* else if */
+
+ else
+ {
+ stat->filesize = (PHYSFS_sint64) entry->uncompressed_size;
+ stat->filetype = PHYSFS_FILETYPE_REGULAR;
+ } /* else */
+
+ stat->modtime = ((entry) ? entry->last_mod_time : 0);
+ stat->createtime = stat->modtime;
+ stat->accesstime = -1;
+ stat->readonly = 1; /* .zip files are always read only */
+
+ return 1;
+} /* ZIP_stat */
+
+
+const PHYSFS_Archiver __PHYSFS_Archiver_ZIP =
+{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
+ {
+ "ZIP",
+ "PkZip/WinZip/Info-Zip compatible",
+ "Ryan C. Gordon ",
+ "https://icculus.org/physfs/",
+ 1, /* supportsSymlinks */
+ },
+ ZIP_openArchive,
+ __PHYSFS_DirTreeEnumerate,
+ ZIP_openRead,
+ ZIP_openWrite,
+ ZIP_openAppend,
+ ZIP_remove,
+ ZIP_mkdir,
+ ZIP_stat,
+ ZIP_closeArchive
+};
+
+#endif /* defined PHYSFS_SUPPORTS_ZIP */
+
+/* end of physfs_archiver_zip.c ... */
+
diff --git a/third-party/physfs/src/physfs_byteorder.c b/third-party/physfs/src/physfs_byteorder.c
new file mode 100644
index 0000000..b2d3a2c
--- /dev/null
+++ b/third-party/physfs/src/physfs_byteorder.c
@@ -0,0 +1,137 @@
+/**
+ * PhysicsFS; a portable, flexible file i/o abstraction.
+ *
+ * Documentation is in physfs.h. It's verbose, honest. :)
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#ifndef PHYSFS_Swap16
+static inline PHYSFS_uint16 PHYSFS_Swap16(PHYSFS_uint16 D)
+{
+ return ((D<<8)|(D>>8));
+}
+#endif
+#ifndef PHYSFS_Swap32
+static inline PHYSFS_uint32 PHYSFS_Swap32(PHYSFS_uint32 D)
+{
+ return ((D<<24)|((D<<8)&0x00FF0000)|((D>>8)&0x0000FF00)|(D>>24));
+}
+#endif
+#ifndef PHYSFS_NO_64BIT_SUPPORT
+#ifndef PHYSFS_Swap64
+static inline PHYSFS_uint64 PHYSFS_Swap64(PHYSFS_uint64 val) {
+ PHYSFS_uint32 hi, lo;
+
+ /* Separate into high and low 32-bit values and swap them */
+ lo = (PHYSFS_uint32)(val&0xFFFFFFFF);
+ val >>= 32;
+ hi = (PHYSFS_uint32)(val&0xFFFFFFFF);
+ val = PHYSFS_Swap32(lo);
+ val <<= 32;
+ val |= PHYSFS_Swap32(hi);
+ return val;
+}
+#endif
+#else
+#ifndef PHYSFS_Swap64
+/* This is mainly to keep compilers from complaining in PHYSFS code.
+ If there is no real 64-bit datatype, then compilers will complain about
+ the fake 64-bit datatype that PHYSFS provides when it compiles user code.
+*/
+#define PHYSFS_Swap64(X) (X)
+#endif
+#endif /* PHYSFS_NO_64BIT_SUPPORT */
+
+
+/* Byteswap item from the specified endianness to the native endianness */
+#if PHYSFS_BYTEORDER == PHYSFS_LIL_ENDIAN
+PHYSFS_uint16 PHYSFS_swapULE16(PHYSFS_uint16 x) { return x; }
+PHYSFS_sint16 PHYSFS_swapSLE16(PHYSFS_sint16 x) { return x; }
+PHYSFS_uint32 PHYSFS_swapULE32(PHYSFS_uint32 x) { return x; }
+PHYSFS_sint32 PHYSFS_swapSLE32(PHYSFS_sint32 x) { return x; }
+PHYSFS_uint64 PHYSFS_swapULE64(PHYSFS_uint64 x) { return x; }
+PHYSFS_sint64 PHYSFS_swapSLE64(PHYSFS_sint64 x) { return x; }
+
+PHYSFS_uint16 PHYSFS_swapUBE16(PHYSFS_uint16 x) { return PHYSFS_Swap16(x); }
+PHYSFS_sint16 PHYSFS_swapSBE16(PHYSFS_sint16 x) { return PHYSFS_Swap16(x); }
+PHYSFS_uint32 PHYSFS_swapUBE32(PHYSFS_uint32 x) { return PHYSFS_Swap32(x); }
+PHYSFS_sint32 PHYSFS_swapSBE32(PHYSFS_sint32 x) { return PHYSFS_Swap32(x); }
+PHYSFS_uint64 PHYSFS_swapUBE64(PHYSFS_uint64 x) { return PHYSFS_Swap64(x); }
+PHYSFS_sint64 PHYSFS_swapSBE64(PHYSFS_sint64 x) { return PHYSFS_Swap64(x); }
+#else
+PHYSFS_uint16 PHYSFS_swapULE16(PHYSFS_uint16 x) { return PHYSFS_Swap16(x); }
+PHYSFS_sint16 PHYSFS_swapSLE16(PHYSFS_sint16 x) { return PHYSFS_Swap16(x); }
+PHYSFS_uint32 PHYSFS_swapULE32(PHYSFS_uint32 x) { return PHYSFS_Swap32(x); }
+PHYSFS_sint32 PHYSFS_swapSLE32(PHYSFS_sint32 x) { return PHYSFS_Swap32(x); }
+PHYSFS_uint64 PHYSFS_swapULE64(PHYSFS_uint64 x) { return PHYSFS_Swap64(x); }
+PHYSFS_sint64 PHYSFS_swapSLE64(PHYSFS_sint64 x) { return PHYSFS_Swap64(x); }
+
+PHYSFS_uint16 PHYSFS_swapUBE16(PHYSFS_uint16 x) { return x; }
+PHYSFS_sint16 PHYSFS_swapSBE16(PHYSFS_sint16 x) { return x; }
+PHYSFS_uint32 PHYSFS_swapUBE32(PHYSFS_uint32 x) { return x; }
+PHYSFS_sint32 PHYSFS_swapSBE32(PHYSFS_sint32 x) { return x; }
+PHYSFS_uint64 PHYSFS_swapUBE64(PHYSFS_uint64 x) { return x; }
+PHYSFS_sint64 PHYSFS_swapSBE64(PHYSFS_sint64 x) { return x; }
+#endif
+
+static inline int readAll(PHYSFS_File *file, void *val, const size_t len)
+{
+ return (PHYSFS_readBytes(file, val, len) == len);
+} /* readAll */
+
+#define PHYSFS_BYTEORDER_READ(datatype, swaptype) \
+ int PHYSFS_read##swaptype(PHYSFS_File *file, PHYSFS_##datatype *val) { \
+ PHYSFS_##datatype in; \
+ BAIL_IF(val == NULL, PHYSFS_ERR_INVALID_ARGUMENT, 0); \
+ BAIL_IF_ERRPASS(!readAll(file, &in, sizeof (in)), 0); \
+ *val = PHYSFS_swap##swaptype(in); \
+ return 1; \
+ }
+
+PHYSFS_BYTEORDER_READ(sint16, SLE16)
+PHYSFS_BYTEORDER_READ(uint16, ULE16)
+PHYSFS_BYTEORDER_READ(sint16, SBE16)
+PHYSFS_BYTEORDER_READ(uint16, UBE16)
+PHYSFS_BYTEORDER_READ(sint32, SLE32)
+PHYSFS_BYTEORDER_READ(uint32, ULE32)
+PHYSFS_BYTEORDER_READ(sint32, SBE32)
+PHYSFS_BYTEORDER_READ(uint32, UBE32)
+PHYSFS_BYTEORDER_READ(sint64, SLE64)
+PHYSFS_BYTEORDER_READ(uint64, ULE64)
+PHYSFS_BYTEORDER_READ(sint64, SBE64)
+PHYSFS_BYTEORDER_READ(uint64, UBE64)
+
+
+static inline int writeAll(PHYSFS_File *f, const void *val, const size_t len)
+{
+ return (PHYSFS_writeBytes(f, val, len) == len);
+} /* writeAll */
+
+#define PHYSFS_BYTEORDER_WRITE(datatype, swaptype) \
+ int PHYSFS_write##swaptype(PHYSFS_File *file, PHYSFS_##datatype val) { \
+ const PHYSFS_##datatype out = PHYSFS_swap##swaptype(val); \
+ BAIL_IF_ERRPASS(!writeAll(file, &out, sizeof (out)), 0); \
+ return 1; \
+ }
+
+PHYSFS_BYTEORDER_WRITE(sint16, SLE16)
+PHYSFS_BYTEORDER_WRITE(uint16, ULE16)
+PHYSFS_BYTEORDER_WRITE(sint16, SBE16)
+PHYSFS_BYTEORDER_WRITE(uint16, UBE16)
+PHYSFS_BYTEORDER_WRITE(sint32, SLE32)
+PHYSFS_BYTEORDER_WRITE(uint32, ULE32)
+PHYSFS_BYTEORDER_WRITE(sint32, SBE32)
+PHYSFS_BYTEORDER_WRITE(uint32, UBE32)
+PHYSFS_BYTEORDER_WRITE(sint64, SLE64)
+PHYSFS_BYTEORDER_WRITE(uint64, ULE64)
+PHYSFS_BYTEORDER_WRITE(sint64, SBE64)
+PHYSFS_BYTEORDER_WRITE(uint64, UBE64)
+
+/* end of physfs_byteorder.c ... */
+
diff --git a/third-party/physfs/src/physfs_casefolding.h b/third-party/physfs/src/physfs_casefolding.h
new file mode 100644
index 0000000..bb6ac18
--- /dev/null
+++ b/third-party/physfs/src/physfs_casefolding.h
@@ -0,0 +1,2572 @@
+/*
+ * This file is part of PhysicsFS (https://icculus.org/physfs/)
+ *
+ * This data generated by physfs/extras/makecasefoldhashtable.pl ...
+ * Do not manually edit this file!
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ */
+
+#ifndef _INCLUDE_PHYSFS_CASEFOLDING_H_
+#define _INCLUDE_PHYSFS_CASEFOLDING_H_
+
+#ifndef __PHYSICSFS_INTERNAL__
+#error Do not include this header from your applications.
+#endif
+
+/* We build three simple hashmaps here: one that maps Unicode codepoints to
+a one, two, or three lowercase codepoints. To retrieve this info: look at
+case_fold_hashX, where X is 1, 2, or 3. Most foldable codepoints fold to one,
+a few dozen fold to two, and a handful fold to three. If the codepoint isn't
+in any of these hashes, it doesn't fold (no separate upper and lowercase).
+
+Almost all these codepoints fit into 16 bits, so we hash them as such to save
+memory. If a codepoint is > 0xFFFF, we have separate hashes for them,
+since there are (currently) only about 120 of them and (currently) all of them
+map to a single lowercase codepoint. */
+
+typedef struct CaseFoldMapping1_32
+{
+ PHYSFS_uint32 from;
+ PHYSFS_uint32 to0;
+} CaseFoldMapping1_32;
+
+typedef struct CaseFoldMapping1_16
+{
+ PHYSFS_uint16 from;
+ PHYSFS_uint16 to0;
+} CaseFoldMapping1_16;
+
+typedef struct CaseFoldMapping2_16
+{
+ PHYSFS_uint16 from;
+ PHYSFS_uint16 to0;
+ PHYSFS_uint16 to1;
+} CaseFoldMapping2_16;
+
+typedef struct CaseFoldMapping3_16
+{
+ PHYSFS_uint16 from;
+ PHYSFS_uint16 to0;
+ PHYSFS_uint16 to1;
+ PHYSFS_uint16 to2;
+} CaseFoldMapping3_16;
+
+typedef struct CaseFoldHashBucket1_16
+{
+ const CaseFoldMapping1_16 *list;
+ const PHYSFS_uint8 count;
+} CaseFoldHashBucket1_16;
+
+typedef struct CaseFoldHashBucket1_32
+{
+ const CaseFoldMapping1_32 *list;
+ const PHYSFS_uint8 count;
+} CaseFoldHashBucket1_32;
+
+typedef struct CaseFoldHashBucket2_16
+{
+ const CaseFoldMapping2_16 *list;
+ const PHYSFS_uint8 count;
+} CaseFoldHashBucket2_16;
+
+typedef struct CaseFoldHashBucket3_16
+{
+ const CaseFoldMapping3_16 *list;
+ const PHYSFS_uint8 count;
+} CaseFoldHashBucket3_16;
+
+static const CaseFoldMapping1_16 case_fold1_16_000[] = {
+ { 0x0202, 0x0203 },
+ { 0x0404, 0x0454 },
+ { 0x1E1E, 0x1E1F },
+ { 0x2C2C, 0x2C5C },
+ { 0xABAB, 0x13DB }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_001[] = {
+ { 0x0100, 0x0101 },
+ { 0x0405, 0x0455 },
+ { 0x0504, 0x0505 },
+ { 0x2C2D, 0x2C5D },
+ { 0xA7A6, 0xA7A7 },
+ { 0xABAA, 0x13DA }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_002[] = {
+ { 0x0200, 0x0201 },
+ { 0x0406, 0x0456 },
+ { 0x1E1C, 0x1E1D },
+ { 0x1F1D, 0x1F15 },
+ { 0x2C2E, 0x2C5E },
+ { 0xABA9, 0x13D9 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_003[] = {
+ { 0x0102, 0x0103 },
+ { 0x0407, 0x0457 },
+ { 0x0506, 0x0507 },
+ { 0x1F1C, 0x1F14 },
+ { 0xA7A4, 0xA7A5 },
+ { 0xABA8, 0x13D8 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_004[] = {
+ { 0x0206, 0x0207 },
+ { 0x0400, 0x0450 },
+ { 0x1E1A, 0x1E1B },
+ { 0x1F1B, 0x1F13 },
+ { 0x2C28, 0x2C58 },
+ { 0xABAF, 0x13DF }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_005[] = {
+ { 0x0104, 0x0105 },
+ { 0x0401, 0x0451 },
+ { 0x0500, 0x0501 },
+ { 0x1F1A, 0x1F12 },
+ { 0x2C29, 0x2C59 },
+ { 0xA7A2, 0xA7A3 },
+ { 0xABAE, 0x13DE }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_006[] = {
+ { 0x0204, 0x0205 },
+ { 0x0402, 0x0452 },
+ { 0x1E18, 0x1E19 },
+ { 0x1F19, 0x1F11 },
+ { 0x2C2A, 0x2C5A },
+ { 0xABAD, 0x13DD }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_007[] = {
+ { 0x0106, 0x0107 },
+ { 0x0403, 0x0453 },
+ { 0x0502, 0x0503 },
+ { 0x1F18, 0x1F10 },
+ { 0x2126, 0x03C9 },
+ { 0x2C2B, 0x2C5B },
+ { 0xA7A0, 0xA7A1 },
+ { 0xABAC, 0x13DC }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_008[] = {
+ { 0x020A, 0x020B },
+ { 0x040C, 0x045C },
+ { 0x1E16, 0x1E17 },
+ { 0x2C24, 0x2C54 },
+ { 0xABA3, 0x13D3 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_009[] = {
+ { 0x0108, 0x0109 },
+ { 0x040D, 0x045D },
+ { 0x050C, 0x050D },
+ { 0x2C25, 0x2C55 },
+ { 0xABA2, 0x13D2 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_010[] = {
+ { 0x0208, 0x0209 },
+ { 0x040E, 0x045E },
+ { 0x1E14, 0x1E15 },
+ { 0x212B, 0x00E5 },
+ { 0x2C26, 0x2C56 },
+ { 0xA7AD, 0x026C },
+ { 0xABA1, 0x13D1 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_011[] = {
+ { 0x010A, 0x010B },
+ { 0x040F, 0x045F },
+ { 0x050E, 0x050F },
+ { 0x212A, 0x006B },
+ { 0x2C27, 0x2C57 },
+ { 0xA7AC, 0x0261 },
+ { 0xABA0, 0x13D0 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_012[] = {
+ { 0x020E, 0x020F },
+ { 0x0408, 0x0458 },
+ { 0x1E12, 0x1E13 },
+ { 0x2C20, 0x2C50 },
+ { 0xA7AB, 0x025C },
+ { 0xABA7, 0x13D7 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_013[] = {
+ { 0x010C, 0x010D },
+ { 0x0409, 0x0459 },
+ { 0x0508, 0x0509 },
+ { 0x2C21, 0x2C51 },
+ { 0xA7AA, 0x0266 },
+ { 0xABA6, 0x13D6 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_014[] = {
+ { 0x020C, 0x020D },
+ { 0x040A, 0x045A },
+ { 0x1E10, 0x1E11 },
+ { 0x2C22, 0x2C52 },
+ { 0xABA5, 0x13D5 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_015[] = {
+ { 0x010E, 0x010F },
+ { 0x040B, 0x045B },
+ { 0x050A, 0x050B },
+ { 0x2C23, 0x2C53 },
+ { 0xA7A8, 0xA7A9 },
+ { 0xABA4, 0x13D4 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_016[] = {
+ { 0x0212, 0x0213 },
+ { 0x0414, 0x0434 },
+ { 0x1E0E, 0x1E0F },
+ { 0x1F0F, 0x1F07 },
+ { 0xABBB, 0x13EB }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_017[] = {
+ { 0x0110, 0x0111 },
+ { 0x0415, 0x0435 },
+ { 0x0514, 0x0515 },
+ { 0x1F0E, 0x1F06 },
+ { 0xA7B6, 0xA7B7 },
+ { 0xABBA, 0x13EA }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_018[] = {
+ { 0x0210, 0x0211 },
+ { 0x0416, 0x0436 },
+ { 0x1E0C, 0x1E0D },
+ { 0x1F0D, 0x1F05 },
+ { 0xABB9, 0x13E9 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_019[] = {
+ { 0x0112, 0x0113 },
+ { 0x0417, 0x0437 },
+ { 0x0516, 0x0517 },
+ { 0x1F0C, 0x1F04 },
+ { 0x2132, 0x214E },
+ { 0xA7B4, 0xA7B5 },
+ { 0xABB8, 0x13E8 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_020[] = {
+ { 0x0216, 0x0217 },
+ { 0x0410, 0x0430 },
+ { 0x1E0A, 0x1E0B },
+ { 0x1F0B, 0x1F03 },
+ { 0xA7B3, 0xAB53 },
+ { 0xABBF, 0x13EF }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_021[] = {
+ { 0x0114, 0x0115 },
+ { 0x0411, 0x0431 },
+ { 0x0510, 0x0511 },
+ { 0x1F0A, 0x1F02 },
+ { 0xA7B2, 0x029D },
+ { 0xABBE, 0x13EE }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_022[] = {
+ { 0x0214, 0x0215 },
+ { 0x0412, 0x0432 },
+ { 0x1E08, 0x1E09 },
+ { 0x1F09, 0x1F01 },
+ { 0xA7B1, 0x0287 },
+ { 0xABBD, 0x13ED }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_023[] = {
+ { 0x0116, 0x0117 },
+ { 0x0413, 0x0433 },
+ { 0x0512, 0x0513 },
+ { 0x1F08, 0x1F00 },
+ { 0xA7B0, 0x029E },
+ { 0xABBC, 0x13EC }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_024[] = {
+ { 0x021A, 0x021B },
+ { 0x041C, 0x043C },
+ { 0x1E06, 0x1E07 },
+ { 0xABB3, 0x13E3 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_025[] = {
+ { 0x0118, 0x0119 },
+ { 0x041D, 0x043D },
+ { 0x051C, 0x051D },
+ { 0xABB2, 0x13E2 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_026[] = {
+ { 0x0218, 0x0219 },
+ { 0x041E, 0x043E },
+ { 0x1E04, 0x1E05 },
+ { 0xABB1, 0x13E1 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_027[] = {
+ { 0x011A, 0x011B },
+ { 0x041F, 0x043F },
+ { 0x051E, 0x051F },
+ { 0xABB0, 0x13E0 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_028[] = {
+ { 0x021E, 0x021F },
+ { 0x0418, 0x0438 },
+ { 0x1E02, 0x1E03 },
+ { 0xABB7, 0x13E7 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_029[] = {
+ { 0x011C, 0x011D },
+ { 0x0419, 0x0439 },
+ { 0x0518, 0x0519 },
+ { 0xABB6, 0x13E6 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_030[] = {
+ { 0x021C, 0x021D },
+ { 0x041A, 0x043A },
+ { 0x1E00, 0x1E01 },
+ { 0xABB5, 0x13E5 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_031[] = {
+ { 0x011E, 0x011F },
+ { 0x041B, 0x043B },
+ { 0x051A, 0x051B },
+ { 0xABB4, 0x13E4 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_032[] = {
+ { 0x0222, 0x0223 },
+ { 0x0424, 0x0444 },
+ { 0x1E3E, 0x1E3F },
+ { 0x1F3F, 0x1F37 },
+ { 0x2C0C, 0x2C3C },
+ { 0xA686, 0xA687 },
+ { 0xAB8B, 0x13BB }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_033[] = {
+ { 0x0120, 0x0121 },
+ { 0x0425, 0x0445 },
+ { 0x0524, 0x0525 },
+ { 0x1F3E, 0x1F36 },
+ { 0x2C0D, 0x2C3D },
+ { 0xA786, 0xA787 },
+ { 0xAB8A, 0x13BA }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_034[] = {
+ { 0x0220, 0x019E },
+ { 0x0426, 0x0446 },
+ { 0x1E3C, 0x1E3D },
+ { 0x1F3D, 0x1F35 },
+ { 0x2C0E, 0x2C3E },
+ { 0xA684, 0xA685 },
+ { 0xAB89, 0x13B9 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_035[] = {
+ { 0x0122, 0x0123 },
+ { 0x0427, 0x0447 },
+ { 0x0526, 0x0527 },
+ { 0x1F3C, 0x1F34 },
+ { 0x2C0F, 0x2C3F },
+ { 0xA784, 0xA785 },
+ { 0xAB88, 0x13B8 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_036[] = {
+ { 0x0226, 0x0227 },
+ { 0x0420, 0x0440 },
+ { 0x1E3A, 0x1E3B },
+ { 0x1F3B, 0x1F33 },
+ { 0x2C08, 0x2C38 },
+ { 0xA682, 0xA683 },
+ { 0xAB8F, 0x13BF }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_037[] = {
+ { 0x0124, 0x0125 },
+ { 0x0421, 0x0441 },
+ { 0x0520, 0x0521 },
+ { 0x1F3A, 0x1F32 },
+ { 0x2C09, 0x2C39 },
+ { 0xA782, 0xA783 },
+ { 0xAB8E, 0x13BE }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_038[] = {
+ { 0x0224, 0x0225 },
+ { 0x0422, 0x0442 },
+ { 0x1E38, 0x1E39 },
+ { 0x1F39, 0x1F31 },
+ { 0x2C0A, 0x2C3A },
+ { 0xA680, 0xA681 },
+ { 0xAB8D, 0x13BD }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_039[] = {
+ { 0x0126, 0x0127 },
+ { 0x0423, 0x0443 },
+ { 0x0522, 0x0523 },
+ { 0x1F38, 0x1F30 },
+ { 0x2C0B, 0x2C3B },
+ { 0xA780, 0xA781 },
+ { 0xAB8C, 0x13BC }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_040[] = {
+ { 0x022A, 0x022B },
+ { 0x042C, 0x044C },
+ { 0x1E36, 0x1E37 },
+ { 0x2C04, 0x2C34 },
+ { 0xA68E, 0xA68F },
+ { 0xAB83, 0x13B3 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_041[] = {
+ { 0x0128, 0x0129 },
+ { 0x042D, 0x044D },
+ { 0x052C, 0x052D },
+ { 0x2C05, 0x2C35 },
+ { 0xAB82, 0x13B2 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_042[] = {
+ { 0x0228, 0x0229 },
+ { 0x042E, 0x044E },
+ { 0x1E34, 0x1E35 },
+ { 0x2C06, 0x2C36 },
+ { 0xA68C, 0xA68D },
+ { 0xA78D, 0x0265 },
+ { 0xAB81, 0x13B1 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_043[] = {
+ { 0x012A, 0x012B },
+ { 0x042F, 0x044F },
+ { 0x052E, 0x052F },
+ { 0x2C07, 0x2C37 },
+ { 0xAB80, 0x13B0 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_044[] = {
+ { 0x022E, 0x022F },
+ { 0x0428, 0x0448 },
+ { 0x1E32, 0x1E33 },
+ { 0x2C00, 0x2C30 },
+ { 0xA68A, 0xA68B },
+ { 0xA78B, 0xA78C },
+ { 0xAB87, 0x13B7 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_045[] = {
+ { 0x012C, 0x012D },
+ { 0x0429, 0x0449 },
+ { 0x0528, 0x0529 },
+ { 0x2C01, 0x2C31 },
+ { 0xAB86, 0x13B6 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_046[] = {
+ { 0x022C, 0x022D },
+ { 0x042A, 0x044A },
+ { 0x1E30, 0x1E31 },
+ { 0x2C02, 0x2C32 },
+ { 0xA688, 0xA689 },
+ { 0xAB85, 0x13B5 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_047[] = {
+ { 0x012E, 0x012F },
+ { 0x042B, 0x044B },
+ { 0x052A, 0x052B },
+ { 0x2C03, 0x2C33 },
+ { 0xAB84, 0x13B4 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_048[] = {
+ { 0x0232, 0x0233 },
+ { 0x0535, 0x0565 },
+ { 0x1E2E, 0x1E2F },
+ { 0x1F2F, 0x1F27 },
+ { 0x2C1C, 0x2C4C },
+ { 0xA696, 0xA697 },
+ { 0xAB9B, 0x13CB }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_049[] = {
+ { 0x0534, 0x0564 },
+ { 0x1F2E, 0x1F26 },
+ { 0x2C1D, 0x2C4D },
+ { 0xA796, 0xA797 },
+ { 0xAB9A, 0x13CA }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_050[] = {
+ { 0x0230, 0x0231 },
+ { 0x0537, 0x0567 },
+ { 0x1E2C, 0x1E2D },
+ { 0x1F2D, 0x1F25 },
+ { 0x2C1E, 0x2C4E },
+ { 0xA694, 0xA695 },
+ { 0xAB99, 0x13C9 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_051[] = {
+ { 0x0132, 0x0133 },
+ { 0x0536, 0x0566 },
+ { 0x1F2C, 0x1F24 },
+ { 0x2C1F, 0x2C4F },
+ { 0xAB98, 0x13C8 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_052[] = {
+ { 0x0531, 0x0561 },
+ { 0x1E2A, 0x1E2B },
+ { 0x1F2B, 0x1F23 },
+ { 0x2C18, 0x2C48 },
+ { 0xA692, 0xA693 },
+ { 0xAB9F, 0x13CF }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_053[] = {
+ { 0x0134, 0x0135 },
+ { 0x1F2A, 0x1F22 },
+ { 0x2C19, 0x2C49 },
+ { 0xA792, 0xA793 },
+ { 0xAB9E, 0x13CE }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_054[] = {
+ { 0x0533, 0x0563 },
+ { 0x1E28, 0x1E29 },
+ { 0x1F29, 0x1F21 },
+ { 0x2C1A, 0x2C4A },
+ { 0xA690, 0xA691 },
+ { 0xAB9D, 0x13CD }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_055[] = {
+ { 0x0136, 0x0137 },
+ { 0x0532, 0x0562 },
+ { 0x1F28, 0x1F20 },
+ { 0x2C1B, 0x2C4B },
+ { 0xA790, 0xA791 },
+ { 0xAB9C, 0x13CC }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_056[] = {
+ { 0x0139, 0x013A },
+ { 0x023A, 0x2C65 },
+ { 0x053D, 0x056D },
+ { 0x1E26, 0x1E27 },
+ { 0x2C14, 0x2C44 },
+ { 0xAB93, 0x13C3 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_057[] = {
+ { 0x023B, 0x023C },
+ { 0x053C, 0x056C },
+ { 0x2C15, 0x2C45 },
+ { 0xA79E, 0xA79F },
+ { 0xAB92, 0x13C2 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_058[] = {
+ { 0x013B, 0x013C },
+ { 0x053F, 0x056F },
+ { 0x1E24, 0x1E25 },
+ { 0x2C16, 0x2C46 },
+ { 0xAB91, 0x13C1 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_059[] = {
+ { 0x053E, 0x056E },
+ { 0x2C17, 0x2C47 },
+ { 0xA79C, 0xA79D },
+ { 0xAB90, 0x13C0 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_060[] = {
+ { 0x013D, 0x013E },
+ { 0x023E, 0x2C66 },
+ { 0x0539, 0x0569 },
+ { 0x1E22, 0x1E23 },
+ { 0x2C10, 0x2C40 },
+ { 0xA69A, 0xA69B },
+ { 0xAB97, 0x13C7 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_061[] = {
+ { 0x0538, 0x0568 },
+ { 0x2C11, 0x2C41 },
+ { 0xA79A, 0xA79B },
+ { 0xAB96, 0x13C6 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_062[] = {
+ { 0x013F, 0x0140 },
+ { 0x053B, 0x056B },
+ { 0x1E20, 0x1E21 },
+ { 0x2C12, 0x2C42 },
+ { 0xA698, 0xA699 },
+ { 0xAB95, 0x13C5 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_063[] = {
+ { 0x023D, 0x019A },
+ { 0x053A, 0x056A },
+ { 0x2C13, 0x2C43 },
+ { 0xA798, 0xA799 },
+ { 0xAB94, 0x13C4 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_064[] = {
+ { 0x0141, 0x0142 },
+ { 0x0545, 0x0575 },
+ { 0x1E5E, 0x1E5F },
+ { 0x1F5F, 0x1F57 },
+ { 0x2161, 0x2171 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_065[] = {
+ { 0x0041, 0x0061 },
+ { 0x0243, 0x0180 },
+ { 0x0544, 0x0574 },
+ { 0x2160, 0x2170 },
+ { 0x2C6D, 0x0251 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_066[] = {
+ { 0x0042, 0x0062 },
+ { 0x0143, 0x0144 },
+ { 0x0547, 0x0577 },
+ { 0x1E5C, 0x1E5D },
+ { 0x1F5D, 0x1F55 },
+ { 0x2163, 0x2173 },
+ { 0x2C6E, 0x0271 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_067[] = {
+ { 0x0043, 0x0063 },
+ { 0x0241, 0x0242 },
+ { 0x0546, 0x0576 },
+ { 0x2162, 0x2172 },
+ { 0x2C6F, 0x0250 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_068[] = {
+ { 0x0044, 0x0064 },
+ { 0x0145, 0x0146 },
+ { 0x0246, 0x0247 },
+ { 0x0541, 0x0571 },
+ { 0x1E5A, 0x1E5B },
+ { 0x1F5B, 0x1F53 },
+ { 0x2165, 0x2175 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_069[] = {
+ { 0x0045, 0x0065 },
+ { 0x0540, 0x0570 },
+ { 0x2164, 0x2174 },
+ { 0x2C69, 0x2C6A }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_070[] = {
+ { 0x0046, 0x0066 },
+ { 0x0147, 0x0148 },
+ { 0x0244, 0x0289 },
+ { 0x0345, 0x03B9 },
+ { 0x0543, 0x0573 },
+ { 0x1E58, 0x1E59 },
+ { 0x1F59, 0x1F51 },
+ { 0x2167, 0x2177 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_071[] = {
+ { 0x0047, 0x0067 },
+ { 0x0245, 0x028C },
+ { 0x0542, 0x0572 },
+ { 0x2166, 0x2176 },
+ { 0x2C6B, 0x2C6C }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_072[] = {
+ { 0x0048, 0x0068 },
+ { 0x024A, 0x024B },
+ { 0x054D, 0x057D },
+ { 0x1E56, 0x1E57 },
+ { 0x2169, 0x2179 },
+ { 0x2C64, 0x027D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_073[] = {
+ { 0x0049, 0x0069 },
+ { 0x054C, 0x057C },
+ { 0x2168, 0x2178 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_074[] = {
+ { 0x004A, 0x006A },
+ { 0x0248, 0x0249 },
+ { 0x054F, 0x057F },
+ { 0x1E54, 0x1E55 },
+ { 0x216B, 0x217B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_075[] = {
+ { 0x004B, 0x006B },
+ { 0x014A, 0x014B },
+ { 0x054E, 0x057E },
+ { 0x216A, 0x217A },
+ { 0x2C67, 0x2C68 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_076[] = {
+ { 0x004C, 0x006C },
+ { 0x024E, 0x024F },
+ { 0x0549, 0x0579 },
+ { 0x1E52, 0x1E53 },
+ { 0x216D, 0x217D },
+ { 0x2C60, 0x2C61 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_077[] = {
+ { 0x004D, 0x006D },
+ { 0x014C, 0x014D },
+ { 0x0548, 0x0578 },
+ { 0x216C, 0x217C }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_078[] = {
+ { 0x004E, 0x006E },
+ { 0x024C, 0x024D },
+ { 0x054B, 0x057B },
+ { 0x1E50, 0x1E51 },
+ { 0x216F, 0x217F },
+ { 0x2C62, 0x026B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_079[] = {
+ { 0x004F, 0x006F },
+ { 0x014E, 0x014F },
+ { 0x054A, 0x057A },
+ { 0x216E, 0x217E },
+ { 0x2C63, 0x1D7D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_080[] = {
+ { 0x0050, 0x0070 },
+ { 0x0555, 0x0585 },
+ { 0x1E4E, 0x1E4F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_081[] = {
+ { 0x0051, 0x0071 },
+ { 0x0150, 0x0151 },
+ { 0x0554, 0x0584 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_082[] = {
+ { 0x0052, 0x0072 },
+ { 0x1E4C, 0x1E4D },
+ { 0x1F4D, 0x1F45 },
+ { 0x2C7E, 0x023F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_083[] = {
+ { 0x0053, 0x0073 },
+ { 0x0152, 0x0153 },
+ { 0x0556, 0x0586 },
+ { 0x1F4C, 0x1F44 },
+ { 0x2C7F, 0x0240 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_084[] = {
+ { 0x0054, 0x0074 },
+ { 0x0551, 0x0581 },
+ { 0x1E4A, 0x1E4B },
+ { 0x1F4B, 0x1F43 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_085[] = {
+ { 0x0055, 0x0075 },
+ { 0x0154, 0x0155 },
+ { 0x0550, 0x0580 },
+ { 0x1F4A, 0x1F42 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_086[] = {
+ { 0x0056, 0x0076 },
+ { 0x0553, 0x0583 },
+ { 0x1E48, 0x1E49 },
+ { 0x1F49, 0x1F41 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_087[] = {
+ { 0x0057, 0x0077 },
+ { 0x0156, 0x0157 },
+ { 0x0552, 0x0582 },
+ { 0x1F48, 0x1F40 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_088[] = {
+ { 0x0058, 0x0078 },
+ { 0x1E46, 0x1E47 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_089[] = {
+ { 0x0059, 0x0079 },
+ { 0x0158, 0x0159 },
+ { 0x2C75, 0x2C76 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_090[] = {
+ { 0x005A, 0x007A },
+ { 0x1E44, 0x1E45 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_091[] = {
+ { 0x015A, 0x015B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_092[] = {
+ { 0x1E42, 0x1E43 },
+ { 0x2C70, 0x0252 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_093[] = {
+ { 0x015C, 0x015D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_094[] = {
+ { 0x1E40, 0x1E41 },
+ { 0x2C72, 0x2C73 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_095[] = {
+ { 0x015E, 0x015F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_096[] = {
+ { 0x0464, 0x0465 },
+ { 0x1E7E, 0x1E7F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_097[] = {
+ { 0x0160, 0x0161 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_098[] = {
+ { 0x0466, 0x0467 },
+ { 0x1E7C, 0x1E7D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_099[] = {
+ { 0x0162, 0x0163 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_100[] = {
+ { 0x0460, 0x0461 },
+ { 0x1E7A, 0x1E7B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_101[] = {
+ { 0x0164, 0x0165 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_102[] = {
+ { 0x0462, 0x0463 },
+ { 0x1E78, 0x1E79 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_103[] = {
+ { 0x0166, 0x0167 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_104[] = {
+ { 0x046C, 0x046D },
+ { 0x1E76, 0x1E77 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_105[] = {
+ { 0x0168, 0x0169 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_106[] = {
+ { 0x046E, 0x046F },
+ { 0x1E74, 0x1E75 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_107[] = {
+ { 0x016A, 0x016B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_108[] = {
+ { 0x0468, 0x0469 },
+ { 0x1E72, 0x1E73 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_109[] = {
+ { 0x016C, 0x016D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_110[] = {
+ { 0x046A, 0x046B },
+ { 0x1E70, 0x1E71 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_111[] = {
+ { 0x016E, 0x016F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_112[] = {
+ { 0x0474, 0x0475 },
+ { 0x1E6E, 0x1E6F },
+ { 0x1F6F, 0x1F67 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_113[] = {
+ { 0x0170, 0x0171 },
+ { 0x0372, 0x0373 },
+ { 0x1F6E, 0x1F66 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_114[] = {
+ { 0x0476, 0x0477 },
+ { 0x1E6C, 0x1E6D },
+ { 0x1F6D, 0x1F65 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_115[] = {
+ { 0x0172, 0x0173 },
+ { 0x0370, 0x0371 },
+ { 0x1F6C, 0x1F64 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_116[] = {
+ { 0x0470, 0x0471 },
+ { 0x1E6A, 0x1E6B },
+ { 0x1F6B, 0x1F63 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_117[] = {
+ { 0x0174, 0x0175 },
+ { 0x0376, 0x0377 },
+ { 0x1F6A, 0x1F62 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_118[] = {
+ { 0x0472, 0x0473 },
+ { 0x1E68, 0x1E69 },
+ { 0x1F69, 0x1F61 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_119[] = {
+ { 0x0176, 0x0177 },
+ { 0x1F68, 0x1F60 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_120[] = {
+ { 0x0179, 0x017A },
+ { 0x047C, 0x047D },
+ { 0x1E66, 0x1E67 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_121[] = {
+ { 0x0178, 0x00FF }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_122[] = {
+ { 0x017B, 0x017C },
+ { 0x047E, 0x047F },
+ { 0x1E64, 0x1E65 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_124[] = {
+ { 0x017D, 0x017E },
+ { 0x037F, 0x03F3 },
+ { 0x0478, 0x0479 },
+ { 0x1E62, 0x1E63 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_126[] = {
+ { 0x017F, 0x0073 },
+ { 0x047A, 0x047B },
+ { 0x1E60, 0x1E61 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_128[] = {
+ { 0x0181, 0x0253 },
+ { 0x2CAC, 0x2CAD }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_129[] = {
+ { 0xA726, 0xA727 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_130[] = {
+ { 0x2CAE, 0x2CAF }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_131[] = {
+ { 0x0182, 0x0183 },
+ { 0xA724, 0xA725 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_132[] = {
+ { 0x0480, 0x0481 },
+ { 0x2CA8, 0x2CA9 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_133[] = {
+ { 0x0184, 0x0185 },
+ { 0x0386, 0x03AC },
+ { 0x1E9B, 0x1E61 },
+ { 0xA722, 0xA723 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_134[] = {
+ { 0x0187, 0x0188 },
+ { 0x2CAA, 0x2CAB }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_135[] = {
+ { 0x0186, 0x0254 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_136[] = {
+ { 0x0189, 0x0256 },
+ { 0x048C, 0x048D },
+ { 0x2CA4, 0x2CA5 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_137[] = {
+ { 0x038A, 0x03AF },
+ { 0xA72E, 0xA72F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_138[] = {
+ { 0x018B, 0x018C },
+ { 0x0389, 0x03AE },
+ { 0x048E, 0x048F },
+ { 0x1E94, 0x1E95 },
+ { 0x2CA6, 0x2CA7 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_139[] = {
+ { 0x018A, 0x0257 },
+ { 0x0388, 0x03AD },
+ { 0xA72C, 0xA72D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_140[] = {
+ { 0x038F, 0x03CE },
+ { 0x1E92, 0x1E93 },
+ { 0x2CA0, 0x2CA1 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_141[] = {
+ { 0x038E, 0x03CD },
+ { 0xA72A, 0xA72B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_142[] = {
+ { 0x018F, 0x0259 },
+ { 0x048A, 0x048B },
+ { 0x1E90, 0x1E91 },
+ { 0x2CA2, 0x2CA3 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_143[] = {
+ { 0x018E, 0x01DD },
+ { 0x038C, 0x03CC },
+ { 0xA728, 0xA729 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_144[] = {
+ { 0x0191, 0x0192 },
+ { 0x0393, 0x03B3 },
+ { 0x0494, 0x0495 },
+ { 0x1E8E, 0x1E8F },
+ { 0x2CBC, 0x2CBD }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_145[] = {
+ { 0x0190, 0x025B },
+ { 0x0392, 0x03B2 },
+ { 0xA736, 0xA737 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_146[] = {
+ { 0x0193, 0x0260 },
+ { 0x0391, 0x03B1 },
+ { 0x0496, 0x0497 },
+ { 0x1E8C, 0x1E8D },
+ { 0x24B6, 0x24D0 },
+ { 0x2CBE, 0x2CBF }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_147[] = {
+ { 0x24B7, 0x24D1 },
+ { 0xA734, 0xA735 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_148[] = {
+ { 0x0397, 0x03B7 },
+ { 0x0490, 0x0491 },
+ { 0x1E8A, 0x1E8B },
+ { 0x2CB8, 0x2CB9 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_149[] = {
+ { 0x0194, 0x0263 },
+ { 0x0396, 0x03B6 },
+ { 0xA732, 0xA733 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_150[] = {
+ { 0x0197, 0x0268 },
+ { 0x0395, 0x03B5 },
+ { 0x0492, 0x0493 },
+ { 0x1E88, 0x1E89 },
+ { 0x2CBA, 0x2CBB }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_151[] = {
+ { 0x0196, 0x0269 },
+ { 0x0394, 0x03B4 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_152[] = {
+ { 0x039B, 0x03BB },
+ { 0x049C, 0x049D },
+ { 0x1E86, 0x1E87 },
+ { 0x24BC, 0x24D6 },
+ { 0x2CB4, 0x2CB5 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_153[] = {
+ { 0x0198, 0x0199 },
+ { 0x039A, 0x03BA },
+ { 0x24BD, 0x24D7 },
+ { 0xA73E, 0xA73F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_154[] = {
+ { 0x0399, 0x03B9 },
+ { 0x049E, 0x049F },
+ { 0x1E84, 0x1E85 },
+ { 0x24BE, 0x24D8 },
+ { 0x2CB6, 0x2CB7 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_155[] = {
+ { 0x0398, 0x03B8 },
+ { 0x24BF, 0x24D9 },
+ { 0xA73C, 0xA73D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_156[] = {
+ { 0x019D, 0x0272 },
+ { 0x039F, 0x03BF },
+ { 0x0498, 0x0499 },
+ { 0x1E82, 0x1E83 },
+ { 0x24B8, 0x24D2 },
+ { 0x2CB0, 0x2CB1 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_157[] = {
+ { 0x019C, 0x026F },
+ { 0x039E, 0x03BE },
+ { 0x24B9, 0x24D3 },
+ { 0xA73A, 0xA73B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_158[] = {
+ { 0x019F, 0x0275 },
+ { 0x039D, 0x03BD },
+ { 0x049A, 0x049B },
+ { 0x1E80, 0x1E81 },
+ { 0x24BA, 0x24D4 },
+ { 0x2CB2, 0x2CB3 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_159[] = {
+ { 0x039C, 0x03BC },
+ { 0x24BB, 0x24D5 },
+ { 0xA738, 0xA739 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_160[] = {
+ { 0x03A3, 0x03C3 },
+ { 0x04A4, 0x04A5 },
+ { 0x10B0, 0x2D10 },
+ { 0x1EBE, 0x1EBF },
+ { 0x2C8C, 0x2C8D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_161[] = {
+ { 0x01A0, 0x01A1 },
+ { 0x10B1, 0x2D11 },
+ { 0x1FBE, 0x03B9 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_162[] = {
+ { 0x03A1, 0x03C1 },
+ { 0x04A6, 0x04A7 },
+ { 0x10B2, 0x2D12 },
+ { 0x1EBC, 0x1EBD },
+ { 0x2183, 0x2184 },
+ { 0x2C8E, 0x2C8F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_163[] = {
+ { 0x01A2, 0x01A3 },
+ { 0x03A0, 0x03C0 },
+ { 0x10B3, 0x2D13 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_164[] = {
+ { 0x03A7, 0x03C7 },
+ { 0x04A0, 0x04A1 },
+ { 0x10B4, 0x2D14 },
+ { 0x1EBA, 0x1EBB },
+ { 0x1FBB, 0x1F71 },
+ { 0x2C88, 0x2C89 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_165[] = {
+ { 0x01A4, 0x01A5 },
+ { 0x03A6, 0x03C6 },
+ { 0x10B5, 0x2D15 },
+ { 0x1FBA, 0x1F70 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_166[] = {
+ { 0x01A7, 0x01A8 },
+ { 0x03A5, 0x03C5 },
+ { 0x04A2, 0x04A3 },
+ { 0x10B6, 0x2D16 },
+ { 0x1EB8, 0x1EB9 },
+ { 0x1FB9, 0x1FB1 },
+ { 0x2C8A, 0x2C8B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_167[] = {
+ { 0x01A6, 0x0280 },
+ { 0x03A4, 0x03C4 },
+ { 0x10B7, 0x2D17 },
+ { 0x1FB8, 0x1FB0 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_168[] = {
+ { 0x01A9, 0x0283 },
+ { 0x03AB, 0x03CB },
+ { 0x04AC, 0x04AD },
+ { 0x10B8, 0x2D18 },
+ { 0x1EB6, 0x1EB7 },
+ { 0x2C84, 0x2C85 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_169[] = {
+ { 0x03AA, 0x03CA },
+ { 0x10B9, 0x2D19 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_170[] = {
+ { 0x03A9, 0x03C9 },
+ { 0x04AE, 0x04AF },
+ { 0x10BA, 0x2D1A },
+ { 0x1EB4, 0x1EB5 },
+ { 0x2C86, 0x2C87 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_171[] = {
+ { 0x03A8, 0x03C8 },
+ { 0x10BB, 0x2D1B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_172[] = {
+ { 0x04A8, 0x04A9 },
+ { 0x10BC, 0x2D1C },
+ { 0x1EB2, 0x1EB3 },
+ { 0x2C80, 0x2C81 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_173[] = {
+ { 0x01AC, 0x01AD },
+ { 0x10BD, 0x2D1D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_174[] = {
+ { 0x01AF, 0x01B0 },
+ { 0x04AA, 0x04AB },
+ { 0x10BE, 0x2D1E },
+ { 0x1EB0, 0x1EB1 },
+ { 0x2C82, 0x2C83 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_175[] = {
+ { 0x01AE, 0x0288 },
+ { 0x10BF, 0x2D1F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_176[] = {
+ { 0x01B1, 0x028A },
+ { 0x04B4, 0x04B5 },
+ { 0x10A0, 0x2D00 },
+ { 0x1EAE, 0x1EAF },
+ { 0x2C9C, 0x2C9D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_177[] = {
+ { 0x10A1, 0x2D01 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_178[] = {
+ { 0x01B3, 0x01B4 },
+ { 0x04B6, 0x04B7 },
+ { 0x10A2, 0x2D02 },
+ { 0x1EAC, 0x1EAD },
+ { 0x2C9E, 0x2C9F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_179[] = {
+ { 0x01B2, 0x028B },
+ { 0x10A3, 0x2D03 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_180[] = {
+ { 0x01B5, 0x01B6 },
+ { 0x04B0, 0x04B1 },
+ { 0x10A4, 0x2D04 },
+ { 0x1EAA, 0x1EAB },
+ { 0x2C98, 0x2C99 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_181[] = {
+ { 0x00B5, 0x03BC },
+ { 0x10A5, 0x2D05 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_182[] = {
+ { 0x01B7, 0x0292 },
+ { 0x04B2, 0x04B3 },
+ { 0x10A6, 0x2D06 },
+ { 0x1EA8, 0x1EA9 },
+ { 0x2C9A, 0x2C9B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_183[] = {
+ { 0x10A7, 0x2D07 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_184[] = {
+ { 0x04BC, 0x04BD },
+ { 0x10A8, 0x2D08 },
+ { 0x1EA6, 0x1EA7 },
+ { 0x2C94, 0x2C95 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_185[] = {
+ { 0x01B8, 0x01B9 },
+ { 0x10A9, 0x2D09 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_186[] = {
+ { 0x04BE, 0x04BF },
+ { 0x10AA, 0x2D0A },
+ { 0x1EA4, 0x1EA5 },
+ { 0x2C96, 0x2C97 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_187[] = {
+ { 0x10AB, 0x2D0B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_188[] = {
+ { 0x04B8, 0x04B9 },
+ { 0x10AC, 0x2D0C },
+ { 0x1EA2, 0x1EA3 },
+ { 0x2C90, 0x2C91 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_189[] = {
+ { 0x01BC, 0x01BD },
+ { 0x10AD, 0x2D0D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_190[] = {
+ { 0x04BA, 0x04BB },
+ { 0x10AE, 0x2D0E },
+ { 0x1EA0, 0x1EA1 },
+ { 0x2C92, 0x2C93 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_191[] = {
+ { 0x10AF, 0x2D0F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_192[] = {
+ { 0x00C0, 0x00E0 },
+ { 0x1EDE, 0x1EDF },
+ { 0xA666, 0xA667 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_193[] = {
+ { 0x00C1, 0x00E1 },
+ { 0x03C2, 0x03C3 },
+ { 0x04C5, 0x04C6 },
+ { 0x2CED, 0x2CEE },
+ { 0xA766, 0xA767 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_194[] = {
+ { 0x00C2, 0x00E2 },
+ { 0x1EDC, 0x1EDD },
+ { 0xA664, 0xA665 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_195[] = {
+ { 0x00C3, 0x00E3 },
+ { 0x04C7, 0x04C8 },
+ { 0xA764, 0xA765 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_196[] = {
+ { 0x00C4, 0x00E4 },
+ { 0x01C5, 0x01C6 },
+ { 0x04C0, 0x04CF },
+ { 0x1EDA, 0x1EDB },
+ { 0x1FDB, 0x1F77 },
+ { 0xA662, 0xA663 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_197[] = {
+ { 0x00C5, 0x00E5 },
+ { 0x01C4, 0x01C6 },
+ { 0x04C1, 0x04C2 },
+ { 0x1FDA, 0x1F76 },
+ { 0xA762, 0xA763 },
+ { 0xFF3A, 0xFF5A }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_198[] = {
+ { 0x00C6, 0x00E6 },
+ { 0x01C7, 0x01C9 },
+ { 0x1ED8, 0x1ED9 },
+ { 0x1FD9, 0x1FD1 },
+ { 0xA660, 0xA661 },
+ { 0xFF39, 0xFF59 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_199[] = {
+ { 0x00C7, 0x00E7 },
+ { 0x04C3, 0x04C4 },
+ { 0x1FD8, 0x1FD0 },
+ { 0x2CEB, 0x2CEC },
+ { 0xA760, 0xA761 },
+ { 0xFF38, 0xFF58 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_200[] = {
+ { 0x00C8, 0x00E8 },
+ { 0x1ED6, 0x1ED7 },
+ { 0xFF37, 0xFF57 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_201[] = {
+ { 0x00C9, 0x00E9 },
+ { 0x01C8, 0x01C9 },
+ { 0x04CD, 0x04CE },
+ { 0xA76E, 0xA76F },
+ { 0xFF36, 0xFF56 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_202[] = {
+ { 0x00CA, 0x00EA },
+ { 0x01CB, 0x01CC },
+ { 0x1ED4, 0x1ED5 },
+ { 0xA66C, 0xA66D },
+ { 0xFF35, 0xFF55 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_203[] = {
+ { 0x00CB, 0x00EB },
+ { 0x01CA, 0x01CC },
+ { 0xA76C, 0xA76D },
+ { 0xFF34, 0xFF54 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_204[] = {
+ { 0x00CC, 0x00EC },
+ { 0x01CD, 0x01CE },
+ { 0x03CF, 0x03D7 },
+ { 0x1ED2, 0x1ED3 },
+ { 0x2CE0, 0x2CE1 },
+ { 0xA66A, 0xA66B },
+ { 0xFF33, 0xFF53 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_205[] = {
+ { 0x00CD, 0x00ED },
+ { 0x04C9, 0x04CA },
+ { 0xA76A, 0xA76B },
+ { 0xFF32, 0xFF52 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_206[] = {
+ { 0x00CE, 0x00EE },
+ { 0x01CF, 0x01D0 },
+ { 0x1ED0, 0x1ED1 },
+ { 0x2CE2, 0x2CE3 },
+ { 0xA668, 0xA669 },
+ { 0xFF31, 0xFF51 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_207[] = {
+ { 0x00CF, 0x00EF },
+ { 0x04CB, 0x04CC },
+ { 0xA768, 0xA769 },
+ { 0xFF30, 0xFF50 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_208[] = {
+ { 0x00D0, 0x00F0 },
+ { 0x01D1, 0x01D2 },
+ { 0x04D4, 0x04D5 },
+ { 0x10C0, 0x2D20 },
+ { 0x1ECE, 0x1ECF },
+ { 0xAB7B, 0x13AB },
+ { 0xFF2F, 0xFF4F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_209[] = {
+ { 0x00D1, 0x00F1 },
+ { 0x10C1, 0x2D21 },
+ { 0xAB7A, 0x13AA },
+ { 0xFF2E, 0xFF4E }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_210[] = {
+ { 0x00D2, 0x00F2 },
+ { 0x01D3, 0x01D4 },
+ { 0x03D1, 0x03B8 },
+ { 0x04D6, 0x04D7 },
+ { 0x10C2, 0x2D22 },
+ { 0x1ECC, 0x1ECD },
+ { 0xAB79, 0x13A9 },
+ { 0xFF2D, 0xFF4D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_211[] = {
+ { 0x00D3, 0x00F3 },
+ { 0x03D0, 0x03B2 },
+ { 0x10C3, 0x2D23 },
+ { 0xAB78, 0x13A8 },
+ { 0xFF2C, 0xFF4C }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_212[] = {
+ { 0x00D4, 0x00F4 },
+ { 0x01D5, 0x01D6 },
+ { 0x04D0, 0x04D1 },
+ { 0x10C4, 0x2D24 },
+ { 0x1ECA, 0x1ECB },
+ { 0x1FCB, 0x1F75 },
+ { 0xAB7F, 0x13AF },
+ { 0xFF2B, 0xFF4B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_213[] = {
+ { 0x00D5, 0x00F5 },
+ { 0x03D6, 0x03C0 },
+ { 0x10C5, 0x2D25 },
+ { 0x1FCA, 0x1F74 },
+ { 0xAB7E, 0x13AE },
+ { 0xFF2A, 0xFF4A }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_214[] = {
+ { 0x00D6, 0x00F6 },
+ { 0x01D7, 0x01D8 },
+ { 0x03D5, 0x03C6 },
+ { 0x04D2, 0x04D3 },
+ { 0x1EC8, 0x1EC9 },
+ { 0x1FC9, 0x1F73 },
+ { 0xAB7D, 0x13AD },
+ { 0xFF29, 0xFF49 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_215[] = {
+ { 0x10C7, 0x2D27 },
+ { 0x1FC8, 0x1F72 },
+ { 0xAB7C, 0x13AC },
+ { 0xFF28, 0xFF48 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_216[] = {
+ { 0x00D8, 0x00F8 },
+ { 0x01D9, 0x01DA },
+ { 0x04DC, 0x04DD },
+ { 0x1EC6, 0x1EC7 },
+ { 0xAB73, 0x13A3 },
+ { 0xFF27, 0xFF47 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_217[] = {
+ { 0x00D9, 0x00F9 },
+ { 0x03DA, 0x03DB },
+ { 0xA77E, 0xA77F },
+ { 0xAB72, 0x13A2 },
+ { 0xFF26, 0xFF46 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_218[] = {
+ { 0x00DA, 0x00FA },
+ { 0x01DB, 0x01DC },
+ { 0x04DE, 0x04DF },
+ { 0x1EC4, 0x1EC5 },
+ { 0xA77D, 0x1D79 },
+ { 0xAB71, 0x13A1 },
+ { 0xFF25, 0xFF45 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_219[] = {
+ { 0x00DB, 0x00FB },
+ { 0x03D8, 0x03D9 },
+ { 0xAB70, 0x13A0 },
+ { 0xFF24, 0xFF44 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_220[] = {
+ { 0x00DC, 0x00FC },
+ { 0x04D8, 0x04D9 },
+ { 0x1EC2, 0x1EC3 },
+ { 0xA77B, 0xA77C },
+ { 0xAB77, 0x13A7 },
+ { 0xFF23, 0xFF43 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_221[] = {
+ { 0x00DD, 0x00FD },
+ { 0x03DE, 0x03DF },
+ { 0x10CD, 0x2D2D },
+ { 0xAB76, 0x13A6 },
+ { 0xFF22, 0xFF42 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_222[] = {
+ { 0x00DE, 0x00FE },
+ { 0x04DA, 0x04DB },
+ { 0x1EC0, 0x1EC1 },
+ { 0x2CF2, 0x2CF3 },
+ { 0xA779, 0xA77A },
+ { 0xAB75, 0x13A5 },
+ { 0xFF21, 0xFF41 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_223[] = {
+ { 0x01DE, 0x01DF },
+ { 0x03DC, 0x03DD },
+ { 0xAB74, 0x13A4 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_224[] = {
+ { 0x04E4, 0x04E5 },
+ { 0x1EFE, 0x1EFF },
+ { 0x24C4, 0x24DE },
+ { 0x2CCC, 0x2CCD },
+ { 0xA646, 0xA647 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_225[] = {
+ { 0x01E0, 0x01E1 },
+ { 0x03E2, 0x03E3 },
+ { 0x24C5, 0x24DF },
+ { 0xA746, 0xA747 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_226[] = {
+ { 0x04E6, 0x04E7 },
+ { 0x1EFC, 0x1EFD },
+ { 0x24C6, 0x24E0 },
+ { 0x2CCE, 0x2CCF },
+ { 0xA644, 0xA645 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_227[] = {
+ { 0x01E2, 0x01E3 },
+ { 0x03E0, 0x03E1 },
+ { 0x24C7, 0x24E1 },
+ { 0xA744, 0xA745 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_228[] = {
+ { 0x04E0, 0x04E1 },
+ { 0x1EFA, 0x1EFB },
+ { 0x1FFB, 0x1F7D },
+ { 0x24C0, 0x24DA },
+ { 0x2CC8, 0x2CC9 },
+ { 0xA642, 0xA643 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_229[] = {
+ { 0x01E4, 0x01E5 },
+ { 0x03E6, 0x03E7 },
+ { 0x1FFA, 0x1F7C },
+ { 0x24C1, 0x24DB },
+ { 0xA742, 0xA743 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_230[] = {
+ { 0x04E2, 0x04E3 },
+ { 0x1EF8, 0x1EF9 },
+ { 0x1FF9, 0x1F79 },
+ { 0x24C2, 0x24DC },
+ { 0x2CCA, 0x2CCB },
+ { 0xA640, 0xA641 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_231[] = {
+ { 0x01E6, 0x01E7 },
+ { 0x03E4, 0x03E5 },
+ { 0x1FF8, 0x1F78 },
+ { 0x24C3, 0x24DD },
+ { 0xA740, 0xA741 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_232[] = {
+ { 0x04EC, 0x04ED },
+ { 0x13FB, 0x13F3 },
+ { 0x1EF6, 0x1EF7 },
+ { 0x24CC, 0x24E6 },
+ { 0x2CC4, 0x2CC5 },
+ { 0xA64E, 0xA64F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_233[] = {
+ { 0x01E8, 0x01E9 },
+ { 0x03EA, 0x03EB },
+ { 0x13FA, 0x13F2 },
+ { 0x24CD, 0x24E7 },
+ { 0xA74E, 0xA74F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_234[] = {
+ { 0x04EE, 0x04EF },
+ { 0x13F9, 0x13F1 },
+ { 0x1EF4, 0x1EF5 },
+ { 0x24CE, 0x24E8 },
+ { 0x2CC6, 0x2CC7 },
+ { 0xA64C, 0xA64D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_235[] = {
+ { 0x01EA, 0x01EB },
+ { 0x03E8, 0x03E9 },
+ { 0x13F8, 0x13F0 },
+ { 0x24CF, 0x24E9 },
+ { 0xA74C, 0xA74D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_236[] = {
+ { 0x04E8, 0x04E9 },
+ { 0x1EF2, 0x1EF3 },
+ { 0x24C8, 0x24E2 },
+ { 0x2CC0, 0x2CC1 },
+ { 0xA64A, 0xA64B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_237[] = {
+ { 0x01EC, 0x01ED },
+ { 0x03EE, 0x03EF },
+ { 0x24C9, 0x24E3 },
+ { 0xA74A, 0xA74B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_238[] = {
+ { 0x04EA, 0x04EB },
+ { 0x13FD, 0x13F5 },
+ { 0x1EF0, 0x1EF1 },
+ { 0x24CA, 0x24E4 },
+ { 0x2CC2, 0x2CC3 },
+ { 0xA648, 0xA649 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_239[] = {
+ { 0x01EE, 0x01EF },
+ { 0x03EC, 0x03ED },
+ { 0x13FC, 0x13F4 },
+ { 0x24CB, 0x24E5 },
+ { 0xA748, 0xA749 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_240[] = {
+ { 0x01F1, 0x01F3 },
+ { 0x04F4, 0x04F5 },
+ { 0x1EEE, 0x1EEF },
+ { 0x2CDC, 0x2CDD },
+ { 0xA656, 0xA657 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_241[] = {
+ { 0xA756, 0xA757 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_242[] = {
+ { 0x03F1, 0x03C1 },
+ { 0x04F6, 0x04F7 },
+ { 0x1EEC, 0x1EED },
+ { 0x2CDE, 0x2CDF },
+ { 0xA654, 0xA655 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_243[] = {
+ { 0x01F2, 0x01F3 },
+ { 0x03F0, 0x03BA },
+ { 0x1FEC, 0x1FE5 },
+ { 0xA754, 0xA755 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_244[] = {
+ { 0x03F7, 0x03F8 },
+ { 0x04F0, 0x04F1 },
+ { 0x1EEA, 0x1EEB },
+ { 0x1FEB, 0x1F7B },
+ { 0x2CD8, 0x2CD9 },
+ { 0xA652, 0xA653 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_245[] = {
+ { 0x01F4, 0x01F5 },
+ { 0x1FEA, 0x1F7A },
+ { 0xA752, 0xA753 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_246[] = {
+ { 0x01F7, 0x01BF },
+ { 0x03F5, 0x03B5 },
+ { 0x04F2, 0x04F3 },
+ { 0x1EE8, 0x1EE9 },
+ { 0x1FE9, 0x1FE1 },
+ { 0x2CDA, 0x2CDB },
+ { 0xA650, 0xA651 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_247[] = {
+ { 0x01F6, 0x0195 },
+ { 0x03F4, 0x03B8 },
+ { 0x1FE8, 0x1FE0 },
+ { 0xA750, 0xA751 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_248[] = {
+ { 0x04FC, 0x04FD },
+ { 0x1EE6, 0x1EE7 },
+ { 0x2CD4, 0x2CD5 },
+ { 0xA65E, 0xA65F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_249[] = {
+ { 0x01F8, 0x01F9 },
+ { 0x03FA, 0x03FB },
+ { 0xA75E, 0xA75F }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_250[] = {
+ { 0x03F9, 0x03F2 },
+ { 0x04FE, 0x04FF },
+ { 0x1EE4, 0x1EE5 },
+ { 0x2CD6, 0x2CD7 },
+ { 0xA65C, 0xA65D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_251[] = {
+ { 0x01FA, 0x01FB },
+ { 0xA75C, 0xA75D }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_252[] = {
+ { 0x03FF, 0x037D },
+ { 0x04F8, 0x04F9 },
+ { 0x1EE2, 0x1EE3 },
+ { 0x2CD0, 0x2CD1 },
+ { 0xA65A, 0xA65B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_253[] = {
+ { 0x01FC, 0x01FD },
+ { 0x03FE, 0x037C },
+ { 0xA75A, 0xA75B }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_254[] = {
+ { 0x03FD, 0x037B },
+ { 0x04FA, 0x04FB },
+ { 0x1EE0, 0x1EE1 },
+ { 0x2CD2, 0x2CD3 },
+ { 0xA658, 0xA659 }
+};
+
+static const CaseFoldMapping1_16 case_fold1_16_255[] = {
+ { 0x01FE, 0x01FF },
+ { 0xA758, 0xA759 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_000[] = {
+ { 0x10404, 0x1042C },
+ { 0x10414, 0x1043C },
+ { 0x10424, 0x1044C },
+ { 0x10C8C, 0x10CCC },
+ { 0x10C9C, 0x10CDC },
+ { 0x10CAC, 0x10CEC },
+ { 0x118A8, 0x118C8 },
+ { 0x118B8, 0x118D8 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_001[] = {
+ { 0x10405, 0x1042D },
+ { 0x10415, 0x1043D },
+ { 0x10425, 0x1044D },
+ { 0x10C8D, 0x10CCD },
+ { 0x10C9D, 0x10CDD },
+ { 0x10CAD, 0x10CED },
+ { 0x118A9, 0x118C9 },
+ { 0x118B9, 0x118D9 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_002[] = {
+ { 0x10406, 0x1042E },
+ { 0x10416, 0x1043E },
+ { 0x10426, 0x1044E },
+ { 0x10C8E, 0x10CCE },
+ { 0x10C9E, 0x10CDE },
+ { 0x10CAE, 0x10CEE },
+ { 0x118AA, 0x118CA },
+ { 0x118BA, 0x118DA }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_003[] = {
+ { 0x10407, 0x1042F },
+ { 0x10417, 0x1043F },
+ { 0x10427, 0x1044F },
+ { 0x10C8F, 0x10CCF },
+ { 0x10C9F, 0x10CDF },
+ { 0x10CAF, 0x10CEF },
+ { 0x118AB, 0x118CB },
+ { 0x118BB, 0x118DB }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_004[] = {
+ { 0x10400, 0x10428 },
+ { 0x10410, 0x10438 },
+ { 0x10420, 0x10448 },
+ { 0x10C88, 0x10CC8 },
+ { 0x10C98, 0x10CD8 },
+ { 0x10CA8, 0x10CE8 },
+ { 0x118AC, 0x118CC },
+ { 0x118BC, 0x118DC }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_005[] = {
+ { 0x10401, 0x10429 },
+ { 0x10411, 0x10439 },
+ { 0x10421, 0x10449 },
+ { 0x10C89, 0x10CC9 },
+ { 0x10C99, 0x10CD9 },
+ { 0x10CA9, 0x10CE9 },
+ { 0x118AD, 0x118CD },
+ { 0x118BD, 0x118DD }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_006[] = {
+ { 0x10402, 0x1042A },
+ { 0x10412, 0x1043A },
+ { 0x10422, 0x1044A },
+ { 0x10C8A, 0x10CCA },
+ { 0x10C9A, 0x10CDA },
+ { 0x10CAA, 0x10CEA },
+ { 0x118AE, 0x118CE },
+ { 0x118BE, 0x118DE }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_007[] = {
+ { 0x10403, 0x1042B },
+ { 0x10413, 0x1043B },
+ { 0x10423, 0x1044B },
+ { 0x10C8B, 0x10CCB },
+ { 0x10C9B, 0x10CDB },
+ { 0x10CAB, 0x10CEB },
+ { 0x118AF, 0x118CF },
+ { 0x118BF, 0x118DF }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_008[] = {
+ { 0x1040C, 0x10434 },
+ { 0x1041C, 0x10444 },
+ { 0x10C84, 0x10CC4 },
+ { 0x10C94, 0x10CD4 },
+ { 0x10CA4, 0x10CE4 },
+ { 0x118A0, 0x118C0 },
+ { 0x118B0, 0x118D0 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_009[] = {
+ { 0x1040D, 0x10435 },
+ { 0x1041D, 0x10445 },
+ { 0x10C85, 0x10CC5 },
+ { 0x10C95, 0x10CD5 },
+ { 0x10CA5, 0x10CE5 },
+ { 0x118A1, 0x118C1 },
+ { 0x118B1, 0x118D1 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_010[] = {
+ { 0x1040E, 0x10436 },
+ { 0x1041E, 0x10446 },
+ { 0x10C86, 0x10CC6 },
+ { 0x10C96, 0x10CD6 },
+ { 0x10CA6, 0x10CE6 },
+ { 0x118A2, 0x118C2 },
+ { 0x118B2, 0x118D2 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_011[] = {
+ { 0x1040F, 0x10437 },
+ { 0x1041F, 0x10447 },
+ { 0x10C87, 0x10CC7 },
+ { 0x10C97, 0x10CD7 },
+ { 0x10CA7, 0x10CE7 },
+ { 0x118A3, 0x118C3 },
+ { 0x118B3, 0x118D3 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_012[] = {
+ { 0x10408, 0x10430 },
+ { 0x10418, 0x10440 },
+ { 0x10C80, 0x10CC0 },
+ { 0x10C90, 0x10CD0 },
+ { 0x10CA0, 0x10CE0 },
+ { 0x10CB0, 0x10CF0 },
+ { 0x118A4, 0x118C4 },
+ { 0x118B4, 0x118D4 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_013[] = {
+ { 0x10409, 0x10431 },
+ { 0x10419, 0x10441 },
+ { 0x10C81, 0x10CC1 },
+ { 0x10C91, 0x10CD1 },
+ { 0x10CA1, 0x10CE1 },
+ { 0x10CB1, 0x10CF1 },
+ { 0x118A5, 0x118C5 },
+ { 0x118B5, 0x118D5 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_014[] = {
+ { 0x1040A, 0x10432 },
+ { 0x1041A, 0x10442 },
+ { 0x10C82, 0x10CC2 },
+ { 0x10C92, 0x10CD2 },
+ { 0x10CA2, 0x10CE2 },
+ { 0x10CB2, 0x10CF2 },
+ { 0x118A6, 0x118C6 },
+ { 0x118B6, 0x118D6 }
+};
+
+static const CaseFoldMapping1_32 case_fold1_32_015[] = {
+ { 0x1040B, 0x10433 },
+ { 0x1041B, 0x10443 },
+ { 0x10C83, 0x10CC3 },
+ { 0x10C93, 0x10CD3 },
+ { 0x10CA3, 0x10CE3 },
+ { 0x118A7, 0x118C7 },
+ { 0x118B7, 0x118D7 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_000[] = {
+ { 0x1E9E, 0x0073, 0x0073 },
+ { 0x1F8F, 0x1F07, 0x03B9 },
+ { 0x1F9F, 0x1F27, 0x03B9 },
+ { 0x1FAF, 0x1F67, 0x03B9 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_001[] = {
+ { 0x0130, 0x0069, 0x0307 },
+ { 0x01F0, 0x006A, 0x030C },
+ { 0x1F8E, 0x1F06, 0x03B9 },
+ { 0x1F9E, 0x1F26, 0x03B9 },
+ { 0x1FAE, 0x1F66, 0x03B9 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_002[] = {
+ { 0x0587, 0x0565, 0x0582 },
+ { 0x1F8D, 0x1F05, 0x03B9 },
+ { 0x1F9D, 0x1F25, 0x03B9 },
+ { 0x1FAD, 0x1F65, 0x03B9 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_003[] = {
+ { 0x1F8C, 0x1F04, 0x03B9 },
+ { 0x1F9C, 0x1F24, 0x03B9 },
+ { 0x1FAC, 0x1F64, 0x03B9 },
+ { 0x1FBC, 0x03B1, 0x03B9 },
+ { 0x1FCC, 0x03B7, 0x03B9 },
+ { 0x1FFC, 0x03C9, 0x03B9 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_004[] = {
+ { 0x1E9A, 0x0061, 0x02BE },
+ { 0x1F8B, 0x1F03, 0x03B9 },
+ { 0x1F9B, 0x1F23, 0x03B9 },
+ { 0x1FAB, 0x1F63, 0x03B9 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_005[] = {
+ { 0x1F8A, 0x1F02, 0x03B9 },
+ { 0x1F9A, 0x1F22, 0x03B9 },
+ { 0x1FAA, 0x1F62, 0x03B9 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_006[] = {
+ { 0x1E98, 0x0077, 0x030A },
+ { 0x1F89, 0x1F01, 0x03B9 },
+ { 0x1F99, 0x1F21, 0x03B9 },
+ { 0x1FA9, 0x1F61, 0x03B9 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_007[] = {
+ { 0x1E99, 0x0079, 0x030A },
+ { 0x1F88, 0x1F00, 0x03B9 },
+ { 0x1F98, 0x1F20, 0x03B9 },
+ { 0x1FA8, 0x1F60, 0x03B9 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_008[] = {
+ { 0x0149, 0x02BC, 0x006E },
+ { 0x1E96, 0x0068, 0x0331 },
+ { 0x1F87, 0x1F07, 0x03B9 },
+ { 0x1F97, 0x1F27, 0x03B9 },
+ { 0x1FA7, 0x1F67, 0x03B9 },
+ { 0xFB13, 0x0574, 0x0576 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_009[] = {
+ { 0x1E97, 0x0074, 0x0308 },
+ { 0x1F86, 0x1F06, 0x03B9 },
+ { 0x1F96, 0x1F26, 0x03B9 },
+ { 0x1FA6, 0x1F66, 0x03B9 },
+ { 0x1FB6, 0x03B1, 0x0342 },
+ { 0x1FC6, 0x03B7, 0x0342 },
+ { 0x1FD6, 0x03B9, 0x0342 },
+ { 0x1FE6, 0x03C5, 0x0342 },
+ { 0x1FF6, 0x03C9, 0x0342 },
+ { 0xFB02, 0x0066, 0x006C }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_010[] = {
+ { 0x1F85, 0x1F05, 0x03B9 },
+ { 0x1F95, 0x1F25, 0x03B9 },
+ { 0x1FA5, 0x1F65, 0x03B9 },
+ { 0xFB01, 0x0066, 0x0069 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_011[] = {
+ { 0x1F84, 0x1F04, 0x03B9 },
+ { 0x1F94, 0x1F24, 0x03B9 },
+ { 0x1FA4, 0x1F64, 0x03B9 },
+ { 0x1FB4, 0x03AC, 0x03B9 },
+ { 0x1FC4, 0x03AE, 0x03B9 },
+ { 0x1FE4, 0x03C1, 0x0313 },
+ { 0x1FF4, 0x03CE, 0x03B9 },
+ { 0xFB00, 0x0066, 0x0066 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_012[] = {
+ { 0x1F83, 0x1F03, 0x03B9 },
+ { 0x1F93, 0x1F23, 0x03B9 },
+ { 0x1FA3, 0x1F63, 0x03B9 },
+ { 0x1FB3, 0x03B1, 0x03B9 },
+ { 0x1FC3, 0x03B7, 0x03B9 },
+ { 0x1FF3, 0x03C9, 0x03B9 },
+ { 0xFB17, 0x0574, 0x056D }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_013[] = {
+ { 0x1F82, 0x1F02, 0x03B9 },
+ { 0x1F92, 0x1F22, 0x03B9 },
+ { 0x1FA2, 0x1F62, 0x03B9 },
+ { 0x1FB2, 0x1F70, 0x03B9 },
+ { 0x1FC2, 0x1F74, 0x03B9 },
+ { 0x1FF2, 0x1F7C, 0x03B9 },
+ { 0xFB06, 0x0073, 0x0074 },
+ { 0xFB16, 0x057E, 0x0576 }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_014[] = {
+ { 0x1F81, 0x1F01, 0x03B9 },
+ { 0x1F91, 0x1F21, 0x03B9 },
+ { 0x1FA1, 0x1F61, 0x03B9 },
+ { 0xFB05, 0x0073, 0x0074 },
+ { 0xFB15, 0x0574, 0x056B }
+};
+
+static const CaseFoldMapping2_16 case_fold2_16_015[] = {
+ { 0x00DF, 0x0073, 0x0073 },
+ { 0x1F50, 0x03C5, 0x0313 },
+ { 0x1F80, 0x1F00, 0x03B9 },
+ { 0x1F90, 0x1F20, 0x03B9 },
+ { 0x1FA0, 0x1F60, 0x03B9 },
+ { 0xFB14, 0x0574, 0x0565 }
+};
+
+static const CaseFoldMapping3_16 case_fold3_16_000[] = {
+ { 0x1FB7, 0x03B1, 0x0342, 0x03B9 },
+ { 0x1FC7, 0x03B7, 0x0342, 0x03B9 },
+ { 0x1FD3, 0x03B9, 0x0308, 0x0301 },
+ { 0x1FD7, 0x03B9, 0x0308, 0x0342 },
+ { 0x1FE3, 0x03C5, 0x0308, 0x0301 },
+ { 0x1FE7, 0x03C5, 0x0308, 0x0342 },
+ { 0x1FF7, 0x03C9, 0x0342, 0x03B9 },
+ { 0xFB03, 0x0066, 0x0066, 0x0069 }
+};
+
+static const CaseFoldMapping3_16 case_fold3_16_001[] = {
+ { 0x1F52, 0x03C5, 0x0313, 0x0300 },
+ { 0x1F56, 0x03C5, 0x0313, 0x0342 },
+ { 0x1FD2, 0x03B9, 0x0308, 0x0300 },
+ { 0x1FE2, 0x03C5, 0x0308, 0x0300 }
+};
+
+static const CaseFoldMapping3_16 case_fold3_16_003[] = {
+ { 0x0390, 0x03B9, 0x0308, 0x0301 },
+ { 0x03B0, 0x03C5, 0x0308, 0x0301 },
+ { 0x1F54, 0x03C5, 0x0313, 0x0301 },
+ { 0xFB04, 0x0066, 0x0066, 0x006C }
+};
+
+static const CaseFoldHashBucket1_16 case_fold_hash1_16[] = {
+ { case_fold1_16_000, __PHYSFS_ARRAYLEN(case_fold1_16_000) },
+ { case_fold1_16_001, __PHYSFS_ARRAYLEN(case_fold1_16_001) },
+ { case_fold1_16_002, __PHYSFS_ARRAYLEN(case_fold1_16_002) },
+ { case_fold1_16_003, __PHYSFS_ARRAYLEN(case_fold1_16_003) },
+ { case_fold1_16_004, __PHYSFS_ARRAYLEN(case_fold1_16_004) },
+ { case_fold1_16_005, __PHYSFS_ARRAYLEN(case_fold1_16_005) },
+ { case_fold1_16_006, __PHYSFS_ARRAYLEN(case_fold1_16_006) },
+ { case_fold1_16_007, __PHYSFS_ARRAYLEN(case_fold1_16_007) },
+ { case_fold1_16_008, __PHYSFS_ARRAYLEN(case_fold1_16_008) },
+ { case_fold1_16_009, __PHYSFS_ARRAYLEN(case_fold1_16_009) },
+ { case_fold1_16_010, __PHYSFS_ARRAYLEN(case_fold1_16_010) },
+ { case_fold1_16_011, __PHYSFS_ARRAYLEN(case_fold1_16_011) },
+ { case_fold1_16_012, __PHYSFS_ARRAYLEN(case_fold1_16_012) },
+ { case_fold1_16_013, __PHYSFS_ARRAYLEN(case_fold1_16_013) },
+ { case_fold1_16_014, __PHYSFS_ARRAYLEN(case_fold1_16_014) },
+ { case_fold1_16_015, __PHYSFS_ARRAYLEN(case_fold1_16_015) },
+ { case_fold1_16_016, __PHYSFS_ARRAYLEN(case_fold1_16_016) },
+ { case_fold1_16_017, __PHYSFS_ARRAYLEN(case_fold1_16_017) },
+ { case_fold1_16_018, __PHYSFS_ARRAYLEN(case_fold1_16_018) },
+ { case_fold1_16_019, __PHYSFS_ARRAYLEN(case_fold1_16_019) },
+ { case_fold1_16_020, __PHYSFS_ARRAYLEN(case_fold1_16_020) },
+ { case_fold1_16_021, __PHYSFS_ARRAYLEN(case_fold1_16_021) },
+ { case_fold1_16_022, __PHYSFS_ARRAYLEN(case_fold1_16_022) },
+ { case_fold1_16_023, __PHYSFS_ARRAYLEN(case_fold1_16_023) },
+ { case_fold1_16_024, __PHYSFS_ARRAYLEN(case_fold1_16_024) },
+ { case_fold1_16_025, __PHYSFS_ARRAYLEN(case_fold1_16_025) },
+ { case_fold1_16_026, __PHYSFS_ARRAYLEN(case_fold1_16_026) },
+ { case_fold1_16_027, __PHYSFS_ARRAYLEN(case_fold1_16_027) },
+ { case_fold1_16_028, __PHYSFS_ARRAYLEN(case_fold1_16_028) },
+ { case_fold1_16_029, __PHYSFS_ARRAYLEN(case_fold1_16_029) },
+ { case_fold1_16_030, __PHYSFS_ARRAYLEN(case_fold1_16_030) },
+ { case_fold1_16_031, __PHYSFS_ARRAYLEN(case_fold1_16_031) },
+ { case_fold1_16_032, __PHYSFS_ARRAYLEN(case_fold1_16_032) },
+ { case_fold1_16_033, __PHYSFS_ARRAYLEN(case_fold1_16_033) },
+ { case_fold1_16_034, __PHYSFS_ARRAYLEN(case_fold1_16_034) },
+ { case_fold1_16_035, __PHYSFS_ARRAYLEN(case_fold1_16_035) },
+ { case_fold1_16_036, __PHYSFS_ARRAYLEN(case_fold1_16_036) },
+ { case_fold1_16_037, __PHYSFS_ARRAYLEN(case_fold1_16_037) },
+ { case_fold1_16_038, __PHYSFS_ARRAYLEN(case_fold1_16_038) },
+ { case_fold1_16_039, __PHYSFS_ARRAYLEN(case_fold1_16_039) },
+ { case_fold1_16_040, __PHYSFS_ARRAYLEN(case_fold1_16_040) },
+ { case_fold1_16_041, __PHYSFS_ARRAYLEN(case_fold1_16_041) },
+ { case_fold1_16_042, __PHYSFS_ARRAYLEN(case_fold1_16_042) },
+ { case_fold1_16_043, __PHYSFS_ARRAYLEN(case_fold1_16_043) },
+ { case_fold1_16_044, __PHYSFS_ARRAYLEN(case_fold1_16_044) },
+ { case_fold1_16_045, __PHYSFS_ARRAYLEN(case_fold1_16_045) },
+ { case_fold1_16_046, __PHYSFS_ARRAYLEN(case_fold1_16_046) },
+ { case_fold1_16_047, __PHYSFS_ARRAYLEN(case_fold1_16_047) },
+ { case_fold1_16_048, __PHYSFS_ARRAYLEN(case_fold1_16_048) },
+ { case_fold1_16_049, __PHYSFS_ARRAYLEN(case_fold1_16_049) },
+ { case_fold1_16_050, __PHYSFS_ARRAYLEN(case_fold1_16_050) },
+ { case_fold1_16_051, __PHYSFS_ARRAYLEN(case_fold1_16_051) },
+ { case_fold1_16_052, __PHYSFS_ARRAYLEN(case_fold1_16_052) },
+ { case_fold1_16_053, __PHYSFS_ARRAYLEN(case_fold1_16_053) },
+ { case_fold1_16_054, __PHYSFS_ARRAYLEN(case_fold1_16_054) },
+ { case_fold1_16_055, __PHYSFS_ARRAYLEN(case_fold1_16_055) },
+ { case_fold1_16_056, __PHYSFS_ARRAYLEN(case_fold1_16_056) },
+ { case_fold1_16_057, __PHYSFS_ARRAYLEN(case_fold1_16_057) },
+ { case_fold1_16_058, __PHYSFS_ARRAYLEN(case_fold1_16_058) },
+ { case_fold1_16_059, __PHYSFS_ARRAYLEN(case_fold1_16_059) },
+ { case_fold1_16_060, __PHYSFS_ARRAYLEN(case_fold1_16_060) },
+ { case_fold1_16_061, __PHYSFS_ARRAYLEN(case_fold1_16_061) },
+ { case_fold1_16_062, __PHYSFS_ARRAYLEN(case_fold1_16_062) },
+ { case_fold1_16_063, __PHYSFS_ARRAYLEN(case_fold1_16_063) },
+ { case_fold1_16_064, __PHYSFS_ARRAYLEN(case_fold1_16_064) },
+ { case_fold1_16_065, __PHYSFS_ARRAYLEN(case_fold1_16_065) },
+ { case_fold1_16_066, __PHYSFS_ARRAYLEN(case_fold1_16_066) },
+ { case_fold1_16_067, __PHYSFS_ARRAYLEN(case_fold1_16_067) },
+ { case_fold1_16_068, __PHYSFS_ARRAYLEN(case_fold1_16_068) },
+ { case_fold1_16_069, __PHYSFS_ARRAYLEN(case_fold1_16_069) },
+ { case_fold1_16_070, __PHYSFS_ARRAYLEN(case_fold1_16_070) },
+ { case_fold1_16_071, __PHYSFS_ARRAYLEN(case_fold1_16_071) },
+ { case_fold1_16_072, __PHYSFS_ARRAYLEN(case_fold1_16_072) },
+ { case_fold1_16_073, __PHYSFS_ARRAYLEN(case_fold1_16_073) },
+ { case_fold1_16_074, __PHYSFS_ARRAYLEN(case_fold1_16_074) },
+ { case_fold1_16_075, __PHYSFS_ARRAYLEN(case_fold1_16_075) },
+ { case_fold1_16_076, __PHYSFS_ARRAYLEN(case_fold1_16_076) },
+ { case_fold1_16_077, __PHYSFS_ARRAYLEN(case_fold1_16_077) },
+ { case_fold1_16_078, __PHYSFS_ARRAYLEN(case_fold1_16_078) },
+ { case_fold1_16_079, __PHYSFS_ARRAYLEN(case_fold1_16_079) },
+ { case_fold1_16_080, __PHYSFS_ARRAYLEN(case_fold1_16_080) },
+ { case_fold1_16_081, __PHYSFS_ARRAYLEN(case_fold1_16_081) },
+ { case_fold1_16_082, __PHYSFS_ARRAYLEN(case_fold1_16_082) },
+ { case_fold1_16_083, __PHYSFS_ARRAYLEN(case_fold1_16_083) },
+ { case_fold1_16_084, __PHYSFS_ARRAYLEN(case_fold1_16_084) },
+ { case_fold1_16_085, __PHYSFS_ARRAYLEN(case_fold1_16_085) },
+ { case_fold1_16_086, __PHYSFS_ARRAYLEN(case_fold1_16_086) },
+ { case_fold1_16_087, __PHYSFS_ARRAYLEN(case_fold1_16_087) },
+ { case_fold1_16_088, __PHYSFS_ARRAYLEN(case_fold1_16_088) },
+ { case_fold1_16_089, __PHYSFS_ARRAYLEN(case_fold1_16_089) },
+ { case_fold1_16_090, __PHYSFS_ARRAYLEN(case_fold1_16_090) },
+ { case_fold1_16_091, __PHYSFS_ARRAYLEN(case_fold1_16_091) },
+ { case_fold1_16_092, __PHYSFS_ARRAYLEN(case_fold1_16_092) },
+ { case_fold1_16_093, __PHYSFS_ARRAYLEN(case_fold1_16_093) },
+ { case_fold1_16_094, __PHYSFS_ARRAYLEN(case_fold1_16_094) },
+ { case_fold1_16_095, __PHYSFS_ARRAYLEN(case_fold1_16_095) },
+ { case_fold1_16_096, __PHYSFS_ARRAYLEN(case_fold1_16_096) },
+ { case_fold1_16_097, __PHYSFS_ARRAYLEN(case_fold1_16_097) },
+ { case_fold1_16_098, __PHYSFS_ARRAYLEN(case_fold1_16_098) },
+ { case_fold1_16_099, __PHYSFS_ARRAYLEN(case_fold1_16_099) },
+ { case_fold1_16_100, __PHYSFS_ARRAYLEN(case_fold1_16_100) },
+ { case_fold1_16_101, __PHYSFS_ARRAYLEN(case_fold1_16_101) },
+ { case_fold1_16_102, __PHYSFS_ARRAYLEN(case_fold1_16_102) },
+ { case_fold1_16_103, __PHYSFS_ARRAYLEN(case_fold1_16_103) },
+ { case_fold1_16_104, __PHYSFS_ARRAYLEN(case_fold1_16_104) },
+ { case_fold1_16_105, __PHYSFS_ARRAYLEN(case_fold1_16_105) },
+ { case_fold1_16_106, __PHYSFS_ARRAYLEN(case_fold1_16_106) },
+ { case_fold1_16_107, __PHYSFS_ARRAYLEN(case_fold1_16_107) },
+ { case_fold1_16_108, __PHYSFS_ARRAYLEN(case_fold1_16_108) },
+ { case_fold1_16_109, __PHYSFS_ARRAYLEN(case_fold1_16_109) },
+ { case_fold1_16_110, __PHYSFS_ARRAYLEN(case_fold1_16_110) },
+ { case_fold1_16_111, __PHYSFS_ARRAYLEN(case_fold1_16_111) },
+ { case_fold1_16_112, __PHYSFS_ARRAYLEN(case_fold1_16_112) },
+ { case_fold1_16_113, __PHYSFS_ARRAYLEN(case_fold1_16_113) },
+ { case_fold1_16_114, __PHYSFS_ARRAYLEN(case_fold1_16_114) },
+ { case_fold1_16_115, __PHYSFS_ARRAYLEN(case_fold1_16_115) },
+ { case_fold1_16_116, __PHYSFS_ARRAYLEN(case_fold1_16_116) },
+ { case_fold1_16_117, __PHYSFS_ARRAYLEN(case_fold1_16_117) },
+ { case_fold1_16_118, __PHYSFS_ARRAYLEN(case_fold1_16_118) },
+ { case_fold1_16_119, __PHYSFS_ARRAYLEN(case_fold1_16_119) },
+ { case_fold1_16_120, __PHYSFS_ARRAYLEN(case_fold1_16_120) },
+ { case_fold1_16_121, __PHYSFS_ARRAYLEN(case_fold1_16_121) },
+ { case_fold1_16_122, __PHYSFS_ARRAYLEN(case_fold1_16_122) },
+ { NULL, 0 },
+ { case_fold1_16_124, __PHYSFS_ARRAYLEN(case_fold1_16_124) },
+ { NULL, 0 },
+ { case_fold1_16_126, __PHYSFS_ARRAYLEN(case_fold1_16_126) },
+ { NULL, 0 },
+ { case_fold1_16_128, __PHYSFS_ARRAYLEN(case_fold1_16_128) },
+ { case_fold1_16_129, __PHYSFS_ARRAYLEN(case_fold1_16_129) },
+ { case_fold1_16_130, __PHYSFS_ARRAYLEN(case_fold1_16_130) },
+ { case_fold1_16_131, __PHYSFS_ARRAYLEN(case_fold1_16_131) },
+ { case_fold1_16_132, __PHYSFS_ARRAYLEN(case_fold1_16_132) },
+ { case_fold1_16_133, __PHYSFS_ARRAYLEN(case_fold1_16_133) },
+ { case_fold1_16_134, __PHYSFS_ARRAYLEN(case_fold1_16_134) },
+ { case_fold1_16_135, __PHYSFS_ARRAYLEN(case_fold1_16_135) },
+ { case_fold1_16_136, __PHYSFS_ARRAYLEN(case_fold1_16_136) },
+ { case_fold1_16_137, __PHYSFS_ARRAYLEN(case_fold1_16_137) },
+ { case_fold1_16_138, __PHYSFS_ARRAYLEN(case_fold1_16_138) },
+ { case_fold1_16_139, __PHYSFS_ARRAYLEN(case_fold1_16_139) },
+ { case_fold1_16_140, __PHYSFS_ARRAYLEN(case_fold1_16_140) },
+ { case_fold1_16_141, __PHYSFS_ARRAYLEN(case_fold1_16_141) },
+ { case_fold1_16_142, __PHYSFS_ARRAYLEN(case_fold1_16_142) },
+ { case_fold1_16_143, __PHYSFS_ARRAYLEN(case_fold1_16_143) },
+ { case_fold1_16_144, __PHYSFS_ARRAYLEN(case_fold1_16_144) },
+ { case_fold1_16_145, __PHYSFS_ARRAYLEN(case_fold1_16_145) },
+ { case_fold1_16_146, __PHYSFS_ARRAYLEN(case_fold1_16_146) },
+ { case_fold1_16_147, __PHYSFS_ARRAYLEN(case_fold1_16_147) },
+ { case_fold1_16_148, __PHYSFS_ARRAYLEN(case_fold1_16_148) },
+ { case_fold1_16_149, __PHYSFS_ARRAYLEN(case_fold1_16_149) },
+ { case_fold1_16_150, __PHYSFS_ARRAYLEN(case_fold1_16_150) },
+ { case_fold1_16_151, __PHYSFS_ARRAYLEN(case_fold1_16_151) },
+ { case_fold1_16_152, __PHYSFS_ARRAYLEN(case_fold1_16_152) },
+ { case_fold1_16_153, __PHYSFS_ARRAYLEN(case_fold1_16_153) },
+ { case_fold1_16_154, __PHYSFS_ARRAYLEN(case_fold1_16_154) },
+ { case_fold1_16_155, __PHYSFS_ARRAYLEN(case_fold1_16_155) },
+ { case_fold1_16_156, __PHYSFS_ARRAYLEN(case_fold1_16_156) },
+ { case_fold1_16_157, __PHYSFS_ARRAYLEN(case_fold1_16_157) },
+ { case_fold1_16_158, __PHYSFS_ARRAYLEN(case_fold1_16_158) },
+ { case_fold1_16_159, __PHYSFS_ARRAYLEN(case_fold1_16_159) },
+ { case_fold1_16_160, __PHYSFS_ARRAYLEN(case_fold1_16_160) },
+ { case_fold1_16_161, __PHYSFS_ARRAYLEN(case_fold1_16_161) },
+ { case_fold1_16_162, __PHYSFS_ARRAYLEN(case_fold1_16_162) },
+ { case_fold1_16_163, __PHYSFS_ARRAYLEN(case_fold1_16_163) },
+ { case_fold1_16_164, __PHYSFS_ARRAYLEN(case_fold1_16_164) },
+ { case_fold1_16_165, __PHYSFS_ARRAYLEN(case_fold1_16_165) },
+ { case_fold1_16_166, __PHYSFS_ARRAYLEN(case_fold1_16_166) },
+ { case_fold1_16_167, __PHYSFS_ARRAYLEN(case_fold1_16_167) },
+ { case_fold1_16_168, __PHYSFS_ARRAYLEN(case_fold1_16_168) },
+ { case_fold1_16_169, __PHYSFS_ARRAYLEN(case_fold1_16_169) },
+ { case_fold1_16_170, __PHYSFS_ARRAYLEN(case_fold1_16_170) },
+ { case_fold1_16_171, __PHYSFS_ARRAYLEN(case_fold1_16_171) },
+ { case_fold1_16_172, __PHYSFS_ARRAYLEN(case_fold1_16_172) },
+ { case_fold1_16_173, __PHYSFS_ARRAYLEN(case_fold1_16_173) },
+ { case_fold1_16_174, __PHYSFS_ARRAYLEN(case_fold1_16_174) },
+ { case_fold1_16_175, __PHYSFS_ARRAYLEN(case_fold1_16_175) },
+ { case_fold1_16_176, __PHYSFS_ARRAYLEN(case_fold1_16_176) },
+ { case_fold1_16_177, __PHYSFS_ARRAYLEN(case_fold1_16_177) },
+ { case_fold1_16_178, __PHYSFS_ARRAYLEN(case_fold1_16_178) },
+ { case_fold1_16_179, __PHYSFS_ARRAYLEN(case_fold1_16_179) },
+ { case_fold1_16_180, __PHYSFS_ARRAYLEN(case_fold1_16_180) },
+ { case_fold1_16_181, __PHYSFS_ARRAYLEN(case_fold1_16_181) },
+ { case_fold1_16_182, __PHYSFS_ARRAYLEN(case_fold1_16_182) },
+ { case_fold1_16_183, __PHYSFS_ARRAYLEN(case_fold1_16_183) },
+ { case_fold1_16_184, __PHYSFS_ARRAYLEN(case_fold1_16_184) },
+ { case_fold1_16_185, __PHYSFS_ARRAYLEN(case_fold1_16_185) },
+ { case_fold1_16_186, __PHYSFS_ARRAYLEN(case_fold1_16_186) },
+ { case_fold1_16_187, __PHYSFS_ARRAYLEN(case_fold1_16_187) },
+ { case_fold1_16_188, __PHYSFS_ARRAYLEN(case_fold1_16_188) },
+ { case_fold1_16_189, __PHYSFS_ARRAYLEN(case_fold1_16_189) },
+ { case_fold1_16_190, __PHYSFS_ARRAYLEN(case_fold1_16_190) },
+ { case_fold1_16_191, __PHYSFS_ARRAYLEN(case_fold1_16_191) },
+ { case_fold1_16_192, __PHYSFS_ARRAYLEN(case_fold1_16_192) },
+ { case_fold1_16_193, __PHYSFS_ARRAYLEN(case_fold1_16_193) },
+ { case_fold1_16_194, __PHYSFS_ARRAYLEN(case_fold1_16_194) },
+ { case_fold1_16_195, __PHYSFS_ARRAYLEN(case_fold1_16_195) },
+ { case_fold1_16_196, __PHYSFS_ARRAYLEN(case_fold1_16_196) },
+ { case_fold1_16_197, __PHYSFS_ARRAYLEN(case_fold1_16_197) },
+ { case_fold1_16_198, __PHYSFS_ARRAYLEN(case_fold1_16_198) },
+ { case_fold1_16_199, __PHYSFS_ARRAYLEN(case_fold1_16_199) },
+ { case_fold1_16_200, __PHYSFS_ARRAYLEN(case_fold1_16_200) },
+ { case_fold1_16_201, __PHYSFS_ARRAYLEN(case_fold1_16_201) },
+ { case_fold1_16_202, __PHYSFS_ARRAYLEN(case_fold1_16_202) },
+ { case_fold1_16_203, __PHYSFS_ARRAYLEN(case_fold1_16_203) },
+ { case_fold1_16_204, __PHYSFS_ARRAYLEN(case_fold1_16_204) },
+ { case_fold1_16_205, __PHYSFS_ARRAYLEN(case_fold1_16_205) },
+ { case_fold1_16_206, __PHYSFS_ARRAYLEN(case_fold1_16_206) },
+ { case_fold1_16_207, __PHYSFS_ARRAYLEN(case_fold1_16_207) },
+ { case_fold1_16_208, __PHYSFS_ARRAYLEN(case_fold1_16_208) },
+ { case_fold1_16_209, __PHYSFS_ARRAYLEN(case_fold1_16_209) },
+ { case_fold1_16_210, __PHYSFS_ARRAYLEN(case_fold1_16_210) },
+ { case_fold1_16_211, __PHYSFS_ARRAYLEN(case_fold1_16_211) },
+ { case_fold1_16_212, __PHYSFS_ARRAYLEN(case_fold1_16_212) },
+ { case_fold1_16_213, __PHYSFS_ARRAYLEN(case_fold1_16_213) },
+ { case_fold1_16_214, __PHYSFS_ARRAYLEN(case_fold1_16_214) },
+ { case_fold1_16_215, __PHYSFS_ARRAYLEN(case_fold1_16_215) },
+ { case_fold1_16_216, __PHYSFS_ARRAYLEN(case_fold1_16_216) },
+ { case_fold1_16_217, __PHYSFS_ARRAYLEN(case_fold1_16_217) },
+ { case_fold1_16_218, __PHYSFS_ARRAYLEN(case_fold1_16_218) },
+ { case_fold1_16_219, __PHYSFS_ARRAYLEN(case_fold1_16_219) },
+ { case_fold1_16_220, __PHYSFS_ARRAYLEN(case_fold1_16_220) },
+ { case_fold1_16_221, __PHYSFS_ARRAYLEN(case_fold1_16_221) },
+ { case_fold1_16_222, __PHYSFS_ARRAYLEN(case_fold1_16_222) },
+ { case_fold1_16_223, __PHYSFS_ARRAYLEN(case_fold1_16_223) },
+ { case_fold1_16_224, __PHYSFS_ARRAYLEN(case_fold1_16_224) },
+ { case_fold1_16_225, __PHYSFS_ARRAYLEN(case_fold1_16_225) },
+ { case_fold1_16_226, __PHYSFS_ARRAYLEN(case_fold1_16_226) },
+ { case_fold1_16_227, __PHYSFS_ARRAYLEN(case_fold1_16_227) },
+ { case_fold1_16_228, __PHYSFS_ARRAYLEN(case_fold1_16_228) },
+ { case_fold1_16_229, __PHYSFS_ARRAYLEN(case_fold1_16_229) },
+ { case_fold1_16_230, __PHYSFS_ARRAYLEN(case_fold1_16_230) },
+ { case_fold1_16_231, __PHYSFS_ARRAYLEN(case_fold1_16_231) },
+ { case_fold1_16_232, __PHYSFS_ARRAYLEN(case_fold1_16_232) },
+ { case_fold1_16_233, __PHYSFS_ARRAYLEN(case_fold1_16_233) },
+ { case_fold1_16_234, __PHYSFS_ARRAYLEN(case_fold1_16_234) },
+ { case_fold1_16_235, __PHYSFS_ARRAYLEN(case_fold1_16_235) },
+ { case_fold1_16_236, __PHYSFS_ARRAYLEN(case_fold1_16_236) },
+ { case_fold1_16_237, __PHYSFS_ARRAYLEN(case_fold1_16_237) },
+ { case_fold1_16_238, __PHYSFS_ARRAYLEN(case_fold1_16_238) },
+ { case_fold1_16_239, __PHYSFS_ARRAYLEN(case_fold1_16_239) },
+ { case_fold1_16_240, __PHYSFS_ARRAYLEN(case_fold1_16_240) },
+ { case_fold1_16_241, __PHYSFS_ARRAYLEN(case_fold1_16_241) },
+ { case_fold1_16_242, __PHYSFS_ARRAYLEN(case_fold1_16_242) },
+ { case_fold1_16_243, __PHYSFS_ARRAYLEN(case_fold1_16_243) },
+ { case_fold1_16_244, __PHYSFS_ARRAYLEN(case_fold1_16_244) },
+ { case_fold1_16_245, __PHYSFS_ARRAYLEN(case_fold1_16_245) },
+ { case_fold1_16_246, __PHYSFS_ARRAYLEN(case_fold1_16_246) },
+ { case_fold1_16_247, __PHYSFS_ARRAYLEN(case_fold1_16_247) },
+ { case_fold1_16_248, __PHYSFS_ARRAYLEN(case_fold1_16_248) },
+ { case_fold1_16_249, __PHYSFS_ARRAYLEN(case_fold1_16_249) },
+ { case_fold1_16_250, __PHYSFS_ARRAYLEN(case_fold1_16_250) },
+ { case_fold1_16_251, __PHYSFS_ARRAYLEN(case_fold1_16_251) },
+ { case_fold1_16_252, __PHYSFS_ARRAYLEN(case_fold1_16_252) },
+ { case_fold1_16_253, __PHYSFS_ARRAYLEN(case_fold1_16_253) },
+ { case_fold1_16_254, __PHYSFS_ARRAYLEN(case_fold1_16_254) },
+ { case_fold1_16_255, __PHYSFS_ARRAYLEN(case_fold1_16_255) },
+};
+
+static const CaseFoldHashBucket1_32 case_fold_hash1_32[] = {
+ { case_fold1_32_000, __PHYSFS_ARRAYLEN(case_fold1_32_000) },
+ { case_fold1_32_001, __PHYSFS_ARRAYLEN(case_fold1_32_001) },
+ { case_fold1_32_002, __PHYSFS_ARRAYLEN(case_fold1_32_002) },
+ { case_fold1_32_003, __PHYSFS_ARRAYLEN(case_fold1_32_003) },
+ { case_fold1_32_004, __PHYSFS_ARRAYLEN(case_fold1_32_004) },
+ { case_fold1_32_005, __PHYSFS_ARRAYLEN(case_fold1_32_005) },
+ { case_fold1_32_006, __PHYSFS_ARRAYLEN(case_fold1_32_006) },
+ { case_fold1_32_007, __PHYSFS_ARRAYLEN(case_fold1_32_007) },
+ { case_fold1_32_008, __PHYSFS_ARRAYLEN(case_fold1_32_008) },
+ { case_fold1_32_009, __PHYSFS_ARRAYLEN(case_fold1_32_009) },
+ { case_fold1_32_010, __PHYSFS_ARRAYLEN(case_fold1_32_010) },
+ { case_fold1_32_011, __PHYSFS_ARRAYLEN(case_fold1_32_011) },
+ { case_fold1_32_012, __PHYSFS_ARRAYLEN(case_fold1_32_012) },
+ { case_fold1_32_013, __PHYSFS_ARRAYLEN(case_fold1_32_013) },
+ { case_fold1_32_014, __PHYSFS_ARRAYLEN(case_fold1_32_014) },
+ { case_fold1_32_015, __PHYSFS_ARRAYLEN(case_fold1_32_015) },
+};
+
+static const CaseFoldHashBucket2_16 case_fold_hash2_16[] = {
+ { case_fold2_16_000, __PHYSFS_ARRAYLEN(case_fold2_16_000) },
+ { case_fold2_16_001, __PHYSFS_ARRAYLEN(case_fold2_16_001) },
+ { case_fold2_16_002, __PHYSFS_ARRAYLEN(case_fold2_16_002) },
+ { case_fold2_16_003, __PHYSFS_ARRAYLEN(case_fold2_16_003) },
+ { case_fold2_16_004, __PHYSFS_ARRAYLEN(case_fold2_16_004) },
+ { case_fold2_16_005, __PHYSFS_ARRAYLEN(case_fold2_16_005) },
+ { case_fold2_16_006, __PHYSFS_ARRAYLEN(case_fold2_16_006) },
+ { case_fold2_16_007, __PHYSFS_ARRAYLEN(case_fold2_16_007) },
+ { case_fold2_16_008, __PHYSFS_ARRAYLEN(case_fold2_16_008) },
+ { case_fold2_16_009, __PHYSFS_ARRAYLEN(case_fold2_16_009) },
+ { case_fold2_16_010, __PHYSFS_ARRAYLEN(case_fold2_16_010) },
+ { case_fold2_16_011, __PHYSFS_ARRAYLEN(case_fold2_16_011) },
+ { case_fold2_16_012, __PHYSFS_ARRAYLEN(case_fold2_16_012) },
+ { case_fold2_16_013, __PHYSFS_ARRAYLEN(case_fold2_16_013) },
+ { case_fold2_16_014, __PHYSFS_ARRAYLEN(case_fold2_16_014) },
+ { case_fold2_16_015, __PHYSFS_ARRAYLEN(case_fold2_16_015) },
+};
+
+static const CaseFoldHashBucket3_16 case_fold_hash3_16[] = {
+ { case_fold3_16_000, __PHYSFS_ARRAYLEN(case_fold3_16_000) },
+ { case_fold3_16_001, __PHYSFS_ARRAYLEN(case_fold3_16_001) },
+ { NULL, 0 },
+ { case_fold3_16_003, __PHYSFS_ARRAYLEN(case_fold3_16_003) },
+};
+
+
+#endif /* _INCLUDE_PHYSFS_CASEFOLDING_H_ */
+
+/* end of physfs_casefolding.h ... */
+
diff --git a/third-party/physfs/src/physfs_internal.h b/third-party/physfs/src/physfs_internal.h
new file mode 100644
index 0000000..2200d4d
--- /dev/null
+++ b/third-party/physfs/src/physfs_internal.h
@@ -0,0 +1,776 @@
+/*
+ * Internal function/structure declaration. Do NOT include in your
+ * application.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#ifndef _INCLUDE_PHYSFS_INTERNAL_H_
+#define _INCLUDE_PHYSFS_INTERNAL_H_
+
+#ifndef __PHYSICSFS_INTERNAL__
+#error Do not include this header from your applications.
+#endif
+
+/* Turn off MSVC warnings that are aggressively anti-portability. */
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS 1
+#endif
+
+#include "physfs.h"
+
+/* The holy trinity. */
+#include
+#include
+#include
+
+#include "physfs_platforms.h"
+
+#include
+
+#define __PHYSFS_COMPILE_TIME_ASSERT(name, x) \
+ typedef int __PHYSFS_compile_time_assert_##name[(x) * 2 - 1]
+
+/* !!! FIXME: remove this when revamping stack allocation code... */
+#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__WATCOMC__)
+#include
+#endif
+
+#if defined(PHYSFS_PLATFORM_SOLARIS) || defined(PHYSFS_PLATFORM_LINUX)
+#include
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __GNUC__
+#define PHYSFS_MINIMUM_GCC_VERSION(major, minor) \
+ ( ((__GNUC__ << 16) + __GNUC_MINOR__) >= (((major) << 16) + (minor)) )
+#else
+#define PHYSFS_MINIMUM_GCC_VERSION(major, minor) (0)
+#endif
+
+#ifdef __cplusplus
+ /* C++ always has a real inline keyword. */
+#elif (defined macintosh) && !(defined __MWERKS__)
+# define inline
+#elif (defined _MSC_VER)
+# define inline __inline
+#endif
+
+#if defined(PHYSFS_PLATFORM_LINUX) && !defined(_FILE_OFFSET_BITS)
+#define _FILE_OFFSET_BITS 64
+#endif
+
+/* All public APIs need to be in physfs.h with a PHYSFS_DECL.
+ All file-private symbols need to be marked "static".
+ Everything shared between PhysicsFS sources needs to be in this
+ file between the visibility pragma blocks. */
+#if !defined(_WIN32) && (PHYSFS_MINIMUM_GCC_VERSION(4,0) || defined(__clang__))
+#define PHYSFS_HAVE_PRAGMA_VISIBILITY 1
+#endif
+
+#if PHYSFS_HAVE_PRAGMA_VISIBILITY
+#pragma GCC visibility push(hidden)
+#endif
+
+/* These are the build-in archivers. We list them all as "extern" here without
+ #ifdefs to keep it tidy, but obviously you need to make sure these are
+ wrapped in PHYSFS_SUPPORTS_* checks before actually referencing them. */
+extern const PHYSFS_Archiver __PHYSFS_Archiver_DIR;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_ZIP;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_7Z;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_GRP;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_QPAK;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_HOG;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_MVL;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_WAD;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_SLB;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_ISO9660;
+extern const PHYSFS_Archiver __PHYSFS_Archiver_VDF;
+
+/* a real C99-compliant snprintf() is in Visual Studio 2015,
+ but just use this everywhere for binary compatibility. */
+#if defined(_MSC_VER)
+#include
+int __PHYSFS_msvc_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap);
+int __PHYSFS_msvc_snprintf(char *outBuf, size_t size, const char *format, ...);
+#define vsnprintf __PHYSFS_msvc_vsnprintf
+#define snprintf __PHYSFS_msvc_snprintf
+#endif
+
+/* Some simple wrappers around WinRT C++ interfaces we can call from C. */
+#ifdef PHYSFS_PLATFORM_WINRT
+const void *__PHYSFS_winrtCalcBaseDir(void);
+const void *__PHYSFS_winrtCalcPrefDir(void);
+#endif
+
+/* atomic operations. */
+/* increment/decrement operations return the final incremented/decremented value. */
+#if defined(_MSC_VER) && (_MSC_VER >= 1500)
+#include
+__PHYSFS_COMPILE_TIME_ASSERT(LongEqualsInt, sizeof (int) == sizeof (long));
+#define __PHYSFS_ATOMIC_INCR(ptrval) _InterlockedIncrement((long*)(ptrval))
+#define __PHYSFS_ATOMIC_DECR(ptrval) _InterlockedDecrement((long*)(ptrval))
+#elif defined(__clang__) || (defined(__GNUC__) && (((__GNUC__ * 10000) + (__GNUC_MINOR__ * 100)) >= 40100))
+#define __PHYSFS_ATOMIC_INCR(ptrval) __sync_add_and_fetch(ptrval, 1)
+#define __PHYSFS_ATOMIC_DECR(ptrval) __sync_add_and_fetch(ptrval, -1)
+#elif defined(__WATCOMC__) && defined(__386__)
+extern __inline int _xadd_watcom(volatile int *a, int v);
+#pragma aux _xadd_watcom = \
+ "lock xadd [ecx], eax" \
+ parm [ecx] [eax] \
+ value [eax] \
+ modify exact [eax];
+#define __PHYSFS_ATOMIC_INCR(ptrval) (_xadd_watcom(ptrval, 1)+1)
+#define __PHYSFS_ATOMIC_DECR(ptrval) (_xadd_watcom(ptrval, -1)-1)
+#else
+#define PHYSFS_NEED_ATOMIC_OP_FALLBACK 1
+int __PHYSFS_ATOMIC_INCR(int *ptrval);
+int __PHYSFS_ATOMIC_DECR(int *ptrval);
+#endif
+
+
+/*
+ * Interface for small allocations. If you need a little scratch space for
+ * a throwaway buffer or string, use this. It will make small allocations
+ * on the stack if possible, and use allocator.Malloc() if they are too
+ * large. This helps reduce malloc pressure.
+ * There are some rules, though:
+ * NEVER return a pointer from this, as stack-allocated buffers go away
+ * when your function returns.
+ * NEVER allocate in a loop, as stack-allocated pointers will pile up. Call
+ * a function that uses smallAlloc from your loop, so the allocation can
+ * free each time.
+ * NEVER call smallAlloc with any complex expression (it's a macro that WILL
+ * have side effects...it references the argument multiple times). Use a
+ * variable or a literal.
+ * NEVER free a pointer from this with anything but smallFree. It will not
+ * be a valid pointer to the allocator, regardless of where the memory came
+ * from.
+ * NEVER realloc a pointer from this.
+ * NEVER forget to use smallFree: it may not be a pointer from the stack.
+ * NEVER forget to check for NULL...allocation can fail here, of course!
+ */
+#define __PHYSFS_SMALLALLOCTHRESHOLD 256
+void *__PHYSFS_initSmallAlloc(void *ptr, const size_t len);
+
+#define __PHYSFS_smallAlloc(bytes) ( \
+ __PHYSFS_initSmallAlloc( \
+ (((bytes) < __PHYSFS_SMALLALLOCTHRESHOLD) ? \
+ alloca((size_t)((bytes)+sizeof(void*))) : NULL), (bytes)) \
+)
+
+void __PHYSFS_smallFree(void *ptr);
+
+
+/* Use the allocation hooks. */
+#define malloc(x) Do not use malloc() directly.
+#define realloc(x, y) Do not use realloc() directly.
+#define free(x) Do not use free() directly.
+/* !!! FIXME: add alloca check here. */
+
+
+/* by default, enable things, so builds can opt out of a few things they
+ want to avoid. But you can build with this #defined to 0 if you would
+ like to turn off everything except a handful of things you opt into. */
+#ifndef PHYSFS_SUPPORTS_DEFAULT
+#define PHYSFS_SUPPORTS_DEFAULT 1
+#endif
+
+
+#ifndef PHYSFS_SUPPORTS_ZIP
+#define PHYSFS_SUPPORTS_ZIP PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_7Z
+#define PHYSFS_SUPPORTS_7Z PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_GRP
+#define PHYSFS_SUPPORTS_GRP PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_HOG
+#define PHYSFS_SUPPORTS_HOG PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_MVL
+#define PHYSFS_SUPPORTS_MVL PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_WAD
+#define PHYSFS_SUPPORTS_WAD PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_QPAK
+#define PHYSFS_SUPPORTS_QPAK PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_SLB
+#define PHYSFS_SUPPORTS_SLB PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_ISO9660
+#define PHYSFS_SUPPORTS_ISO9660 PHYSFS_SUPPORTS_DEFAULT
+#endif
+#ifndef PHYSFS_SUPPORTS_VDF
+#define PHYSFS_SUPPORTS_VDF PHYSFS_SUPPORTS_DEFAULT
+#endif
+
+#if PHYSFS_SUPPORTS_7Z
+/* 7zip support needs a global init function called at startup (no deinit). */
+extern void SZIP_global_init(void);
+#endif
+
+/* The latest supported PHYSFS_Io::version value. */
+#define CURRENT_PHYSFS_IO_API_VERSION 0
+
+/* The latest supported PHYSFS_Archiver::version value. */
+#define CURRENT_PHYSFS_ARCHIVER_API_VERSION 0
+
+
+/* This byteorder stuff was lifted from SDL. https://www.libsdl.org/ */
+#define PHYSFS_LIL_ENDIAN 1234
+#define PHYSFS_BIG_ENDIAN 4321
+
+#ifdef __linux__
+#include
+#define PHYSFS_BYTEORDER __BYTE_ORDER
+#elif defined(__OpenBSD__) || defined(__DragonFly__)
+#include
+#define PHYSFS_BYTEORDER BYTE_ORDER
+#elif defined(__FreeBSD__) || defined(__NetBSD__)
+#include
+#define PHYSFS_BYTEORDER BYTE_ORDER
+/* predefs from newer gcc and clang versions: */
+#elif defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) && defined(__BYTE_ORDER__)
+#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#define PHYSFS_BYTEORDER PHYSFS_LIL_ENDIAN
+#elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define PHYSFS_BYTEORDER PHYSFS_BIG_ENDIAN
+#else
+#error Unsupported endianness
+#endif /**/
+#else
+#if defined(__hppa__) || \
+ defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
+ (defined(__MIPS__) && defined(__MIPSEB__)) || \
+ defined(__ppc__) || defined(__POWERPC__) || defined(__powerpc__) || defined(__PPC__) || \
+ defined(__sparc__)
+#define PHYSFS_BYTEORDER PHYSFS_BIG_ENDIAN
+#else
+#define PHYSFS_BYTEORDER PHYSFS_LIL_ENDIAN
+#endif
+#endif /* __linux__ */
+
+
+/*
+ * When sorting the entries in an archive, we use a modified QuickSort.
+ * When there are less then PHYSFS_QUICKSORT_THRESHOLD entries left to sort,
+ * we switch over to a BubbleSort for the remainder. Tweak to taste.
+ *
+ * You can override this setting by defining PHYSFS_QUICKSORT_THRESHOLD
+ * before #including "physfs_internal.h".
+ */
+#ifndef PHYSFS_QUICKSORT_THRESHOLD
+#define PHYSFS_QUICKSORT_THRESHOLD 4
+#endif
+
+/*
+ * Sort an array (or whatever) of (max) elements. This uses a mixture of
+ * a QuickSort and BubbleSort internally.
+ * (cmpfn) is used to determine ordering, and (swapfn) does the actual
+ * swapping of elements in the list.
+ */
+void __PHYSFS_sort(void *entries, size_t max,
+ int (*cmpfn)(void *, size_t, size_t),
+ void (*swapfn)(void *, size_t, size_t));
+
+/* These get used all over for lessening code clutter. */
+/* "ERRPASS" means "something else just set the error state for us" and is
+ just to make it clear where the responsibility for the error state lays. */
+#define BAIL(e, r) do { if (e) PHYSFS_setErrorCode(e); return r; } while (0)
+#define BAIL_ERRPASS(r) do { return r; } while (0)
+#define BAIL_IF(c, e, r) do { if (c) { if (e) PHYSFS_setErrorCode(e); return r; } } while (0)
+#define BAIL_IF_ERRPASS(c, r) do { if (c) { return r; } } while (0)
+#define BAIL_MUTEX(e, m, r) do { if (e) PHYSFS_setErrorCode(e); __PHYSFS_platformReleaseMutex(m); return r; } while (0)
+#define BAIL_MUTEX_ERRPASS(m, r) do { __PHYSFS_platformReleaseMutex(m); return r; } while (0)
+#define BAIL_IF_MUTEX(c, e, m, r) do { if (c) { if (e) PHYSFS_setErrorCode(e); __PHYSFS_platformReleaseMutex(m); return r; } } while (0)
+#define BAIL_IF_MUTEX_ERRPASS(c, m, r) do { if (c) { __PHYSFS_platformReleaseMutex(m); return r; } } while (0)
+#define GOTO(e, g) do { if (e) PHYSFS_setErrorCode(e); goto g; } while (0)
+#define GOTO_ERRPASS(g) do { goto g; } while (0)
+#define GOTO_IF(c, e, g) do { if (c) { if (e) PHYSFS_setErrorCode(e); goto g; } } while (0)
+#define GOTO_IF_ERRPASS(c, g) do { if (c) { goto g; } } while (0)
+#define GOTO_MUTEX(e, m, g) do { if (e) PHYSFS_setErrorCode(e); __PHYSFS_platformReleaseMutex(m); goto g; } while (0)
+#define GOTO_MUTEX_ERRPASS(m, g) do { __PHYSFS_platformReleaseMutex(m); goto g; } while (0)
+#define GOTO_IF_MUTEX(c, e, m, g) do { if (c) { if (e) PHYSFS_setErrorCode(e); __PHYSFS_platformReleaseMutex(m); goto g; } } while (0)
+#define GOTO_IF_MUTEX_ERRPASS(c, m, g) do { if (c) { __PHYSFS_platformReleaseMutex(m); goto g; } } while (0)
+
+#define __PHYSFS_ARRAYLEN(x) ( (sizeof (x)) / (sizeof (x[0])) )
+
+#ifdef PHYSFS_NO_64BIT_SUPPORT
+#define __PHYSFS_SI64(x) ((PHYSFS_sint64) (x))
+#define __PHYSFS_UI64(x) ((PHYSFS_uint64) (x))
+#elif (defined __GNUC__)
+#define __PHYSFS_SI64(x) x##LL
+#define __PHYSFS_UI64(x) x##ULL
+#elif (defined _MSC_VER)
+#define __PHYSFS_SI64(x) x##i64
+#define __PHYSFS_UI64(x) x##ui64
+#else
+#define __PHYSFS_SI64(x) ((PHYSFS_sint64) (x))
+#define __PHYSFS_UI64(x) ((PHYSFS_uint64) (x))
+#endif
+
+
+/*
+ * Check if a ui64 will fit in the platform's address space.
+ * The initial sizeof check will optimize this macro out entirely on
+ * 64-bit (and larger?!) platforms, and the other condition will
+ * return zero or non-zero if the variable will fit in the platform's
+ * size_t, suitable to pass to malloc. This is kinda messy, but effective.
+ */
+#define __PHYSFS_ui64FitsAddressSpace(s) ( \
+ (sizeof (PHYSFS_uint64) <= sizeof (size_t)) || \
+ ((s) < (__PHYSFS_UI64(0xFFFFFFFFFFFFFFFF) >> (64-(sizeof(size_t)*8)))) \
+)
+
+/*
+ * Like strdup(), but uses the current PhysicsFS allocator.
+ */
+char *__PHYSFS_strdup(const char *str);
+
+/*
+ * Give a hash value for a C string (uses djb's xor hashing algorithm).
+ */
+PHYSFS_uint32 __PHYSFS_hashString(const char *str);
+
+/*
+ * Give a hash value for a C string (uses djb's xor hashing algorithm), case folding as it goes.
+ */
+PHYSFS_uint32 __PHYSFS_hashStringCaseFold(const char *str);
+
+/*
+ * Give a hash value for a C string (uses djb's xor hashing algorithm), case folding as it goes,
+ * assuming that this is only US-ASCII chars (one byte per char, only 'A' through 'Z' need folding).
+ */
+PHYSFS_uint32 __PHYSFS_hashStringCaseFoldUSAscii(const char *str);
+
+
+/*
+ * The current allocator. Not valid before PHYSFS_init is called!
+ */
+extern PHYSFS_Allocator __PHYSFS_AllocatorHooks;
+
+/* convenience macro to make this less cumbersome internally... */
+#define allocator __PHYSFS_AllocatorHooks
+
+/*
+ * Create a PHYSFS_Io for a file in the physical filesystem.
+ * This path is in platform-dependent notation. (mode) must be 'r', 'w', or
+ * 'a' for Read, Write, or Append.
+ */
+PHYSFS_Io *__PHYSFS_createNativeIo(const char *path, const int mode);
+
+/*
+ * Create a PHYSFS_Io for a buffer of memory (READ-ONLY). If you already
+ * have one of these, just use its duplicate() method, and it'll increment
+ * its refcount without allocating a copy of the buffer.
+ */
+PHYSFS_Io *__PHYSFS_createMemoryIo(const void *buf, PHYSFS_uint64 len,
+ void (*destruct)(void *));
+
+
+/*
+ * Read (len) bytes from (io) into (buf). Returns non-zero on success,
+ * zero on i/o error. Literally: "return (io->read(io, buf, len) == len);"
+ */
+int __PHYSFS_readAll(PHYSFS_Io *io, void *buf, const size_t len);
+
+
+/* These are shared between some archivers. */
+
+/* LOTS of legacy formats that only use US ASCII, not actually UTF-8, so let them optimize here. */
+void *UNPK_openArchive(PHYSFS_Io *io, const int case_sensitive, const int only_usascii);
+void UNPK_abandonArchive(void *opaque);
+void UNPK_closeArchive(void *opaque);
+void *UNPK_addEntry(void *opaque, char *name, const int isdir,
+ const PHYSFS_sint64 ctime, const PHYSFS_sint64 mtime,
+ const PHYSFS_uint64 pos, const PHYSFS_uint64 len);
+PHYSFS_Io *UNPK_openRead(void *opaque, const char *name);
+PHYSFS_Io *UNPK_openWrite(void *opaque, const char *name);
+PHYSFS_Io *UNPK_openAppend(void *opaque, const char *name);
+int UNPK_remove(void *opaque, const char *name);
+int UNPK_mkdir(void *opaque, const char *name);
+int UNPK_stat(void *opaque, const char *fn, PHYSFS_Stat *st);
+#define UNPK_enumerate __PHYSFS_DirTreeEnumerate
+
+
+
+/* Optional API many archivers use this to manage their directory tree. */
+/* !!! FIXME: document this better. */
+
+typedef struct __PHYSFS_DirTreeEntry
+{
+ char *name; /* Full path in archive. */
+ struct __PHYSFS_DirTreeEntry *hashnext; /* next item in hash bucket. */
+ struct __PHYSFS_DirTreeEntry *children; /* linked list of kids, if dir. */
+ struct __PHYSFS_DirTreeEntry *sibling; /* next item in same dir. */
+ int isdir;
+} __PHYSFS_DirTreeEntry;
+
+typedef struct __PHYSFS_DirTree
+{
+ __PHYSFS_DirTreeEntry *root; /* root of directory tree. */
+ __PHYSFS_DirTreeEntry **hash; /* all entries hashed for fast lookup. */
+ size_t hashBuckets; /* number of buckets in hash. */
+ size_t entrylen; /* size in bytes of entries (including subclass). */
+ int case_sensitive; /* non-zero to treat entries as case-sensitive in DirTreeFind */
+ int only_usascii; /* non-zero to treat paths as US ASCII only (one byte per char, only 'A' through 'Z' are considered for case folding). */
+} __PHYSFS_DirTree;
+
+
+/* LOTS of legacy formats that only use US ASCII, not actually UTF-8, so let them optimize here. */
+int __PHYSFS_DirTreeInit(__PHYSFS_DirTree *dt, const size_t entrylen, const int case_sensitive, const int only_usascii);
+void *__PHYSFS_DirTreeAdd(__PHYSFS_DirTree *dt, char *name, const int isdir);
+void *__PHYSFS_DirTreeFind(__PHYSFS_DirTree *dt, const char *path);
+PHYSFS_EnumerateCallbackResult __PHYSFS_DirTreeEnumerate(void *opaque,
+ const char *dname, PHYSFS_EnumerateCallback cb,
+ const char *origdir, void *callbackdata);
+void __PHYSFS_DirTreeDeinit(__PHYSFS_DirTree *dt);
+
+
+
+/*--------------------------------------------------------------------------*/
+/*--------------------------------------------------------------------------*/
+/*------------ ----------------*/
+/*------------ You MUST implement the following functions ----------------*/
+/*------------ if porting to a new platform. ----------------*/
+/*------------ (see platform/unix.c for an example) ----------------*/
+/*------------ ----------------*/
+/*--------------------------------------------------------------------------*/
+/*--------------------------------------------------------------------------*/
+
+
+/*
+ * The dir separator; '/' on unix, '\\' on win32, ":" on MacOS, etc...
+ * Obviously, this isn't a function. If you need more than one char for this,
+ * you'll need to pull some old pieces of PhysicsFS out of revision control.
+ */
+#if defined(PHYSFS_PLATFORM_WINDOWS) || defined(PHYSFS_PLATFORM_OS2)
+#define __PHYSFS_platformDirSeparator '\\'
+#else
+#define __PHYSFS_STANDARD_DIRSEP 1
+#define __PHYSFS_platformDirSeparator '/'
+#endif
+
+/*
+ * Initialize the platform. This is called when PHYSFS_init() is called from
+ * the application.
+ *
+ * Return zero if there was a catastrophic failure (which prevents you from
+ * functioning at all), and non-zero otherwise.
+ */
+int __PHYSFS_platformInit(void);
+
+
+/*
+ * Deinitialize the platform. This is called when PHYSFS_deinit() is called
+ * from the application. You can use this to clean up anything you've
+ * allocated in your platform driver.
+ */
+void __PHYSFS_platformDeinit(void);
+
+
+/*
+ * Open a file for reading. (filename) is in platform-dependent notation. The
+ * file pointer should be positioned on the first byte of the file.
+ *
+ * The return value will be some platform-specific datatype that is opaque to
+ * the caller; it could be a (FILE *) under Unix, or a (HANDLE *) under win32.
+ *
+ * The same file can be opened for read multiple times, and each should have
+ * a unique file handle; this is frequently employed to prevent race
+ * conditions in the archivers.
+ *
+ * Call PHYSFS_setErrorCode() and return (NULL) if the file can't be opened.
+ */
+void *__PHYSFS_platformOpenRead(const char *filename);
+
+
+/*
+ * Open a file for writing. (filename) is in platform-dependent notation. If
+ * the file exists, it should be truncated to zero bytes, and if it doesn't
+ * exist, it should be created as a zero-byte file. The file pointer should
+ * be positioned on the first byte of the file.
+ *
+ * The return value will be some platform-specific datatype that is opaque to
+ * the caller; it could be a (FILE *) under Unix, or a (HANDLE *) under win32,
+ * etc.
+ *
+ * Opening a file for write multiple times has undefined results.
+ *
+ * Call PHYSFS_setErrorCode() and return (NULL) if the file can't be opened.
+ */
+void *__PHYSFS_platformOpenWrite(const char *filename);
+
+
+/*
+ * Open a file for appending. (filename) is in platform-dependent notation. If
+ * the file exists, the file pointer should be place just past the end of the
+ * file, so that the first write will be one byte after the current end of
+ * the file. If the file doesn't exist, it should be created as a zero-byte
+ * file. The file pointer should be positioned on the first byte of the file.
+ *
+ * The return value will be some platform-specific datatype that is opaque to
+ * the caller; it could be a (FILE *) under Unix, or a (HANDLE *) under win32,
+ * etc.
+ *
+ * Opening a file for append multiple times has undefined results.
+ *
+ * Call PHYSFS_setErrorCode() and return (NULL) if the file can't be opened.
+ */
+void *__PHYSFS_platformOpenAppend(const char *filename);
+
+/*
+ * Read more data from a platform-specific file handle. (opaque) should be
+ * cast to whatever data type your platform uses. Read a maximum of (len)
+ * 8-bit bytes to the area pointed to by (buf). If there isn't enough data
+ * available, return the number of bytes read, and position the file pointer
+ * immediately after those bytes.
+ * On success, return (len) and position the file pointer immediately past
+ * the end of the last read byte. Return (-1) if there is a catastrophic
+ * error, and call PHYSFS_setErrorCode() to describe the problem; the file
+ * pointer should not move in such a case. A partial read is success; only
+ * return (-1) on total failure; presumably, the next read call after a
+ * partial read will fail as such.
+ */
+PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buf, PHYSFS_uint64 len);
+
+/*
+ * Write more data to a platform-specific file handle. (opaque) should be
+ * cast to whatever data type your platform uses. Write a maximum of (len)
+ * 8-bit bytes from the area pointed to by (buffer). If there is a problem,
+ * return the number of bytes written, and position the file pointer
+ * immediately after those bytes. Return (-1) if there is a catastrophic
+ * error, and call PHYSFS_setErrorCode() to describe the problem; the file
+ * pointer should not move in such a case. A partial write is success; only
+ * return (-1) on total failure; presumably, the next write call after a
+ * partial write will fail as such.
+ */
+PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
+ PHYSFS_uint64 len);
+
+/*
+ * Set the file pointer to a new position. (opaque) should be cast to
+ * whatever data type your platform uses. (pos) specifies the number
+ * of 8-bit bytes to seek to from the start of the file. Seeking past the
+ * end of the file is an error condition, and you should check for it.
+ *
+ * Not all file types can seek; this is to be expected by the caller.
+ *
+ * On error, call PHYSFS_setErrorCode() and return zero. On success, return
+ * a non-zero value.
+ */
+int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos);
+
+
+/*
+ * Get the file pointer's position, in an 8-bit byte offset from the start of
+ * the file. (opaque) should be cast to whatever data type your platform
+ * uses.
+ *
+ * Not all file types can "tell"; this is to be expected by the caller.
+ *
+ * On error, call PHYSFS_setErrorCode() and return -1. On success, return >= 0.
+ */
+PHYSFS_sint64 __PHYSFS_platformTell(void *opaque);
+
+
+/*
+ * Determine the current size of a file, in 8-bit bytes, from an open file.
+ *
+ * The caller expects that this information may not be available for all
+ * file types on all platforms.
+ *
+ * Return -1 if you can't do it, and call PHYSFS_setErrorCode(). Otherwise,
+ * return the file length in 8-bit bytes.
+ */
+PHYSFS_sint64 __PHYSFS_platformFileLength(void *handle);
+
+
+/*
+ * Read filesystem metadata for a specific path.
+ *
+ * This needs to fill in all the fields of (stat). For fields that might not
+ * mean anything on a platform (access time, perhaps), choose a reasonable
+ * default. if (follow), we want to follow symlinks and stat what they
+ * link to and not the link itself.
+ *
+ * Return zero on failure, non-zero on success.
+ */
+int __PHYSFS_platformStat(const char *fn, PHYSFS_Stat *stat, const int follow);
+
+/*
+ * Flush any pending writes to disk. (opaque) should be cast to whatever data
+ * type your platform uses. Be sure to check for errors; the caller expects
+ * that this function can fail if there was a flushing error, etc.
+ *
+ * Return zero on failure, non-zero on success.
+ */
+int __PHYSFS_platformFlush(void *opaque);
+
+/*
+ * Close file and deallocate resources. (opaque) should be cast to whatever
+ * data type your platform uses. This should close the file in any scenario:
+ * flushing is a separate function call, and this function should never fail.
+ *
+ * You should clean up all resources associated with (opaque); the pointer
+ * will be considered invalid after this call.
+ */
+void __PHYSFS_platformClose(void *opaque);
+
+/*
+ * Platform implementation of PHYSFS_getCdRomDirsCallback()...
+ * CD directories are discovered and reported to the callback one at a time.
+ * Pointers passed to the callback are assumed to be invalid to the
+ * application after the callback returns, so you can free them or whatever.
+ * Callback does not assume results will be sorted in any meaningful way.
+ */
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data);
+
+/*
+ * Calculate the base dir, if your platform needs special consideration.
+ * Just return NULL if the standard routines will suffice. (see
+ * calculateBaseDir() in physfs.c ...)
+ * Your string must end with a dir separator if you don't return NULL.
+ * Caller will allocator.Free() the retval if it's not NULL.
+ */
+char *__PHYSFS_platformCalcBaseDir(const char *argv0);
+
+/*
+ * Get the platform-specific user dir.
+ * As of PhysicsFS 2.1, returning NULL means fatal error.
+ * Your string must end with a dir separator if you don't return NULL.
+ * Caller will allocator.Free() the retval if it's not NULL.
+ */
+char *__PHYSFS_platformCalcUserDir(void);
+
+
+/* This is the cached version from PHYSFS_init(). This is a fast call. */
+const char *__PHYSFS_getUserDir(void); /* not deprecated internal version. */
+
+
+/*
+ * Get the platform-specific pref dir.
+ * Returning NULL means fatal error.
+ * Your string must end with a dir separator if you don't return NULL.
+ * Caller will allocator.Free() the retval if it's not NULL.
+ * Caller will make missing directories if necessary; this just reports
+ * the final path.
+ */
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app);
+
+
+/*
+ * Return a pointer that uniquely identifies the current thread.
+ * On a platform without threading, (0x1) will suffice. These numbers are
+ * arbitrary; the only requirement is that no two threads have the same
+ * pointer.
+ */
+void *__PHYSFS_platformGetThreadID(void);
+
+
+/*
+ * Enumerate a directory of files. This follows the rules for the
+ * PHYSFS_Archiver::enumerate() method, except that the (dirName) that is
+ * passed to this function is converted to platform-DEPENDENT notation by
+ * the caller. The PHYSFS_Archiver version uses platform-independent
+ * notation. Note that ".", "..", and other meta-entries should always
+ * be ignored.
+ */
+PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
+ PHYSFS_EnumerateCallback callback,
+ const char *origdir, void *callbackdata);
+
+/*
+ * Make a directory in the actual filesystem. (path) is specified in
+ * platform-dependent notation. On error, return zero and set the error
+ * message. Return non-zero on success.
+ */
+int __PHYSFS_platformMkDir(const char *path);
+
+
+/*
+ * Remove a file or directory entry in the actual filesystem. (path) is
+ * specified in platform-dependent notation. Note that this deletes files
+ * _and_ directories, so you might need to do some determination.
+ * Non-empty directories should report an error and not delete themselves
+ * or their contents.
+ *
+ * Deleting a symlink should remove the link, not what it points to.
+ *
+ * On error, return zero and set the error message. Return non-zero on success.
+ */
+int __PHYSFS_platformDelete(const char *path);
+
+
+/*
+ * Create a platform-specific mutex. This can be whatever datatype your
+ * platform uses for mutexes, but it is cast to a (void *) for abstractness.
+ *
+ * Return (NULL) if you couldn't create one. Systems without threads can
+ * return any arbitrary non-NULL value.
+ */
+void *__PHYSFS_platformCreateMutex(void);
+
+/*
+ * Destroy a platform-specific mutex, and clean up any resources associated
+ * with it. (mutex) is a value previously returned by
+ * __PHYSFS_platformCreateMutex(). This can be a no-op on single-threaded
+ * platforms.
+ */
+void __PHYSFS_platformDestroyMutex(void *mutex);
+
+/*
+ * Grab possession of a platform-specific mutex. Mutexes should be recursive;
+ * that is, the same thread should be able to call this function multiple
+ * times in a row without causing a deadlock. This function should block
+ * until a thread can gain possession of the mutex.
+ *
+ * Return non-zero if the mutex was grabbed, zero if there was an
+ * unrecoverable problem grabbing it (this should not be a matter of
+ * timing out! We're talking major system errors; block until the mutex
+ * is available otherwise.)
+ *
+ * _DO NOT_ call PHYSFS_setErrorCode() in here! Since setErrorCode calls this
+ * function, you'll cause an infinite recursion. This means you can't
+ * use the BAIL_*MACRO* macros, either.
+ */
+int __PHYSFS_platformGrabMutex(void *mutex);
+
+/*
+ * Relinquish possession of the mutex when this method has been called
+ * once for each time that platformGrabMutex was called. Once possession has
+ * been released, the next thread in line to grab the mutex (if any) may
+ * proceed.
+ *
+ * _DO NOT_ call PHYSFS_setErrorCode() in here! Since setErrorCode calls this
+ * function, you'll cause an infinite recursion. This means you can't
+ * use the BAIL_*MACRO* macros, either.
+ */
+void __PHYSFS_platformReleaseMutex(void *mutex);
+
+
+/* !!! FIXME: move to public API? */
+PHYSFS_uint32 __PHYSFS_utf8codepoint(const char **_str);
+
+
+#if PHYSFS_HAVE_PRAGMA_VISIBILITY
+#pragma GCC visibility pop
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+/* end of physfs_internal.h ... */
+
diff --git a/third-party/physfs/src/physfs_lzmasdk.h b/third-party/physfs/src/physfs_lzmasdk.h
new file mode 100644
index 0000000..d2bbcc6
--- /dev/null
+++ b/third-party/physfs/src/physfs_lzmasdk.h
@@ -0,0 +1,6031 @@
+#ifndef _INCLUDE_PHYSFS_LZMASDK_H_
+#define _INCLUDE_PHYSFS_LZMASDK_H_
+
+/* This is just a bunch of the LZMA SDK mushed together into one header.
+This code is all public domain, and mostly (if not entirely) written by
+Igor Pavlov. http://www.7-zip.org/sdk.html
+--ryan. */
+
+
+
+/* 7zTypes.h -- Basic types
+2013-11-12 : Igor Pavlov : Public domain */
+
+#ifndef __7Z_TYPES_H
+#define __7Z_TYPES_H
+
+#ifdef _WIN32
+/* #include */
+#endif
+
+#include
+
+#ifndef EXTERN_C_BEGIN
+#ifdef __cplusplus
+#define EXTERN_C_BEGIN extern "C" {
+#define EXTERN_C_END }
+#else
+#define EXTERN_C_BEGIN
+#define EXTERN_C_END
+#endif
+#endif
+
+EXTERN_C_BEGIN
+
+#define SZ_OK 0
+
+#define SZ_ERROR_DATA 1
+#define SZ_ERROR_MEM 2
+#define SZ_ERROR_CRC 3
+#define SZ_ERROR_UNSUPPORTED 4
+#define SZ_ERROR_PARAM 5
+#define SZ_ERROR_INPUT_EOF 6
+#define SZ_ERROR_OUTPUT_EOF 7
+#define SZ_ERROR_READ 8
+#define SZ_ERROR_WRITE 9
+#define SZ_ERROR_PROGRESS 10
+#define SZ_ERROR_FAIL 11
+#define SZ_ERROR_THREAD 12
+
+#define SZ_ERROR_ARCHIVE 16
+#define SZ_ERROR_NO_ARCHIVE 17
+
+typedef int SRes;
+
+#ifdef _WIN32
+/* typedef DWORD WRes; */
+typedef unsigned WRes;
+#else
+typedef int WRes;
+#endif
+
+#ifndef RINOK
+#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; }
+#endif
+
+typedef unsigned char Byte;
+typedef short Int16;
+typedef unsigned short UInt16;
+
+#ifdef _LZMA_UINT32_IS_ULONG
+typedef long Int32;
+typedef unsigned long UInt32;
+#else
+typedef int Int32;
+typedef unsigned int UInt32;
+#endif
+
+#ifdef _SZ_NO_INT_64
+
+/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers.
+ NOTES: Some code will work incorrectly in that case! */
+
+typedef long Int64;
+typedef unsigned long UInt64;
+
+#else
+
+#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)
+typedef __int64 Int64;
+typedef unsigned __int64 UInt64;
+#define UINT64_CONST(n) n ## ui64
+#else
+typedef long long int Int64;
+typedef unsigned long long int UInt64;
+#define UINT64_CONST(n) n ## ULL
+#endif
+
+#endif
+
+#ifdef _LZMA_NO_SYSTEM_SIZE_T
+typedef UInt32 SizeT;
+#else
+typedef size_t SizeT;
+#endif
+
+typedef int Bool;
+#define True 1
+#define False 0
+
+
+#ifdef _WIN32
+#define MY_STD_CALL __stdcall
+#else
+#define MY_STD_CALL
+#endif
+
+#ifdef _MSC_VER
+
+#if _MSC_VER >= 1300
+#define MY_NO_INLINE __declspec(noinline)
+#else
+#define MY_NO_INLINE
+#endif
+
+#define MY_CDECL __cdecl
+#define MY_FAST_CALL __fastcall
+
+#else
+
+#define MY_NO_INLINE
+#define MY_CDECL
+#define MY_FAST_CALL
+
+#endif
+
+
+/* The following interfaces use first parameter as pointer to structure */
+
+typedef struct
+{
+ Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */
+} IByteIn;
+
+typedef struct
+{
+ void (*Write)(void *p, Byte b);
+} IByteOut;
+
+typedef struct
+{
+ SRes (*Read)(void *p, void *buf, size_t *size);
+ /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
+ (output(*size) < input(*size)) is allowed */
+} ISeqInStream;
+
+typedef struct
+{
+ size_t (*Write)(void *p, const void *buf, size_t size);
+ /* Returns: result - the number of actually written bytes.
+ (result < size) means error */
+} ISeqOutStream;
+
+typedef enum
+{
+ SZ_SEEK_SET = 0,
+ SZ_SEEK_CUR = 1,
+ SZ_SEEK_END = 2
+} ESzSeek;
+
+typedef struct
+{
+ SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */
+ SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
+} ISeekInStream;
+
+typedef struct
+{
+ SRes (*Look)(void *p, const void **buf, size_t *size);
+ /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
+ (output(*size) > input(*size)) is not allowed
+ (output(*size) < input(*size)) is allowed */
+ SRes (*Skip)(void *p, size_t offset);
+ /* offset must be <= output(*size) of Look */
+
+ SRes (*Read)(void *p, void *buf, size_t *size);
+ /* reads directly (without buffer). It's same as ISeqInStream::Read */
+ SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin);
+} ILookInStream;
+
+static SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset);
+
+/* reads via ILookInStream::Read */
+static SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType);
+static SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size);
+
+#define LookToRead_BUF_SIZE (1 << 14)
+
+typedef struct
+{
+ ILookInStream s;
+ ISeekInStream *realStream;
+ size_t pos;
+ size_t size;
+ Byte buf[LookToRead_BUF_SIZE];
+} CLookToRead;
+
+static void LookToRead_CreateVTable(CLookToRead *p, int lookahead);
+static void LookToRead_Init(CLookToRead *p);
+
+typedef struct
+{
+ ISeqInStream s;
+ ILookInStream *realStream;
+} CSecToLook;
+
+typedef struct
+{
+ ISeqInStream s;
+ ILookInStream *realStream;
+} CSecToRead;
+
+typedef struct
+{
+ SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize);
+ /* Returns: result. (result != SZ_OK) means break.
+ Value (UInt64)(Int64)-1 for size means unknown value. */
+} ICompressProgress;
+
+typedef struct
+{
+ void *(*Alloc)(void *p, size_t size);
+ void (*Free)(void *p, void *address); /* address can be 0 */
+} ISzAlloc;
+
+#define IAlloc_Alloc(p, size) (p)->Alloc((p), size)
+#define IAlloc_Free(p, a) (p)->Free((p), a)
+
+#ifdef _WIN32
+
+#define CHAR_PATH_SEPARATOR '\\'
+#define WCHAR_PATH_SEPARATOR L'\\'
+#define STRING_PATH_SEPARATOR "\\"
+#define WSTRING_PATH_SEPARATOR L"\\"
+
+#else
+
+#define CHAR_PATH_SEPARATOR '/'
+#define WCHAR_PATH_SEPARATOR L'/'
+#define STRING_PATH_SEPARATOR "/"
+#define WSTRING_PATH_SEPARATOR L"/"
+
+#endif
+
+EXTERN_C_END
+
+#endif
+
+/* 7z.h -- 7z interface
+2015-11-18 : Igor Pavlov : Public domain */
+
+#ifndef __7Z_H
+#define __7Z_H
+
+/*#include "7zTypes.h"*/
+
+EXTERN_C_BEGIN
+
+#define k7zStartHeaderSize 0x20
+#define k7zSignatureSize 6
+
+static const Byte k7zSignature[k7zSignatureSize];
+
+typedef struct
+{
+ const Byte *Data;
+ size_t Size;
+} CSzData;
+
+/* CSzCoderInfo & CSzFolder support only default methods */
+
+typedef struct
+{
+ size_t PropsOffset;
+ UInt32 MethodID;
+ Byte NumStreams;
+ Byte PropsSize;
+} CSzCoderInfo;
+
+typedef struct
+{
+ UInt32 InIndex;
+ UInt32 OutIndex;
+} CSzBond;
+
+#define SZ_NUM_CODERS_IN_FOLDER_MAX 4
+#define SZ_NUM_BONDS_IN_FOLDER_MAX 3
+#define SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX 4
+
+typedef struct
+{
+ UInt32 NumCoders;
+ UInt32 NumBonds;
+ UInt32 NumPackStreams;
+ UInt32 UnpackStream;
+ UInt32 PackStreams[SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX];
+ CSzBond Bonds[SZ_NUM_BONDS_IN_FOLDER_MAX];
+ CSzCoderInfo Coders[SZ_NUM_CODERS_IN_FOLDER_MAX];
+} CSzFolder;
+
+
+static SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd);
+
+typedef struct
+{
+ UInt32 Low;
+ UInt32 High;
+} CNtfsFileTime;
+
+typedef struct
+{
+ Byte *Defs; /* MSB 0 bit numbering */
+ UInt32 *Vals;
+} CSzBitUi32s;
+
+typedef struct
+{
+ Byte *Defs; /* MSB 0 bit numbering */
+ /* UInt64 *Vals; */
+ CNtfsFileTime *Vals;
+} CSzBitUi64s;
+
+#define SzBitArray_Check(p, i) (((p)[(i) >> 3] & (0x80 >> ((i) & 7))) != 0)
+
+#define SzBitWithVals_Check(p, i) ((p)->Defs && ((p)->Defs[(i) >> 3] & (0x80 >> ((i) & 7))) != 0)
+
+typedef struct
+{
+ UInt32 NumPackStreams;
+ UInt32 NumFolders;
+
+ UInt64 *PackPositions; /* NumPackStreams + 1 */
+ CSzBitUi32s FolderCRCs; /* NumFolders */
+
+ size_t *FoCodersOffsets; /* NumFolders + 1 */
+ UInt32 *FoStartPackStreamIndex; /* NumFolders + 1 */
+ UInt32 *FoToCoderUnpackSizes; /* NumFolders + 1 */
+ Byte *FoToMainUnpackSizeIndex; /* NumFolders */
+ UInt64 *CoderUnpackSizes; /* for all coders in all folders */
+
+ Byte *CodersData;
+} CSzAr;
+
+static UInt64 SzAr_GetFolderUnpackSize(const CSzAr *p, UInt32 folderIndex);
+
+static SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex,
+ ILookInStream *stream, UInt64 startPos,
+ Byte *outBuffer, size_t outSize,
+ ISzAlloc *allocMain);
+
+typedef struct
+{
+ CSzAr db;
+
+ UInt64 startPosAfterHeader;
+ UInt64 dataPos;
+
+ UInt32 NumFiles;
+
+ UInt64 *UnpackPositions; /* NumFiles + 1 */
+ /* Byte *IsEmptyFiles; */
+ Byte *IsDirs;
+ CSzBitUi32s CRCs;
+
+ CSzBitUi32s Attribs;
+ /* CSzBitUi32s Parents; */
+ CSzBitUi64s MTime;
+ CSzBitUi64s CTime;
+
+ UInt32 *FolderToFile; /* NumFolders + 1 */
+ UInt32 *FileToFolder; /* NumFiles */
+
+ size_t *FileNameOffsets; /* in 2-byte steps */
+ Byte *FileNames; /* UTF-16-LE */
+} CSzArEx;
+
+#define SzArEx_IsDir(p, i) (SzBitArray_Check((p)->IsDirs, i))
+
+#define SzArEx_GetFileSize(p, i) ((p)->UnpackPositions[(i) + 1] - (p)->UnpackPositions[i])
+
+static void SzArEx_Init(CSzArEx *p);
+static void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc);
+
+/*
+if dest == NULL, the return value specifies the required size of the buffer,
+ in 16-bit characters, including the null-terminating character.
+if dest != NULL, the return value specifies the number of 16-bit characters that
+ are written to the dest, including the null-terminating character. */
+
+static size_t SzArEx_GetFileNameUtf16(const CSzArEx *p, size_t fileIndex, UInt16 *dest);
+
+/*
+size_t SzArEx_GetFullNameLen(const CSzArEx *p, size_t fileIndex);
+UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 *dest);
+*/
+
+
+
+/*
+ SzArEx_Extract extracts file from archive
+
+ *outBuffer must be 0 before first call for each new archive.
+
+ Extracting cache:
+ If you need to decompress more than one file, you can send
+ these values from previous call:
+ *blockIndex,
+ *outBuffer,
+ *outBufferSize
+ You can consider "*outBuffer" as cache of solid block. If your archive is solid,
+ it will increase decompression speed.
+
+ If you use external function, you can declare these 3 cache variables
+ (blockIndex, outBuffer, outBufferSize) as static in that external function.
+
+ Free *outBuffer and set *outBuffer to 0, if you want to flush cache.
+*/
+
+static SRes SzArEx_Extract(
+ const CSzArEx *db,
+ ILookInStream *inStream,
+ UInt32 fileIndex, /* index of file */
+ UInt32 *blockIndex, /* index of solid block */
+ Byte **outBuffer, /* pointer to pointer to output buffer (allocated with allocMain) */
+ size_t *outBufferSize, /* buffer size for output buffer */
+ size_t *offset, /* offset of stream for required file in *outBuffer */
+ size_t *outSizeProcessed, /* size of file in *outBuffer */
+ ISzAlloc *allocMain,
+ ISzAlloc *allocTemp);
+
+
+/*
+SzArEx_Open Errors:
+SZ_ERROR_NO_ARCHIVE
+SZ_ERROR_ARCHIVE
+SZ_ERROR_UNSUPPORTED
+SZ_ERROR_MEM
+SZ_ERROR_CRC
+SZ_ERROR_INPUT_EOF
+SZ_ERROR_FAIL
+*/
+
+static SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream,
+ ISzAlloc *allocMain, ISzAlloc *allocTemp);
+
+EXTERN_C_END
+
+#endif
+
+/* 7zCrc.h -- CRC32 calculation
+2013-01-18 : Igor Pavlov : Public domain */
+
+#ifndef __7Z_CRC_H
+#define __7Z_CRC_H
+
+/*#include "7zTypes.h" */
+
+EXTERN_C_BEGIN
+
+/* Call CrcGenerateTable one time before other CRC functions */
+static void MY_FAST_CALL CrcGenerateTable(void);
+
+#define CRC_INIT_VAL 0xFFFFFFFF
+#define CRC_GET_DIGEST(crc) ((crc) ^ CRC_INIT_VAL)
+#define CRC_UPDATE_BYTE(crc, b) (g_CrcTable[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
+
+static UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size);
+
+EXTERN_C_END
+
+#endif
+
+/* CpuArch.h -- CPU specific code
+2016-06-09: Igor Pavlov : Public domain */
+
+#ifndef __CPU_ARCH_H
+#define __CPU_ARCH_H
+
+/*#include "7zTypes.h"*/
+
+EXTERN_C_BEGIN
+
+/*
+MY_CPU_LE means that CPU is LITTLE ENDIAN.
+MY_CPU_BE means that CPU is BIG ENDIAN.
+If MY_CPU_LE and MY_CPU_BE are not defined, we don't know about ENDIANNESS of platform.
+
+MY_CPU_LE_UNALIGN means that CPU is LITTLE ENDIAN and CPU supports unaligned memory accesses.
+*/
+
+#if defined(_M_X64) \
+ || defined(_M_AMD64) \
+ || defined(__x86_64__) \
+ || defined(__AMD64__) \
+ || defined(__amd64__)
+ #define MY_CPU_AMD64
+#endif
+
+#if defined(MY_CPU_AMD64) \
+ || defined(_M_ARM64) \
+ || defined(_M_IA64) \
+ || defined(__AARCH64EL__) \
+ || defined(__AARCH64EB__)
+ #define MY_CPU_64BIT
+#endif
+
+#if defined(_M_IX86) || defined(__i386__)
+#define MY_CPU_X86
+#endif
+
+#if defined(MY_CPU_X86) || defined(MY_CPU_AMD64)
+#define MY_CPU_X86_OR_AMD64
+#endif
+
+#if defined(MY_CPU_X86) \
+ || defined(_M_ARM) \
+ || defined(__ARMEL__) \
+ || defined(__THUMBEL__) \
+ || defined(__ARMEB__) \
+ || defined(__THUMBEB__)
+ #define MY_CPU_32BIT
+#endif
+
+#if defined(_WIN32) && defined(_M_ARM)
+#define MY_CPU_ARM_LE
+#elif defined(_WIN64) && defined(_M_ARM64)
+#define MY_CPU_ARM_LE
+#endif
+
+#if defined(_WIN32) && defined(_M_IA64)
+#define MY_CPU_IA64_LE
+#endif
+
+#if defined(MY_CPU_X86_OR_AMD64) \
+ || defined(MY_CPU_ARM_LE) \
+ || defined(MY_CPU_IA64_LE) \
+ || defined(__LITTLE_ENDIAN__) \
+ || defined(__ARMEL__) \
+ || defined(__THUMBEL__) \
+ || defined(__AARCH64EL__) \
+ || defined(__MIPSEL__) \
+ || defined(__MIPSEL) \
+ || defined(_MIPSEL) \
+ || defined(__BFIN__) \
+ || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
+ #define MY_CPU_LE
+#endif
+
+#if defined(__BIG_ENDIAN__) \
+ || defined(__ARMEB__) \
+ || defined(__THUMBEB__) \
+ || defined(__AARCH64EB__) \
+ || defined(__MIPSEB__) \
+ || defined(__MIPSEB) \
+ || defined(_MIPSEB) \
+ || defined(__m68k__) \
+ || defined(__s390__) \
+ || defined(__s390x__) \
+ || defined(__zarch__) \
+ || (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
+ #define MY_CPU_BE
+#endif
+
+#if defined(MY_CPU_LE) && defined(MY_CPU_BE)
+Stop_Compiling_Bad_Endian
+#endif
+
+
+#ifdef MY_CPU_LE
+ #if defined(MY_CPU_X86_OR_AMD64) \
+ /* || defined(__AARCH64EL__) */
+ /*#define MY_CPU_LE_UNALIGN*/
+ #endif
+#endif
+
+
+#ifdef MY_CPU_LE_UNALIGN
+
+#define GetUi16(p) (*(const UInt16 *)(const void *)(p))
+#define GetUi32(p) (*(const UInt32 *)(const void *)(p))
+#define GetUi64(p) (*(const UInt64 *)(const void *)(p))
+
+#define SetUi16(p, v) { *(UInt16 *)(p) = (v); }
+#define SetUi32(p, v) { *(UInt32 *)(p) = (v); }
+#define SetUi64(p, v) { *(UInt64 *)(p) = (v); }
+
+#else
+
+#define GetUi16(p) ( (UInt16) ( \
+ ((const Byte *)(p))[0] | \
+ ((UInt16)((const Byte *)(p))[1] << 8) ))
+
+#define GetUi32(p) ( \
+ ((const Byte *)(p))[0] | \
+ ((UInt32)((const Byte *)(p))[1] << 8) | \
+ ((UInt32)((const Byte *)(p))[2] << 16) | \
+ ((UInt32)((const Byte *)(p))[3] << 24))
+
+#define GetUi64(p) (GetUi32(p) | ((UInt64)GetUi32(((const Byte *)(p)) + 4) << 32))
+
+#define SetUi16(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \
+ _ppp_[0] = (Byte)_vvv_; \
+ _ppp_[1] = (Byte)(_vvv_ >> 8); }
+
+#define SetUi32(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \
+ _ppp_[0] = (Byte)_vvv_; \
+ _ppp_[1] = (Byte)(_vvv_ >> 8); \
+ _ppp_[2] = (Byte)(_vvv_ >> 16); \
+ _ppp_[3] = (Byte)(_vvv_ >> 24); }
+
+#define SetUi64(p, v) { Byte *_ppp2_ = (Byte *)(p); UInt64 _vvv2_ = (v); \
+ SetUi32(_ppp2_ , (UInt32)_vvv2_); \
+ SetUi32(_ppp2_ + 4, (UInt32)(_vvv2_ >> 32)); }
+
+#endif
+
+
+#if defined(MY_CPU_LE_UNALIGN) && /* defined(_WIN64) && */ (_MSC_VER >= 1300)
+
+/* Note: we use bswap instruction, that is unsupported in 386 cpu */
+
+#include
+
+#pragma intrinsic(_byteswap_ulong)
+#pragma intrinsic(_byteswap_uint64)
+#define GetBe32(p) _byteswap_ulong(*(const UInt32 *)(const Byte *)(p))
+#define GetBe64(p) _byteswap_uint64(*(const UInt64 *)(const Byte *)(p))
+
+#define SetBe32(p, v) (*(UInt32 *)(void *)(p)) = _byteswap_ulong(v)
+
+#elif defined(MY_CPU_LE_UNALIGN) && defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
+
+#define GetBe32(p) __builtin_bswap32(*(const UInt32 *)(const Byte *)(p))
+#define GetBe64(p) __builtin_bswap64(*(const UInt64 *)(const Byte *)(p))
+
+#define SetBe32(p, v) (*(UInt32 *)(void *)(p)) = __builtin_bswap32(v)
+
+#else
+
+#define GetBe32(p) ( \
+ ((UInt32)((const Byte *)(p))[0] << 24) | \
+ ((UInt32)((const Byte *)(p))[1] << 16) | \
+ ((UInt32)((const Byte *)(p))[2] << 8) | \
+ ((const Byte *)(p))[3] )
+
+#define GetBe64(p) (((UInt64)GetBe32(p) << 32) | GetBe32(((const Byte *)(p)) + 4))
+
+#define SetBe32(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \
+ _ppp_[0] = (Byte)(_vvv_ >> 24); \
+ _ppp_[1] = (Byte)(_vvv_ >> 16); \
+ _ppp_[2] = (Byte)(_vvv_ >> 8); \
+ _ppp_[3] = (Byte)_vvv_; }
+
+#endif
+
+
+#define GetBe16(p) ( (UInt16) ( \
+ ((UInt16)((const Byte *)(p))[0] << 8) | \
+ ((const Byte *)(p))[1] ))
+
+
+
+#ifdef MY_CPU_X86_OR_AMD64
+
+typedef struct
+{
+ UInt32 maxFunc;
+ UInt32 vendor[3];
+ UInt32 ver;
+ UInt32 b;
+ UInt32 c;
+ UInt32 d;
+} Cx86cpuid;
+
+enum
+{
+ CPU_FIRM_INTEL,
+ CPU_FIRM_AMD,
+ CPU_FIRM_VIA
+};
+
+static void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d);
+
+static Bool x86cpuid_CheckAndRead(Cx86cpuid *p);
+static int x86cpuid_GetFirm(const Cx86cpuid *p);
+
+#define x86cpuid_GetFamily(ver) (((ver >> 16) & 0xFF0) | ((ver >> 8) & 0xF))
+#define x86cpuid_GetModel(ver) (((ver >> 12) & 0xF0) | ((ver >> 4) & 0xF))
+#define x86cpuid_GetStepping(ver) (ver & 0xF)
+
+static Bool CPU_Is_InOrder();
+
+#endif
+
+EXTERN_C_END
+
+#endif
+
+/* 7zBuf.h -- Byte Buffer
+2013-01-18 : Igor Pavlov : Public domain */
+
+#ifndef __7Z_BUF_H
+#define __7Z_BUF_H
+
+/*#include "7zTypes.h" */
+
+EXTERN_C_BEGIN
+
+typedef struct
+{
+ Byte *data;
+ size_t size;
+} CBuf;
+
+static void Buf_Init(CBuf *p);
+static int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc);
+static void Buf_Free(CBuf *p, ISzAlloc *alloc);
+
+EXTERN_C_END
+
+#endif
+
+
+/* Bcj2.h -- BCJ2 Converter for x86 code
+2014-11-10 : Igor Pavlov : Public domain */
+
+#ifndef __BCJ2_H
+#define __BCJ2_H
+
+/*#include "7zTypes.h" */
+
+EXTERN_C_BEGIN
+
+#define BCJ2_NUM_STREAMS 4
+
+enum
+{
+ BCJ2_STREAM_MAIN,
+ BCJ2_STREAM_CALL,
+ BCJ2_STREAM_JUMP,
+ BCJ2_STREAM_RC
+};
+
+enum
+{
+ BCJ2_DEC_STATE_ORIG_0 = BCJ2_NUM_STREAMS,
+ BCJ2_DEC_STATE_ORIG_1,
+ BCJ2_DEC_STATE_ORIG_2,
+ BCJ2_DEC_STATE_ORIG_3,
+
+ BCJ2_DEC_STATE_ORIG,
+ BCJ2_DEC_STATE_OK
+};
+
+enum
+{
+ BCJ2_ENC_STATE_ORIG = BCJ2_NUM_STREAMS,
+ BCJ2_ENC_STATE_OK
+};
+
+
+#define BCJ2_IS_32BIT_STREAM(s) ((s) == BCJ2_STREAM_CALL || (s) == BCJ2_STREAM_JUMP)
+
+/*
+CBcj2Dec / CBcj2Enc
+bufs sizes:
+ BUF_SIZE(n) = lims[n] - bufs[n]
+bufs sizes for BCJ2_STREAM_CALL and BCJ2_STREAM_JUMP must be mutliply of 4:
+ (BUF_SIZE(BCJ2_STREAM_CALL) & 3) == 0
+ (BUF_SIZE(BCJ2_STREAM_JUMP) & 3) == 0
+*/
+
+/*
+CBcj2Dec:
+dest is allowed to overlap with bufs[BCJ2_STREAM_MAIN], with the following conditions:
+ bufs[BCJ2_STREAM_MAIN] >= dest &&
+ bufs[BCJ2_STREAM_MAIN] - dest >= tempReserv +
+ BUF_SIZE(BCJ2_STREAM_CALL) +
+ BUF_SIZE(BCJ2_STREAM_JUMP)
+ tempReserv = 0 : for first call of Bcj2Dec_Decode
+ tempReserv = 4 : for any other calls of Bcj2Dec_Decode
+ overlap with offset = 1 is not allowed
+*/
+
+typedef struct
+{
+ const Byte *bufs[BCJ2_NUM_STREAMS];
+ const Byte *lims[BCJ2_NUM_STREAMS];
+ Byte *dest;
+ const Byte *destLim;
+
+ unsigned state; /* BCJ2_STREAM_MAIN has more priority than BCJ2_STATE_ORIG */
+
+ UInt32 ip;
+ Byte temp[4];
+ UInt32 range;
+ UInt32 code;
+ UInt16 probs[2 + 256];
+} CBcj2Dec;
+
+static void Bcj2Dec_Init(CBcj2Dec *p);
+
+/* Returns: SZ_OK or SZ_ERROR_DATA */
+static SRes Bcj2Dec_Decode(CBcj2Dec *p);
+
+#define Bcj2Dec_IsFinished(_p_) ((_p_)->code == 0)
+
+#define BCJ2_RELAT_LIMIT_NUM_BITS 26
+#define BCJ2_RELAT_LIMIT ((UInt32)1 << BCJ2_RELAT_LIMIT_NUM_BITS)
+
+/* limit for CBcj2Enc::fileSize variable */
+#define BCJ2_FileSize_MAX ((UInt32)1 << 31)
+
+EXTERN_C_END
+
+#endif
+
+/* Bra.h -- Branch converters for executables
+2013-01-18 : Igor Pavlov : Public domain */
+
+#ifndef __BRA_H
+#define __BRA_H
+
+/*#include "7zTypes.h"*/
+
+EXTERN_C_BEGIN
+
+/*
+These functions convert relative addresses to absolute addresses
+in CALL instructions to increase the compression ratio.
+
+ In:
+ data - data buffer
+ size - size of data
+ ip - current virtual Instruction Pinter (IP) value
+ state - state variable for x86 converter
+ encoding - 0 (for decoding), 1 (for encoding)
+
+ Out:
+ state - state variable for x86 converter
+
+ Returns:
+ The number of processed bytes. If you call these functions with multiple calls,
+ you must start next call with first byte after block of processed bytes.
+
+ Type Endian Alignment LookAhead
+
+ x86 little 1 4
+ ARMT little 2 2
+ ARM little 4 0
+ PPC big 4 0
+ SPARC big 4 0
+ IA64 little 16 0
+
+ size must be >= Alignment + LookAhead, if it's not last block.
+ If (size < Alignment + LookAhead), converter returns 0.
+
+ Example:
+
+ UInt32 ip = 0;
+ for ()
+ {
+ ; size must be >= Alignment + LookAhead, if it's not last block
+ SizeT processed = Convert(data, size, ip, 1);
+ data += processed;
+ size -= processed;
+ ip += processed;
+ }
+*/
+
+#define x86_Convert_Init(state) { state = 0; }
+static SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding);
+static SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding);
+static SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding);
+static SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding);
+static SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding);
+static SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding);
+
+EXTERN_C_END
+
+#endif
+
+/* Delta.h -- Delta converter
+2013-01-18 : Igor Pavlov : Public domain */
+
+#ifndef __DELTA_H
+#define __DELTA_H
+
+/*#include "7zTypes.h" */
+
+EXTERN_C_BEGIN
+
+#define DELTA_STATE_SIZE 256
+
+static void Delta_Init(Byte *state);
+static void Delta_Decode(Byte *state, unsigned delta, Byte *data, SizeT size);
+
+EXTERN_C_END
+
+#endif
+
+/* LzmaDec.h -- LZMA Decoder
+2013-01-18 : Igor Pavlov : Public domain */
+
+#ifndef __LZMA_DEC_H
+#define __LZMA_DEC_H
+
+/*#include "7zTypes.h"*/
+
+EXTERN_C_BEGIN
+
+/* #define _LZMA_PROB32 */
+/* _LZMA_PROB32 can increase the speed on some CPUs,
+ but memory usage for CLzmaDec::probs will be doubled in that case */
+
+#ifdef _LZMA_PROB32
+#define CLzmaProb UInt32
+#else
+#define CLzmaProb UInt16
+#endif
+
+
+/* ---------- LZMA Properties ---------- */
+
+#define LZMA_PROPS_SIZE 5
+
+typedef struct _CLzmaProps
+{
+ unsigned lc, lp, pb;
+ UInt32 dicSize;
+} CLzmaProps;
+
+/* LzmaProps_Decode - decodes properties
+Returns:
+ SZ_OK
+ SZ_ERROR_UNSUPPORTED - Unsupported properties
+*/
+
+static SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size);
+
+
+/* ---------- LZMA Decoder state ---------- */
+
+/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case.
+ Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */
+
+#define LZMA_REQUIRED_INPUT_MAX 20
+
+typedef struct
+{
+ CLzmaProps prop;
+ CLzmaProb *probs;
+ Byte *dic;
+ const Byte *buf;
+ UInt32 range, code;
+ SizeT dicPos;
+ SizeT dicBufSize;
+ UInt32 processedPos;
+ UInt32 checkDicSize;
+ unsigned state;
+ UInt32 reps[4];
+ unsigned remainLen;
+ int needFlush;
+ int needInitState;
+ UInt32 numProbs;
+ unsigned tempBufSize;
+ Byte tempBuf[LZMA_REQUIRED_INPUT_MAX];
+} CLzmaDec;
+
+#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; }
+
+static void LzmaDec_Init(CLzmaDec *p);
+
+/* There are two types of LZMA streams:
+ 0) Stream with end mark. That end mark adds about 6 bytes to compressed size.
+ 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */
+
+typedef enum
+{
+ LZMA_FINISH_ANY, /* finish at any point */
+ LZMA_FINISH_END /* block must be finished at the end */
+} ELzmaFinishMode;
+
+/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!!
+
+ You must use LZMA_FINISH_END, when you know that current output buffer
+ covers last bytes of block. In other cases you must use LZMA_FINISH_ANY.
+
+ If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK,
+ and output value of destLen will be less than output buffer size limit.
+ You can check status result also.
+
+ You can use multiple checks to test data integrity after full decompression:
+ 1) Check Result and "status" variable.
+ 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize.
+ 3) Check that output(srcLen) = compressedSize, if you know real compressedSize.
+ You must use correct finish mode in that case. */
+
+typedef enum
+{
+ LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */
+ LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */
+ LZMA_STATUS_NOT_FINISHED, /* stream was not finished */
+ LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */
+ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */
+} ELzmaStatus;
+
+/* ELzmaStatus is used only as output value for function call */
+
+
+/* ---------- Interfaces ---------- */
+
+/* There are 3 levels of interfaces:
+ 1) Dictionary Interface
+ 2) Buffer Interface
+ 3) One Call Interface
+ You can select any of these interfaces, but don't mix functions from different
+ groups for same object. */
+
+
+/* There are two variants to allocate state for Dictionary Interface:
+ 1) LzmaDec_Allocate / LzmaDec_Free
+ 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs
+ You can use variant 2, if you set dictionary buffer manually.
+ For Buffer Interface you must always use variant 1.
+
+LzmaDec_Allocate* can return:
+ SZ_OK
+ SZ_ERROR_MEM - Memory allocation error
+ SZ_ERROR_UNSUPPORTED - Unsupported properties
+*/
+
+static SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc);
+static void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc);
+
+/* ---------- Dictionary Interface ---------- */
+
+/* You can use it, if you want to eliminate the overhead for data copying from
+ dictionary to some other external buffer.
+ You must work with CLzmaDec variables directly in this interface.
+
+ STEPS:
+ LzmaDec_Constr()
+ LzmaDec_Allocate()
+ for (each new stream)
+ {
+ LzmaDec_Init()
+ while (it needs more decompression)
+ {
+ LzmaDec_DecodeToDic()
+ use data from CLzmaDec::dic and update CLzmaDec::dicPos
+ }
+ }
+ LzmaDec_Free()
+*/
+
+/* LzmaDec_DecodeToDic
+
+ The decoding to internal dictionary buffer (CLzmaDec::dic).
+ You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!!
+
+finishMode:
+ It has meaning only if the decoding reaches output limit (dicLimit).
+ LZMA_FINISH_ANY - Decode just dicLimit bytes.
+ LZMA_FINISH_END - Stream must be finished after dicLimit.
+
+Returns:
+ SZ_OK
+ status:
+ LZMA_STATUS_FINISHED_WITH_MARK
+ LZMA_STATUS_NOT_FINISHED
+ LZMA_STATUS_NEEDS_MORE_INPUT
+ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
+ SZ_ERROR_DATA - Data error
+*/
+
+static SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit,
+ const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
+
+EXTERN_C_END
+
+#endif
+
+/* Lzma2Dec.h -- LZMA2 Decoder
+2015-05-13 : Igor Pavlov : Public domain */
+
+#ifndef __LZMA2_DEC_H
+#define __LZMA2_DEC_H
+
+/*#include "LzmaDec.h"*/
+
+EXTERN_C_BEGIN
+
+/* ---------- State Interface ---------- */
+
+typedef struct
+{
+ CLzmaDec decoder;
+ UInt32 packSize;
+ UInt32 unpackSize;
+ unsigned state;
+ Byte control;
+ Bool needInitDic;
+ Bool needInitState;
+ Bool needInitProp;
+} CLzma2Dec;
+
+#define Lzma2Dec_Construct(p) LzmaDec_Construct(&(p)->decoder)
+#define Lzma2Dec_FreeProbs(p, alloc) LzmaDec_FreeProbs(&(p)->decoder, alloc);
+#define Lzma2Dec_Free(p, alloc) LzmaDec_Free(&(p)->decoder, alloc);
+
+static SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAlloc *alloc);
+static void Lzma2Dec_Init(CLzma2Dec *p);
+
+
+/*
+finishMode:
+ It has meaning only if the decoding reaches output limit (*destLen or dicLimit).
+ LZMA_FINISH_ANY - use smallest number of input bytes
+ LZMA_FINISH_END - read EndOfStream marker after decoding
+
+Returns:
+ SZ_OK
+ status:
+ LZMA_STATUS_FINISHED_WITH_MARK
+ LZMA_STATUS_NOT_FINISHED
+ LZMA_STATUS_NEEDS_MORE_INPUT
+ SZ_ERROR_DATA - Data error
+*/
+
+static SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit,
+ const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
+
+
+EXTERN_C_END
+
+#endif
+
+
+/* END HEADERS */
+
+
+/* 7zCrc.c -- CRC32 init
+2015-03-10 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "7zCrc.h"
+#include "CpuArch.h"
+*/
+#define UNUSED_VAR(x) (void)x;
+
+#define kCrcPoly 0xEDB88320
+
+#ifdef MY_CPU_LE
+ #define CRC_NUM_TABLES 8
+#else
+ #define CRC_NUM_TABLES 9
+
+ #define CRC_UINT32_SWAP(v) ((v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24))
+
+ static UInt32 MY_FAST_CALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table);
+ static UInt32 MY_FAST_CALL CrcUpdateT1_BeT8(UInt32 v, const void *data, size_t size, const UInt32 *table);
+#endif
+
+#ifndef MY_CPU_BE
+ static UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table);
+ static UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table);
+#endif
+
+typedef UInt32 (MY_FAST_CALL *CRC_FUNC)(UInt32 v, const void *data, size_t size, const UInt32 *table);
+
+static CRC_FUNC g_CrcUpdateT4;
+static CRC_FUNC g_CrcUpdateT8;
+static CRC_FUNC g_CrcUpdate;
+
+static UInt32 g_CrcTable[256 * CRC_NUM_TABLES];
+
+static UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size)
+{
+ return g_CrcUpdate(CRC_INIT_VAL, data, size, g_CrcTable) ^ CRC_INIT_VAL;
+}
+
+#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
+
+#if CRC_NUM_TABLES < 4
+static UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table)
+{
+ const Byte *p = (const Byte *)data;
+ const Byte *pEnd = p + size;
+ for (; p != pEnd; p++)
+ v = CRC_UPDATE_BYTE_2(v, *p);
+ return v;
+}
+#endif
+
+static void MY_FAST_CALL CrcGenerateTable()
+{
+ UInt32 i;
+ for (i = 0; i < 256; i++)
+ {
+ UInt32 r = i;
+ unsigned j;
+ for (j = 0; j < 8; j++)
+ r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1));
+ g_CrcTable[i] = r;
+ }
+ for (; i < 256 * CRC_NUM_TABLES; i++)
+ {
+ UInt32 r = g_CrcTable[i - 256];
+ g_CrcTable[i] = g_CrcTable[r & 0xFF] ^ (r >> 8);
+ }
+
+ #if CRC_NUM_TABLES < 4
+
+ g_CrcUpdate = CrcUpdateT1;
+
+ #else
+
+ #ifdef MY_CPU_LE
+
+ g_CrcUpdateT4 = CrcUpdateT4;
+ g_CrcUpdate = CrcUpdateT4;
+
+ #if CRC_NUM_TABLES >= 8
+ g_CrcUpdateT8 = CrcUpdateT8;
+
+ #ifdef MY_CPU_X86_OR_AMD64
+ if (!CPU_Is_InOrder())
+ g_CrcUpdate = CrcUpdateT8;
+ #endif
+ #endif
+
+ #else
+ {
+ #ifndef MY_CPU_BE
+ UInt32 k = 0x01020304;
+ const Byte *p = (const Byte *)&k;
+ if (p[0] == 4 && p[1] == 3)
+ {
+ g_CrcUpdateT4 = CrcUpdateT4;
+ g_CrcUpdate = CrcUpdateT4;
+ #if CRC_NUM_TABLES >= 8
+ g_CrcUpdateT8 = CrcUpdateT8;
+ /* g_CrcUpdate = CrcUpdateT8; */
+ #endif
+ }
+ else if (p[0] != 1 || p[1] != 2)
+ g_CrcUpdate = CrcUpdateT1;
+ else
+ #endif
+ {
+ for (i = 256 * CRC_NUM_TABLES - 1; i >= 256; i--)
+ {
+ UInt32 x = g_CrcTable[i - 256];
+ g_CrcTable[i] = CRC_UINT32_SWAP(x);
+ }
+ g_CrcUpdateT4 = CrcUpdateT1_BeT4;
+ g_CrcUpdate = CrcUpdateT1_BeT4;
+ #if CRC_NUM_TABLES >= 8
+ g_CrcUpdateT8 = CrcUpdateT1_BeT8;
+ /* g_CrcUpdate = CrcUpdateT1_BeT8; */
+ #endif
+ }
+ }
+ #endif
+
+ #endif
+}
+
+/* 7zCrcOpt.c -- CRC32 calculation
+2015-03-01 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "CpuArch.h"
+*/
+
+#ifndef MY_CPU_BE
+
+#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
+
+static UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table)
+{
+ const Byte *p = (const Byte *)data;
+ for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++)
+ v = CRC_UPDATE_BYTE_2(v, *p);
+ for (; size >= 4; size -= 4, p += 4)
+ {
+ v ^= *(const UInt32 *)p;
+ v =
+ table[0x300 + ((v ) & 0xFF)]
+ ^ table[0x200 + ((v >> 8) & 0xFF)]
+ ^ table[0x100 + ((v >> 16) & 0xFF)]
+ ^ table[0x000 + ((v >> 24))];
+ }
+ for (; size > 0; size--, p++)
+ v = CRC_UPDATE_BYTE_2(v, *p);
+ return v;
+}
+
+static UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table)
+{
+ const Byte *p = (const Byte *)data;
+ for (; size > 0 && ((unsigned)(ptrdiff_t)p & 7) != 0; size--, p++)
+ v = CRC_UPDATE_BYTE_2(v, *p);
+ for (; size >= 8; size -= 8, p += 8)
+ {
+ UInt32 d;
+ v ^= *(const UInt32 *)p;
+ v =
+ table[0x700 + ((v ) & 0xFF)]
+ ^ table[0x600 + ((v >> 8) & 0xFF)]
+ ^ table[0x500 + ((v >> 16) & 0xFF)]
+ ^ table[0x400 + ((v >> 24))];
+ d = *((const UInt32 *)p + 1);
+ v ^=
+ table[0x300 + ((d ) & 0xFF)]
+ ^ table[0x200 + ((d >> 8) & 0xFF)]
+ ^ table[0x100 + ((d >> 16) & 0xFF)]
+ ^ table[0x000 + ((d >> 24))];
+ }
+ for (; size > 0; size--, p++)
+ v = CRC_UPDATE_BYTE_2(v, *p);
+ return v;
+}
+
+#endif
+
+
+#ifndef MY_CPU_LE
+
+#define CRC_UINT32_SWAP(v) ((v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24))
+
+#define CRC_UPDATE_BYTE_2_BE(crc, b) (table[(((crc) >> 24) ^ (b))] ^ ((crc) << 8))
+
+static UInt32 MY_FAST_CALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table)
+{
+ const Byte *p = (const Byte *)data;
+ table += 0x100;
+ v = CRC_UINT32_SWAP(v);
+ for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++)
+ v = CRC_UPDATE_BYTE_2_BE(v, *p);
+ for (; size >= 4; size -= 4, p += 4)
+ {
+ v ^= *(const UInt32 *)p;
+ v =
+ table[0x000 + ((v ) & 0xFF)]
+ ^ table[0x100 + ((v >> 8) & 0xFF)]
+ ^ table[0x200 + ((v >> 16) & 0xFF)]
+ ^ table[0x300 + ((v >> 24))];
+ }
+ for (; size > 0; size--, p++)
+ v = CRC_UPDATE_BYTE_2_BE(v, *p);
+ return CRC_UINT32_SWAP(v);
+}
+
+static UInt32 MY_FAST_CALL CrcUpdateT1_BeT8(UInt32 v, const void *data, size_t size, const UInt32 *table)
+{
+ const Byte *p = (const Byte *)data;
+ table += 0x100;
+ v = CRC_UINT32_SWAP(v);
+ for (; size > 0 && ((unsigned)(ptrdiff_t)p & 7) != 0; size--, p++)
+ v = CRC_UPDATE_BYTE_2_BE(v, *p);
+ for (; size >= 8; size -= 8, p += 8)
+ {
+ UInt32 d;
+ v ^= *(const UInt32 *)p;
+ v =
+ table[0x400 + ((v ) & 0xFF)]
+ ^ table[0x500 + ((v >> 8) & 0xFF)]
+ ^ table[0x600 + ((v >> 16) & 0xFF)]
+ ^ table[0x700 + ((v >> 24))];
+ d = *((const UInt32 *)p + 1);
+ v ^=
+ table[0x000 + ((d ) & 0xFF)]
+ ^ table[0x100 + ((d >> 8) & 0xFF)]
+ ^ table[0x200 + ((d >> 16) & 0xFF)]
+ ^ table[0x300 + ((d >> 24))];
+ }
+ for (; size > 0; size--, p++)
+ v = CRC_UPDATE_BYTE_2_BE(v, *p);
+ return CRC_UINT32_SWAP(v);
+}
+
+#endif
+
+/* CpuArch.c -- CPU specific code
+2016-02-25: Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "CpuArch.h"
+*/
+
+#ifdef MY_CPU_X86_OR_AMD64
+
+#if (defined(_MSC_VER) && !defined(MY_CPU_AMD64)) || defined(__GNUC__)
+#define USE_ASM
+#endif
+
+#if !defined(USE_ASM) && _MSC_VER >= 1500
+#include
+#endif
+
+#if defined(USE_ASM) && !defined(MY_CPU_AMD64)
+static UInt32 CheckFlag(UInt32 flag)
+{
+ #ifdef _MSC_VER
+ __asm pushfd;
+ __asm pop EAX;
+ __asm mov EDX, EAX;
+ __asm xor EAX, flag;
+ __asm push EAX;
+ __asm popfd;
+ __asm pushfd;
+ __asm pop EAX;
+ __asm xor EAX, EDX;
+ __asm push EDX;
+ __asm popfd;
+ __asm and flag, EAX;
+ #else
+ __asm__ __volatile__ (
+ "pushf\n\t"
+ "pop %%EAX\n\t"
+ "movl %%EAX,%%EDX\n\t"
+ "xorl %0,%%EAX\n\t"
+ "push %%EAX\n\t"
+ "popf\n\t"
+ "pushf\n\t"
+ "pop %%EAX\n\t"
+ "xorl %%EDX,%%EAX\n\t"
+ "push %%EDX\n\t"
+ "popf\n\t"
+ "andl %%EAX, %0\n\t":
+ "=c" (flag) : "c" (flag) :
+ "%eax", "%edx");
+ #endif
+ return flag;
+}
+#define CHECK_CPUID_IS_SUPPORTED if (CheckFlag(1 << 18) == 0 || CheckFlag(1 << 21) == 0) return False;
+#else
+#define CHECK_CPUID_IS_SUPPORTED
+#endif
+
+#if defined(__WATCOMC__)
+static void __cpuid(int *cpuinfo, const UInt32 infotype);
+#pragma aux __cpuid = \
+ ".586" \
+ "cpuid" \
+ "mov [esi+0],eax" \
+ "mov [esi+4],ebx" \
+ "mov [esi+8],ecx" \
+ "mov [esi+12],edx" \
+ parm [esi] [eax] modify [ebx ecx edx];
+#endif
+
+
+static void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d)
+{
+ #ifdef USE_ASM
+
+ #ifdef _MSC_VER
+
+ UInt32 a2, b2, c2, d2;
+ __asm xor EBX, EBX;
+ __asm xor ECX, ECX;
+ __asm xor EDX, EDX;
+ __asm mov EAX, function;
+ __asm cpuid;
+ __asm mov a2, EAX;
+ __asm mov b2, EBX;
+ __asm mov c2, ECX;
+ __asm mov d2, EDX;
+
+ *a = a2;
+ *b = b2;
+ *c = c2;
+ *d = d2;
+
+ #else
+
+ __asm__ __volatile__ (
+ #if defined(MY_CPU_AMD64) && defined(__PIC__)
+ "mov %%rbx, %%rdi;"
+ "cpuid;"
+ "xchg %%rbx, %%rdi;"
+ : "=a" (*a) ,
+ "=D" (*b) ,
+ #elif defined(MY_CPU_X86) && defined(__PIC__)
+ "mov %%ebx, %%edi;"
+ "cpuid;"
+ "xchgl %%ebx, %%edi;"
+ : "=a" (*a) ,
+ "=D" (*b) ,
+ #else
+ "cpuid"
+ : "=a" (*a) ,
+ "=b" (*b) ,
+ #endif
+ "=c" (*c) ,
+ "=d" (*d)
+ : "0" (function)) ;
+
+ #endif
+
+ #else
+
+ int CPUInfo[4];
+ __cpuid(CPUInfo, function);
+ *a = CPUInfo[0];
+ *b = CPUInfo[1];
+ *c = CPUInfo[2];
+ *d = CPUInfo[3];
+
+ #endif
+}
+
+static Bool x86cpuid_CheckAndRead(Cx86cpuid *p)
+{
+ CHECK_CPUID_IS_SUPPORTED
+ MyCPUID(0, &p->maxFunc, &p->vendor[0], &p->vendor[2], &p->vendor[1]);
+ MyCPUID(1, &p->ver, &p->b, &p->c, &p->d);
+ return True;
+}
+
+static const UInt32 kVendors[][3] =
+{
+ { 0x756E6547, 0x49656E69, 0x6C65746E},
+ { 0x68747541, 0x69746E65, 0x444D4163},
+ { 0x746E6543, 0x48727561, 0x736C7561}
+};
+
+static int x86cpuid_GetFirm(const Cx86cpuid *p)
+{
+ unsigned i;
+ for (i = 0; i < sizeof(kVendors) / sizeof(kVendors[i]); i++)
+ {
+ const UInt32 *v = kVendors[i];
+ if (v[0] == p->vendor[0] &&
+ v[1] == p->vendor[1] &&
+ v[2] == p->vendor[2])
+ return (int)i;
+ }
+ return -1;
+}
+
+static Bool CPU_Is_InOrder()
+{
+ Cx86cpuid p;
+ int firm;
+ UInt32 family, model;
+ if (!x86cpuid_CheckAndRead(&p))
+ return True;
+
+ family = x86cpuid_GetFamily(p.ver);
+ model = x86cpuid_GetModel(p.ver);
+
+ firm = x86cpuid_GetFirm(&p);
+
+ switch (firm)
+ {
+ case CPU_FIRM_INTEL: return (family < 6 || (family == 6 && (
+ /* In-Order Atom CPU */
+ model == 0x1C /* 45 nm, N4xx, D4xx, N5xx, D5xx, 230, 330 */
+ || model == 0x26 /* 45 nm, Z6xx */
+ || model == 0x27 /* 32 nm, Z2460 */
+ || model == 0x35 /* 32 nm, Z2760 */
+ || model == 0x36 /* 32 nm, N2xxx, D2xxx */
+ )));
+ case CPU_FIRM_AMD: return (family < 5 || (family == 5 && (model < 6 || model == 0xA)));
+ case CPU_FIRM_VIA: return (family < 6 || (family == 6 && model < 0xF));
+ }
+ return True;
+}
+
+#endif
+
+/* 7zStream.c -- 7z Stream functions
+2013-11-12 : Igor Pavlov : Public domain */
+
+/*#include "Precomp.h"*/
+
+#include
+
+/*#include "7zTypes.h"*/
+
+static SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset)
+{
+ Int64 t = offset;
+ return stream->Seek(stream, &t, SZ_SEEK_SET);
+}
+
+static SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType)
+{
+ while (size != 0)
+ {
+ size_t processed = size;
+ RINOK(stream->Read(stream, buf, &processed));
+ if (processed == 0)
+ return errorType;
+ buf = (void *)((Byte *)buf + processed);
+ size -= processed;
+ }
+ return SZ_OK;
+}
+
+static SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size)
+{
+ return LookInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF);
+}
+
+static SRes LookToRead_Look_Lookahead(void *pp, const void **buf, size_t *size)
+{
+ SRes res = SZ_OK;
+ CLookToRead *p = (CLookToRead *)pp;
+ size_t size2 = p->size - p->pos;
+ if (size2 == 0 && *size > 0)
+ {
+ p->pos = 0;
+ size2 = LookToRead_BUF_SIZE;
+ res = p->realStream->Read(p->realStream, p->buf, &size2);
+ p->size = size2;
+ }
+ if (size2 < *size)
+ *size = size2;
+ *buf = p->buf + p->pos;
+ return res;
+}
+
+static SRes LookToRead_Look_Exact(void *pp, const void **buf, size_t *size)
+{
+ SRes res = SZ_OK;
+ CLookToRead *p = (CLookToRead *)pp;
+ size_t size2 = p->size - p->pos;
+ if (size2 == 0 && *size > 0)
+ {
+ p->pos = 0;
+ if (*size > LookToRead_BUF_SIZE)
+ *size = LookToRead_BUF_SIZE;
+ res = p->realStream->Read(p->realStream, p->buf, size);
+ size2 = p->size = *size;
+ }
+ if (size2 < *size)
+ *size = size2;
+ *buf = p->buf + p->pos;
+ return res;
+}
+
+static SRes LookToRead_Skip(void *pp, size_t offset)
+{
+ CLookToRead *p = (CLookToRead *)pp;
+ p->pos += offset;
+ return SZ_OK;
+}
+
+static SRes LookToRead_Read(void *pp, void *buf, size_t *size)
+{
+ CLookToRead *p = (CLookToRead *)pp;
+ size_t rem = p->size - p->pos;
+ if (rem == 0)
+ return p->realStream->Read(p->realStream, buf, size);
+ if (rem > *size)
+ rem = *size;
+ memcpy(buf, p->buf + p->pos, rem);
+ p->pos += rem;
+ *size = rem;
+ return SZ_OK;
+}
+
+static SRes LookToRead_Seek(void *pp, Int64 *pos, ESzSeek origin)
+{
+ CLookToRead *p = (CLookToRead *)pp;
+ p->pos = p->size = 0;
+ return p->realStream->Seek(p->realStream, pos, origin);
+}
+
+static void LookToRead_CreateVTable(CLookToRead *p, int lookahead)
+{
+ p->s.Look = lookahead ?
+ LookToRead_Look_Lookahead :
+ LookToRead_Look_Exact;
+ p->s.Skip = LookToRead_Skip;
+ p->s.Read = LookToRead_Read;
+ p->s.Seek = LookToRead_Seek;
+}
+
+static void LookToRead_Init(CLookToRead *p)
+{
+ p->pos = p->size = 0;
+}
+
+
+/* 7zArcIn.c -- 7z Input functions
+2016-05-16 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include
+
+#include "7z.h"
+#include "7zBuf.h"
+#include "7zCrc.h"
+#include "CpuArch.h"
+*/
+
+#define MY_ALLOC(T, p, size, alloc) { \
+ if ((p = (T *)IAlloc_Alloc(alloc, (size) * sizeof(T))) == NULL) return SZ_ERROR_MEM; }
+
+#define MY_ALLOC_ZE(T, p, size, alloc) { if ((size) == 0) p = NULL; else MY_ALLOC(T, p, size, alloc) }
+
+#define MY_ALLOC_AND_CPY(to, size, from, alloc) \
+ { MY_ALLOC(Byte, to, size, alloc); memcpy(to, from, size); }
+
+#define MY_ALLOC_ZE_AND_CPY(to, size, from, alloc) \
+ { if ((size) == 0) p = NULL; else { MY_ALLOC_AND_CPY(to, size, from, alloc) } }
+
+#define k7zMajorVersion 0
+
+enum EIdEnum
+{
+ k7zIdEnd,
+ k7zIdHeader,
+ k7zIdArchiveProperties,
+ k7zIdAdditionalStreamsInfo,
+ k7zIdMainStreamsInfo,
+ k7zIdFilesInfo,
+ k7zIdPackInfo,
+ k7zIdUnpackInfo,
+ k7zIdSubStreamsInfo,
+ k7zIdSize,
+ k7zIdCRC,
+ k7zIdFolder,
+ k7zIdCodersUnpackSize,
+ k7zIdNumUnpackStream,
+ k7zIdEmptyStream,
+ k7zIdEmptyFile,
+ k7zIdAnti,
+ k7zIdName,
+ k7zIdCTime,
+ k7zIdATime,
+ k7zIdMTime,
+ k7zIdWinAttrib,
+ k7zIdComment,
+ k7zIdEncodedHeader,
+ k7zIdStartPos,
+ k7zIdDummy
+ /* k7zNtSecure, */
+ /* k7zParent, */
+ /* k7zIsReal */
+};
+
+static const Byte k7zSignature[k7zSignatureSize] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C};
+
+#define SzBitUi32s_Init(p) { (p)->Defs = NULL; (p)->Vals = NULL; }
+
+static SRes SzBitUi32s_Alloc(CSzBitUi32s *p, size_t num, ISzAlloc *alloc)
+{
+ if (num == 0)
+ {
+ p->Defs = NULL;
+ p->Vals = NULL;
+ }
+ else
+ {
+ MY_ALLOC(Byte, p->Defs, (num + 7) >> 3, alloc);
+ MY_ALLOC(UInt32, p->Vals, num, alloc);
+ }
+ return SZ_OK;
+}
+
+static void SzBitUi32s_Free(CSzBitUi32s *p, ISzAlloc *alloc)
+{
+ IAlloc_Free(alloc, p->Defs); p->Defs = NULL;
+ IAlloc_Free(alloc, p->Vals); p->Vals = NULL;
+}
+
+#define SzBitUi64s_Init(p) { (p)->Defs = NULL; (p)->Vals = NULL; }
+
+static void SzBitUi64s_Free(CSzBitUi64s *p, ISzAlloc *alloc)
+{
+ IAlloc_Free(alloc, p->Defs); p->Defs = NULL;
+ IAlloc_Free(alloc, p->Vals); p->Vals = NULL;
+}
+
+
+static void SzAr_Init(CSzAr *p)
+{
+ p->NumPackStreams = 0;
+ p->NumFolders = 0;
+
+ p->PackPositions = NULL;
+ SzBitUi32s_Init(&p->FolderCRCs);
+
+ p->FoCodersOffsets = NULL;
+ p->FoStartPackStreamIndex = NULL;
+ p->FoToCoderUnpackSizes = NULL;
+ p->FoToMainUnpackSizeIndex = NULL;
+ p->CoderUnpackSizes = NULL;
+
+ p->CodersData = NULL;
+}
+
+static void SzAr_Free(CSzAr *p, ISzAlloc *alloc)
+{
+ IAlloc_Free(alloc, p->PackPositions);
+ SzBitUi32s_Free(&p->FolderCRCs, alloc);
+
+ IAlloc_Free(alloc, p->FoCodersOffsets);
+ IAlloc_Free(alloc, p->FoStartPackStreamIndex);
+ IAlloc_Free(alloc, p->FoToCoderUnpackSizes);
+ IAlloc_Free(alloc, p->FoToMainUnpackSizeIndex);
+ IAlloc_Free(alloc, p->CoderUnpackSizes);
+
+ IAlloc_Free(alloc, p->CodersData);
+
+ SzAr_Init(p);
+}
+
+
+static void SzArEx_Init(CSzArEx *p)
+{
+ SzAr_Init(&p->db);
+
+ p->NumFiles = 0;
+ p->dataPos = 0;
+
+ p->UnpackPositions = NULL;
+ p->IsDirs = NULL;
+
+ p->FolderToFile = NULL;
+ p->FileToFolder = NULL;
+
+ p->FileNameOffsets = NULL;
+ p->FileNames = NULL;
+
+ SzBitUi32s_Init(&p->CRCs);
+ SzBitUi32s_Init(&p->Attribs);
+ /* SzBitUi32s_Init(&p->Parents); */
+ SzBitUi64s_Init(&p->MTime);
+ SzBitUi64s_Init(&p->CTime);
+}
+
+static void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc)
+{
+ IAlloc_Free(alloc, p->UnpackPositions);
+ IAlloc_Free(alloc, p->IsDirs);
+
+ IAlloc_Free(alloc, p->FolderToFile);
+ IAlloc_Free(alloc, p->FileToFolder);
+
+ IAlloc_Free(alloc, p->FileNameOffsets);
+ IAlloc_Free(alloc, p->FileNames);
+
+ SzBitUi32s_Free(&p->CRCs, alloc);
+ SzBitUi32s_Free(&p->Attribs, alloc);
+ /* SzBitUi32s_Free(&p->Parents, alloc); */
+ SzBitUi64s_Free(&p->MTime, alloc);
+ SzBitUi64s_Free(&p->CTime, alloc);
+
+ SzAr_Free(&p->db, alloc);
+ SzArEx_Init(p);
+}
+
+
+static int TestSignatureCandidate(const Byte *testBytes)
+{
+ unsigned i;
+ for (i = 0; i < k7zSignatureSize; i++)
+ if (testBytes[i] != k7zSignature[i])
+ return 0;
+ return 1;
+}
+
+#define SzData_Clear(p) { (p)->Data = NULL; (p)->Size = 0; }
+
+#define SZ_READ_BYTE_SD(_sd_, dest) if ((_sd_)->Size == 0) return SZ_ERROR_ARCHIVE; (_sd_)->Size--; dest = *(_sd_)->Data++;
+#define SZ_READ_BYTE(dest) SZ_READ_BYTE_SD(sd, dest)
+#define SZ_READ_BYTE_2(dest) if (sd.Size == 0) return SZ_ERROR_ARCHIVE; sd.Size--; dest = *sd.Data++;
+
+#define SKIP_DATA(sd, size) { sd->Size -= (size_t)(size); sd->Data += (size_t)(size); }
+#define SKIP_DATA2(sd, size) { sd.Size -= (size_t)(size); sd.Data += (size_t)(size); }
+
+#define SZ_READ_32(dest) if (sd.Size < 4) return SZ_ERROR_ARCHIVE; \
+ dest = GetUi32(sd.Data); SKIP_DATA2(sd, 4);
+
+static MY_NO_INLINE SRes ReadNumber(CSzData *sd, UInt64 *value)
+{
+ Byte firstByte, mask;
+ unsigned i;
+ UInt32 v;
+
+ SZ_READ_BYTE(firstByte);
+ if ((firstByte & 0x80) == 0)
+ {
+ *value = firstByte;
+ return SZ_OK;
+ }
+ SZ_READ_BYTE(v);
+ if ((firstByte & 0x40) == 0)
+ {
+ *value = (((UInt32)firstByte & 0x3F) << 8) | v;
+ return SZ_OK;
+ }
+ SZ_READ_BYTE(mask);
+ *value = v | ((UInt32)mask << 8);
+ mask = 0x20;
+ for (i = 2; i < 8; i++)
+ {
+ Byte b;
+ if ((firstByte & mask) == 0)
+ {
+ UInt64 highPart = (unsigned)firstByte & (unsigned)(mask - 1);
+ *value |= (highPart << (8 * i));
+ return SZ_OK;
+ }
+ SZ_READ_BYTE(b);
+ *value |= ((UInt64)b << (8 * i));
+ mask >>= 1;
+ }
+ return SZ_OK;
+}
+
+
+static MY_NO_INLINE SRes SzReadNumber32(CSzData *sd, UInt32 *value)
+{
+ Byte firstByte;
+ UInt64 value64;
+ if (sd->Size == 0)
+ return SZ_ERROR_ARCHIVE;
+ firstByte = *sd->Data;
+ if ((firstByte & 0x80) == 0)
+ {
+ *value = firstByte;
+ sd->Data++;
+ sd->Size--;
+ return SZ_OK;
+ }
+ RINOK(ReadNumber(sd, &value64));
+ if (value64 >= (UInt32)0x80000000 - 1)
+ return SZ_ERROR_UNSUPPORTED;
+ if (value64 >= ((UInt64)(1) << ((sizeof(size_t) - 1) * 8 + 4)))
+ return SZ_ERROR_UNSUPPORTED;
+ *value = (UInt32)value64;
+ return SZ_OK;
+}
+
+#define ReadID(sd, value) ReadNumber(sd, value)
+
+static SRes SkipData(CSzData *sd)
+{
+ UInt64 size;
+ RINOK(ReadNumber(sd, &size));
+ if (size > sd->Size)
+ return SZ_ERROR_ARCHIVE;
+ SKIP_DATA(sd, size);
+ return SZ_OK;
+}
+
+static SRes WaitId(CSzData *sd, UInt32 id)
+{
+ for (;;)
+ {
+ UInt64 type;
+ RINOK(ReadID(sd, &type));
+ if (type == id)
+ return SZ_OK;
+ if (type == k7zIdEnd)
+ return SZ_ERROR_ARCHIVE;
+ RINOK(SkipData(sd));
+ }
+}
+
+static SRes RememberBitVector(CSzData *sd, UInt32 numItems, const Byte **v)
+{
+ UInt32 numBytes = (numItems + 7) >> 3;
+ if (numBytes > sd->Size)
+ return SZ_ERROR_ARCHIVE;
+ *v = sd->Data;
+ SKIP_DATA(sd, numBytes);
+ return SZ_OK;
+}
+
+static UInt32 CountDefinedBits(const Byte *bits, UInt32 numItems)
+{
+ Byte b = 0;
+ unsigned m = 0;
+ UInt32 sum = 0;
+ for (; numItems != 0; numItems--)
+ {
+ if (m == 0)
+ {
+ b = *bits++;
+ m = 8;
+ }
+ m--;
+ sum += ((b >> m) & 1);
+ }
+ return sum;
+}
+
+static MY_NO_INLINE SRes ReadBitVector(CSzData *sd, UInt32 numItems, Byte **v, ISzAlloc *alloc)
+{
+ Byte allAreDefined;
+ Byte *v2;
+ UInt32 numBytes = (numItems + 7) >> 3;
+ *v = NULL;
+ SZ_READ_BYTE(allAreDefined);
+ if (numBytes == 0)
+ return SZ_OK;
+ if (allAreDefined == 0)
+ {
+ if (numBytes > sd->Size)
+ return SZ_ERROR_ARCHIVE;
+ MY_ALLOC_AND_CPY(*v, numBytes, sd->Data, alloc);
+ SKIP_DATA(sd, numBytes);
+ return SZ_OK;
+ }
+ MY_ALLOC(Byte, *v, numBytes, alloc);
+ v2 = *v;
+ memset(v2, 0xFF, (size_t)numBytes);
+ {
+ unsigned numBits = (unsigned)numItems & 7;
+ if (numBits != 0)
+ v2[numBytes - 1] = (Byte)((((UInt32)1 << numBits) - 1) << (8 - numBits));
+ }
+ return SZ_OK;
+}
+
+static MY_NO_INLINE SRes ReadUi32s(CSzData *sd2, UInt32 numItems, CSzBitUi32s *crcs, ISzAlloc *alloc)
+{
+ UInt32 i;
+ CSzData sd;
+ UInt32 *vals;
+ const Byte *defs;
+ MY_ALLOC_ZE(UInt32, crcs->Vals, numItems, alloc);
+ sd = *sd2;
+ defs = crcs->Defs;
+ vals = crcs->Vals;
+ for (i = 0; i < numItems; i++)
+ if (SzBitArray_Check(defs, i))
+ {
+ SZ_READ_32(vals[i]);
+ }
+ else
+ vals[i] = 0;
+ *sd2 = sd;
+ return SZ_OK;
+}
+
+static SRes ReadBitUi32s(CSzData *sd, UInt32 numItems, CSzBitUi32s *crcs, ISzAlloc *alloc)
+{
+ SzBitUi32s_Free(crcs, alloc);
+ RINOK(ReadBitVector(sd, numItems, &crcs->Defs, alloc));
+ return ReadUi32s(sd, numItems, crcs, alloc);
+}
+
+static SRes SkipBitUi32s(CSzData *sd, UInt32 numItems)
+{
+ Byte allAreDefined;
+ UInt32 numDefined = numItems;
+ SZ_READ_BYTE(allAreDefined);
+ if (!allAreDefined)
+ {
+ size_t numBytes = (numItems + 7) >> 3;
+ if (numBytes > sd->Size)
+ return SZ_ERROR_ARCHIVE;
+ numDefined = CountDefinedBits(sd->Data, numItems);
+ SKIP_DATA(sd, numBytes);
+ }
+ if (numDefined > (sd->Size >> 2))
+ return SZ_ERROR_ARCHIVE;
+ SKIP_DATA(sd, (size_t)numDefined * 4);
+ return SZ_OK;
+}
+
+static SRes ReadPackInfo(CSzAr *p, CSzData *sd, ISzAlloc *alloc)
+{
+ RINOK(SzReadNumber32(sd, &p->NumPackStreams));
+
+ RINOK(WaitId(sd, k7zIdSize));
+ MY_ALLOC(UInt64, p->PackPositions, (size_t)p->NumPackStreams + 1, alloc);
+ {
+ UInt64 sum = 0;
+ UInt32 i;
+ UInt32 numPackStreams = p->NumPackStreams;
+ for (i = 0; i < numPackStreams; i++)
+ {
+ UInt64 packSize;
+ p->PackPositions[i] = sum;
+ RINOK(ReadNumber(sd, &packSize));
+ sum += packSize;
+ if (sum < packSize)
+ return SZ_ERROR_ARCHIVE;
+ }
+ p->PackPositions[i] = sum;
+ }
+
+ for (;;)
+ {
+ UInt64 type;
+ RINOK(ReadID(sd, &type));
+ if (type == k7zIdEnd)
+ return SZ_OK;
+ if (type == k7zIdCRC)
+ {
+ /* CRC of packed streams is unused now */
+ RINOK(SkipBitUi32s(sd, p->NumPackStreams));
+ continue;
+ }
+ RINOK(SkipData(sd));
+ }
+}
+
+/*
+static SRes SzReadSwitch(CSzData *sd)
+{
+ Byte external;
+ RINOK(SzReadByte(sd, &external));
+ return (external == 0) ? SZ_OK: SZ_ERROR_UNSUPPORTED;
+}
+*/
+
+#define k_NumCodersStreams_in_Folder_MAX (SZ_NUM_BONDS_IN_FOLDER_MAX + SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX)
+
+static SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd)
+{
+ UInt32 numCoders, i;
+ UInt32 numInStreams = 0;
+ const Byte *dataStart = sd->Data;
+
+ f->NumCoders = 0;
+ f->NumBonds = 0;
+ f->NumPackStreams = 0;
+ f->UnpackStream = 0;
+
+ RINOK(SzReadNumber32(sd, &numCoders));
+ if (numCoders == 0 || numCoders > SZ_NUM_CODERS_IN_FOLDER_MAX)
+ return SZ_ERROR_UNSUPPORTED;
+
+ for (i = 0; i < numCoders; i++)
+ {
+ Byte mainByte;
+ CSzCoderInfo *coder = f->Coders + i;
+ unsigned idSize, j;
+ UInt64 id;
+
+ SZ_READ_BYTE(mainByte);
+ if ((mainByte & 0xC0) != 0)
+ return SZ_ERROR_UNSUPPORTED;
+
+ idSize = (unsigned)(mainByte & 0xF);
+ if (idSize > sizeof(id))
+ return SZ_ERROR_UNSUPPORTED;
+ if (idSize > sd->Size)
+ return SZ_ERROR_ARCHIVE;
+ id = 0;
+ for (j = 0; j < idSize; j++)
+ {
+ id = ((id << 8) | *sd->Data);
+ sd->Data++;
+ sd->Size--;
+ }
+ if (id > UINT64_CONST(0xFFFFFFFF))
+ return SZ_ERROR_UNSUPPORTED;
+ coder->MethodID = (UInt32)id;
+
+ coder->NumStreams = 1;
+ coder->PropsOffset = 0;
+ coder->PropsSize = 0;
+
+ if ((mainByte & 0x10) != 0)
+ {
+ UInt32 numStreams;
+
+ RINOK(SzReadNumber32(sd, &numStreams));
+ if (numStreams > k_NumCodersStreams_in_Folder_MAX)
+ return SZ_ERROR_UNSUPPORTED;
+ coder->NumStreams = (Byte)numStreams;
+
+ RINOK(SzReadNumber32(sd, &numStreams));
+ if (numStreams != 1)
+ return SZ_ERROR_UNSUPPORTED;
+ }
+
+ numInStreams += coder->NumStreams;
+
+ if (numInStreams > k_NumCodersStreams_in_Folder_MAX)
+ return SZ_ERROR_UNSUPPORTED;
+
+ if ((mainByte & 0x20) != 0)
+ {
+ UInt32 propsSize = 0;
+ RINOK(SzReadNumber32(sd, &propsSize));
+ if (propsSize > sd->Size)
+ return SZ_ERROR_ARCHIVE;
+ if (propsSize >= 0x80)
+ return SZ_ERROR_UNSUPPORTED;
+ coder->PropsOffset = sd->Data - dataStart;
+ coder->PropsSize = (Byte)propsSize;
+ sd->Data += (size_t)propsSize;
+ sd->Size -= (size_t)propsSize;
+ }
+ }
+
+ /*
+ if (numInStreams == 1 && numCoders == 1)
+ {
+ f->NumPackStreams = 1;
+ f->PackStreams[0] = 0;
+ }
+ else
+ */
+ {
+ Byte streamUsed[k_NumCodersStreams_in_Folder_MAX];
+ UInt32 numBonds, numPackStreams;
+
+ numBonds = numCoders - 1;
+ if (numInStreams < numBonds)
+ return SZ_ERROR_ARCHIVE;
+ if (numBonds > SZ_NUM_BONDS_IN_FOLDER_MAX)
+ return SZ_ERROR_UNSUPPORTED;
+ f->NumBonds = numBonds;
+
+ numPackStreams = numInStreams - numBonds;
+ if (numPackStreams > SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX)
+ return SZ_ERROR_UNSUPPORTED;
+ f->NumPackStreams = numPackStreams;
+
+ for (i = 0; i < numInStreams; i++)
+ streamUsed[i] = False;
+
+ if (numBonds != 0)
+ {
+ Byte coderUsed[SZ_NUM_CODERS_IN_FOLDER_MAX];
+
+ for (i = 0; i < numCoders; i++)
+ coderUsed[i] = False;
+
+ for (i = 0; i < numBonds; i++)
+ {
+ CSzBond *bp = f->Bonds + i;
+
+ RINOK(SzReadNumber32(sd, &bp->InIndex));
+ if (bp->InIndex >= numInStreams || streamUsed[bp->InIndex])
+ return SZ_ERROR_ARCHIVE;
+ streamUsed[bp->InIndex] = True;
+
+ RINOK(SzReadNumber32(sd, &bp->OutIndex));
+ if (bp->OutIndex >= numCoders || coderUsed[bp->OutIndex])
+ return SZ_ERROR_ARCHIVE;
+ coderUsed[bp->OutIndex] = True;
+ }
+
+ for (i = 0; i < numCoders; i++)
+ if (!coderUsed[i])
+ {
+ f->UnpackStream = i;
+ break;
+ }
+
+ if (i == numCoders)
+ return SZ_ERROR_ARCHIVE;
+ }
+
+ if (numPackStreams == 1)
+ {
+ for (i = 0; i < numInStreams; i++)
+ if (!streamUsed[i])
+ break;
+ if (i == numInStreams)
+ return SZ_ERROR_ARCHIVE;
+ f->PackStreams[0] = i;
+ }
+ else
+ for (i = 0; i < numPackStreams; i++)
+ {
+ UInt32 index;
+ RINOK(SzReadNumber32(sd, &index));
+ if (index >= numInStreams || streamUsed[index])
+ return SZ_ERROR_ARCHIVE;
+ streamUsed[index] = True;
+ f->PackStreams[i] = index;
+ }
+ }
+
+ f->NumCoders = numCoders;
+
+ return SZ_OK;
+}
+
+
+static MY_NO_INLINE SRes SkipNumbers(CSzData *sd2, UInt32 num)
+{
+ CSzData sd;
+ sd = *sd2;
+ for (; num != 0; num--)
+ {
+ Byte firstByte, mask;
+ unsigned i;
+ SZ_READ_BYTE_2(firstByte);
+ if ((firstByte & 0x80) == 0)
+ continue;
+ if ((firstByte & 0x40) == 0)
+ {
+ if (sd.Size == 0)
+ return SZ_ERROR_ARCHIVE;
+ sd.Size--;
+ sd.Data++;
+ continue;
+ }
+ mask = 0x20;
+ for (i = 2; i < 8 && (firstByte & mask) != 0; i++)
+ mask >>= 1;
+ if (i > sd.Size)
+ return SZ_ERROR_ARCHIVE;
+ SKIP_DATA2(sd, i);
+ }
+ *sd2 = sd;
+ return SZ_OK;
+}
+
+
+#define k_Scan_NumCoders_MAX 64
+#define k_Scan_NumCodersStreams_in_Folder_MAX 64
+
+
+static SRes ReadUnpackInfo(CSzAr *p,
+ CSzData *sd2,
+ UInt32 numFoldersMax,
+ const CBuf *tempBufs, UInt32 numTempBufs,
+ ISzAlloc *alloc)
+{
+ CSzData sd;
+
+ UInt32 fo, numFolders, numCodersOutStreams, packStreamIndex;
+ const Byte *startBufPtr;
+ Byte external;
+
+ RINOK(WaitId(sd2, k7zIdFolder));
+
+ RINOK(SzReadNumber32(sd2, &numFolders));
+ if (numFolders > numFoldersMax)
+ return SZ_ERROR_UNSUPPORTED;
+ p->NumFolders = numFolders;
+
+ SZ_READ_BYTE_SD(sd2, external);
+ if (external == 0)
+ sd = *sd2;
+ else
+ {
+ UInt32 index;
+ RINOK(SzReadNumber32(sd2, &index));
+ if (index >= numTempBufs)
+ return SZ_ERROR_ARCHIVE;
+ sd.Data = tempBufs[index].data;
+ sd.Size = tempBufs[index].size;
+ }
+
+ MY_ALLOC(size_t, p->FoCodersOffsets, (size_t)numFolders + 1, alloc);
+ MY_ALLOC(UInt32, p->FoStartPackStreamIndex, (size_t)numFolders + 1, alloc);
+ MY_ALLOC(UInt32, p->FoToCoderUnpackSizes, (size_t)numFolders + 1, alloc);
+ MY_ALLOC(Byte, p->FoToMainUnpackSizeIndex, (size_t)numFolders, alloc);
+
+ startBufPtr = sd.Data;
+
+ packStreamIndex = 0;
+ numCodersOutStreams = 0;
+
+ for (fo = 0; fo < numFolders; fo++)
+ {
+ UInt32 numCoders, ci, numInStreams = 0;
+
+ p->FoCodersOffsets[fo] = sd.Data - startBufPtr;
+
+ RINOK(SzReadNumber32(&sd, &numCoders));
+ if (numCoders == 0 || numCoders > k_Scan_NumCoders_MAX)
+ return SZ_ERROR_UNSUPPORTED;
+
+ for (ci = 0; ci < numCoders; ci++)
+ {
+ Byte mainByte;
+ unsigned idSize;
+ UInt32 coderInStreams;
+
+ SZ_READ_BYTE_2(mainByte);
+ if ((mainByte & 0xC0) != 0)
+ return SZ_ERROR_UNSUPPORTED;
+ idSize = (mainByte & 0xF);
+ if (idSize > 8)
+ return SZ_ERROR_UNSUPPORTED;
+ if (idSize > sd.Size)
+ return SZ_ERROR_ARCHIVE;
+ SKIP_DATA2(sd, idSize);
+
+ coderInStreams = 1;
+
+ if ((mainByte & 0x10) != 0)
+ {
+ UInt32 coderOutStreams;
+ RINOK(SzReadNumber32(&sd, &coderInStreams));
+ RINOK(SzReadNumber32(&sd, &coderOutStreams));
+ if (coderInStreams > k_Scan_NumCodersStreams_in_Folder_MAX || coderOutStreams != 1)
+ return SZ_ERROR_UNSUPPORTED;
+ }
+
+ numInStreams += coderInStreams;
+
+ if ((mainByte & 0x20) != 0)
+ {
+ UInt32 propsSize;
+ RINOK(SzReadNumber32(&sd, &propsSize));
+ if (propsSize > sd.Size)
+ return SZ_ERROR_ARCHIVE;
+ SKIP_DATA2(sd, propsSize);
+ }
+ }
+
+ {
+ UInt32 indexOfMainStream = 0;
+ UInt32 numPackStreams = 1;
+
+ if (numCoders != 1 || numInStreams != 1)
+ {
+ Byte streamUsed[k_Scan_NumCodersStreams_in_Folder_MAX];
+ Byte coderUsed[k_Scan_NumCoders_MAX];
+
+ UInt32 i;
+ UInt32 numBonds = numCoders - 1;
+ if (numInStreams < numBonds)
+ return SZ_ERROR_ARCHIVE;
+
+ if (numInStreams > k_Scan_NumCodersStreams_in_Folder_MAX)
+ return SZ_ERROR_UNSUPPORTED;
+
+ for (i = 0; i < numInStreams; i++)
+ streamUsed[i] = False;
+ for (i = 0; i < numCoders; i++)
+ coderUsed[i] = False;
+
+ for (i = 0; i < numBonds; i++)
+ {
+ UInt32 index;
+
+ RINOK(SzReadNumber32(&sd, &index));
+ if (index >= numInStreams || streamUsed[index])
+ return SZ_ERROR_ARCHIVE;
+ streamUsed[index] = True;
+
+ RINOK(SzReadNumber32(&sd, &index));
+ if (index >= numCoders || coderUsed[index])
+ return SZ_ERROR_ARCHIVE;
+ coderUsed[index] = True;
+ }
+
+ numPackStreams = numInStreams - numBonds;
+
+ if (numPackStreams != 1)
+ for (i = 0; i < numPackStreams; i++)
+ {
+ UInt32 index;
+ RINOK(SzReadNumber32(&sd, &index));
+ if (index >= numInStreams || streamUsed[index])
+ return SZ_ERROR_ARCHIVE;
+ streamUsed[index] = True;
+ }
+
+ for (i = 0; i < numCoders; i++)
+ if (!coderUsed[i])
+ {
+ indexOfMainStream = i;
+ break;
+ }
+
+ if (i == numCoders)
+ return SZ_ERROR_ARCHIVE;
+ }
+
+ p->FoStartPackStreamIndex[fo] = packStreamIndex;
+ p->FoToCoderUnpackSizes[fo] = numCodersOutStreams;
+ p->FoToMainUnpackSizeIndex[fo] = (Byte)indexOfMainStream;
+ numCodersOutStreams += numCoders;
+ if (numCodersOutStreams < numCoders)
+ return SZ_ERROR_UNSUPPORTED;
+ if (numPackStreams > p->NumPackStreams - packStreamIndex)
+ return SZ_ERROR_ARCHIVE;
+ packStreamIndex += numPackStreams;
+ }
+ }
+
+ p->FoToCoderUnpackSizes[fo] = numCodersOutStreams;
+
+ {
+ size_t dataSize = sd.Data - startBufPtr;
+ p->FoStartPackStreamIndex[fo] = packStreamIndex;
+ p->FoCodersOffsets[fo] = dataSize;
+ MY_ALLOC_ZE_AND_CPY(p->CodersData, dataSize, startBufPtr, alloc);
+ }
+
+ if (external != 0)
+ {
+ if (sd.Size != 0)
+ return SZ_ERROR_ARCHIVE;
+ sd = *sd2;
+ }
+
+ RINOK(WaitId(&sd, k7zIdCodersUnpackSize));
+
+ MY_ALLOC_ZE(UInt64, p->CoderUnpackSizes, (size_t)numCodersOutStreams, alloc);
+ {
+ UInt32 i;
+ for (i = 0; i < numCodersOutStreams; i++)
+ {
+ RINOK(ReadNumber(&sd, p->CoderUnpackSizes + i));
+ }
+ }
+
+ for (;;)
+ {
+ UInt64 type;
+ RINOK(ReadID(&sd, &type));
+ if (type == k7zIdEnd)
+ {
+ *sd2 = sd;
+ return SZ_OK;
+ }
+ if (type == k7zIdCRC)
+ {
+ RINOK(ReadBitUi32s(&sd, numFolders, &p->FolderCRCs, alloc));
+ continue;
+ }
+ RINOK(SkipData(&sd));
+ }
+}
+
+
+static UInt64 SzAr_GetFolderUnpackSize(const CSzAr *p, UInt32 folderIndex)
+{
+ return p->CoderUnpackSizes[p->FoToCoderUnpackSizes[folderIndex] + p->FoToMainUnpackSizeIndex[folderIndex]];
+}
+
+
+typedef struct
+{
+ UInt32 NumTotalSubStreams;
+ UInt32 NumSubDigests;
+ CSzData sdNumSubStreams;
+ CSzData sdSizes;
+ CSzData sdCRCs;
+} CSubStreamInfo;
+
+
+static SRes ReadSubStreamsInfo(CSzAr *p, CSzData *sd, CSubStreamInfo *ssi)
+{
+ UInt64 type = 0;
+ UInt32 numSubDigests = 0;
+ UInt32 numFolders = p->NumFolders;
+ UInt32 numUnpackStreams = numFolders;
+ UInt32 numUnpackSizesInData = 0;
+
+ for (;;)
+ {
+ RINOK(ReadID(sd, &type));
+ if (type == k7zIdNumUnpackStream)
+ {
+ UInt32 i;
+ ssi->sdNumSubStreams.Data = sd->Data;
+ numUnpackStreams = 0;
+ numSubDigests = 0;
+ for (i = 0; i < numFolders; i++)
+ {
+ UInt32 numStreams;
+ RINOK(SzReadNumber32(sd, &numStreams));
+ if (numUnpackStreams > numUnpackStreams + numStreams)
+ return SZ_ERROR_UNSUPPORTED;
+ numUnpackStreams += numStreams;
+ if (numStreams != 0)
+ numUnpackSizesInData += (numStreams - 1);
+ if (numStreams != 1 || !SzBitWithVals_Check(&p->FolderCRCs, i))
+ numSubDigests += numStreams;
+ }
+ ssi->sdNumSubStreams.Size = sd->Data - ssi->sdNumSubStreams.Data;
+ continue;
+ }
+ if (type == k7zIdCRC || type == k7zIdSize || type == k7zIdEnd)
+ break;
+ RINOK(SkipData(sd));
+ }
+
+ if (!ssi->sdNumSubStreams.Data)
+ {
+ numSubDigests = numFolders;
+ if (p->FolderCRCs.Defs)
+ numSubDigests = numFolders - CountDefinedBits(p->FolderCRCs.Defs, numFolders);
+ }
+
+ ssi->NumTotalSubStreams = numUnpackStreams;
+ ssi->NumSubDigests = numSubDigests;
+
+ if (type == k7zIdSize)
+ {
+ ssi->sdSizes.Data = sd->Data;
+ RINOK(SkipNumbers(sd, numUnpackSizesInData));
+ ssi->sdSizes.Size = sd->Data - ssi->sdSizes.Data;
+ RINOK(ReadID(sd, &type));
+ }
+
+ for (;;)
+ {
+ if (type == k7zIdEnd)
+ return SZ_OK;
+ if (type == k7zIdCRC)
+ {
+ ssi->sdCRCs.Data = sd->Data;
+ RINOK(SkipBitUi32s(sd, numSubDigests));
+ ssi->sdCRCs.Size = sd->Data - ssi->sdCRCs.Data;
+ }
+ else
+ {
+ RINOK(SkipData(sd));
+ }
+ RINOK(ReadID(sd, &type));
+ }
+}
+
+static SRes SzReadStreamsInfo(CSzAr *p,
+ CSzData *sd,
+ UInt32 numFoldersMax, const CBuf *tempBufs, UInt32 numTempBufs,
+ UInt64 *dataOffset,
+ CSubStreamInfo *ssi,
+ ISzAlloc *alloc)
+{
+ UInt64 type;
+
+ SzData_Clear(&ssi->sdSizes);
+ SzData_Clear(&ssi->sdCRCs);
+ SzData_Clear(&ssi->sdNumSubStreams);
+
+ *dataOffset = 0;
+ RINOK(ReadID(sd, &type));
+ if (type == k7zIdPackInfo)
+ {
+ RINOK(ReadNumber(sd, dataOffset));
+ RINOK(ReadPackInfo(p, sd, alloc));
+ RINOK(ReadID(sd, &type));
+ }
+ if (type == k7zIdUnpackInfo)
+ {
+ RINOK(ReadUnpackInfo(p, sd, numFoldersMax, tempBufs, numTempBufs, alloc));
+ RINOK(ReadID(sd, &type));
+ }
+ if (type == k7zIdSubStreamsInfo)
+ {
+ RINOK(ReadSubStreamsInfo(p, sd, ssi));
+ RINOK(ReadID(sd, &type));
+ }
+ else
+ {
+ ssi->NumTotalSubStreams = p->NumFolders;
+ /* ssi->NumSubDigests = 0; */
+ }
+
+ return (type == k7zIdEnd ? SZ_OK : SZ_ERROR_UNSUPPORTED);
+}
+
+static SRes SzReadAndDecodePackedStreams(
+ ILookInStream *inStream,
+ CSzData *sd,
+ CBuf *tempBufs,
+ UInt32 numFoldersMax,
+ UInt64 baseOffset,
+ CSzAr *p,
+ ISzAlloc *allocTemp)
+{
+ UInt64 dataStartPos = 0;
+ UInt32 fo;
+ CSubStreamInfo ssi;
+ UInt32 numFolders;
+
+ RINOK(SzReadStreamsInfo(p, sd, numFoldersMax, NULL, 0, &dataStartPos, &ssi, allocTemp));
+
+ numFolders = p->NumFolders;
+ if (numFolders == 0)
+ return SZ_ERROR_ARCHIVE;
+ else if (numFolders > numFoldersMax)
+ return SZ_ERROR_UNSUPPORTED;
+
+ dataStartPos += baseOffset;
+
+ for (fo = 0; fo < numFolders; fo++)
+ Buf_Init(tempBufs + fo);
+
+ for (fo = 0; fo < numFolders; fo++)
+ {
+ CBuf *tempBuf = tempBufs + fo;
+ UInt64 unpackSize = SzAr_GetFolderUnpackSize(p, fo);
+ if ((size_t)unpackSize != unpackSize)
+ return SZ_ERROR_MEM;
+ if (!Buf_Create(tempBuf, (size_t)unpackSize, allocTemp))
+ return SZ_ERROR_MEM;
+ }
+
+ for (fo = 0; fo < numFolders; fo++)
+ {
+ const CBuf *tempBuf = tempBufs + fo;
+ RINOK(LookInStream_SeekTo(inStream, dataStartPos));
+ RINOK(SzAr_DecodeFolder(p, fo, inStream, dataStartPos, tempBuf->data, tempBuf->size, allocTemp));
+ }
+
+ return SZ_OK;
+}
+
+static SRes SzReadFileNames(const Byte *data, size_t size, UInt32 numFiles, size_t *offsets)
+{
+ size_t pos = 0;
+ *offsets++ = 0;
+ if (numFiles == 0)
+ return (size == 0) ? SZ_OK : SZ_ERROR_ARCHIVE;
+ if (size < 2)
+ return SZ_ERROR_ARCHIVE;
+ if (data[size - 2] != 0 || data[size - 1] != 0)
+ return SZ_ERROR_ARCHIVE;
+ do
+ {
+ const Byte *p;
+ if (pos == size)
+ return SZ_ERROR_ARCHIVE;
+ for (p = data + pos;
+ #ifdef _WIN32
+ *(const UInt16 *)p != 0
+ #else
+ p[0] != 0 || p[1] != 0
+ #endif
+ ; p += 2);
+ pos = p - data + 2;
+ *offsets++ = (pos >> 1);
+ }
+ while (--numFiles);
+ return (pos == size) ? SZ_OK : SZ_ERROR_ARCHIVE;
+}
+
+static MY_NO_INLINE SRes ReadTime(CSzBitUi64s *p, UInt32 num,
+ CSzData *sd2,
+ const CBuf *tempBufs, UInt32 numTempBufs,
+ ISzAlloc *alloc)
+{
+ CSzData sd;
+ UInt32 i;
+ CNtfsFileTime *vals;
+ Byte *defs;
+ Byte external;
+
+ RINOK(ReadBitVector(sd2, num, &p->Defs, alloc));
+
+ SZ_READ_BYTE_SD(sd2, external);
+ if (external == 0)
+ sd = *sd2;
+ else
+ {
+ UInt32 index;
+ RINOK(SzReadNumber32(sd2, &index));
+ if (index >= numTempBufs)
+ return SZ_ERROR_ARCHIVE;
+ sd.Data = tempBufs[index].data;
+ sd.Size = tempBufs[index].size;
+ }
+
+ MY_ALLOC_ZE(CNtfsFileTime, p->Vals, num, alloc);
+ vals = p->Vals;
+ defs = p->Defs;
+ for (i = 0; i < num; i++)
+ if (SzBitArray_Check(defs, i))
+ {
+ if (sd.Size < 8)
+ return SZ_ERROR_ARCHIVE;
+ vals[i].Low = GetUi32(sd.Data);
+ vals[i].High = GetUi32(sd.Data + 4);
+ SKIP_DATA2(sd, 8);
+ }
+ else
+ vals[i].High = vals[i].Low = 0;
+
+ if (external == 0)
+ *sd2 = sd;
+
+ return SZ_OK;
+}
+
+
+#define NUM_ADDITIONAL_STREAMS_MAX 8
+
+
+static SRes SzReadHeader2(
+ CSzArEx *p, /* allocMain */
+ CSzData *sd,
+ ILookInStream *inStream,
+ CBuf *tempBufs, UInt32 *numTempBufs,
+ ISzAlloc *allocMain,
+ ISzAlloc *allocTemp
+ )
+{
+ CSubStreamInfo ssi;
+
+{
+ UInt64 type;
+
+ SzData_Clear(&ssi.sdSizes);
+ SzData_Clear(&ssi.sdCRCs);
+ SzData_Clear(&ssi.sdNumSubStreams);
+
+ ssi.NumSubDigests = 0;
+ ssi.NumTotalSubStreams = 0;
+
+ RINOK(ReadID(sd, &type));
+
+ if (type == k7zIdArchiveProperties)
+ {
+ for (;;)
+ {
+ UInt64 type2;
+ RINOK(ReadID(sd, &type2));
+ if (type2 == k7zIdEnd)
+ break;
+ RINOK(SkipData(sd));
+ }
+ RINOK(ReadID(sd, &type));
+ }
+
+ if (type == k7zIdAdditionalStreamsInfo)
+ {
+ CSzAr tempAr;
+ SRes res;
+
+ SzAr_Init(&tempAr);
+ res = SzReadAndDecodePackedStreams(inStream, sd, tempBufs, NUM_ADDITIONAL_STREAMS_MAX,
+ p->startPosAfterHeader, &tempAr, allocTemp);
+ *numTempBufs = tempAr.NumFolders;
+ SzAr_Free(&tempAr, allocTemp);
+
+ if (res != SZ_OK)
+ return res;
+ RINOK(ReadID(sd, &type));
+ }
+
+ if (type == k7zIdMainStreamsInfo)
+ {
+ RINOK(SzReadStreamsInfo(&p->db, sd, (UInt32)1 << 30, tempBufs, *numTempBufs,
+ &p->dataPos, &ssi, allocMain));
+ p->dataPos += p->startPosAfterHeader;
+ RINOK(ReadID(sd, &type));
+ }
+
+ if (type == k7zIdEnd)
+ {
+ return SZ_OK;
+ }
+
+ if (type != k7zIdFilesInfo)
+ return SZ_ERROR_ARCHIVE;
+}
+
+{
+ UInt32 numFiles = 0;
+ UInt32 numEmptyStreams = 0;
+ const Byte *emptyStreams = NULL;
+ const Byte *emptyFiles = NULL;
+
+ RINOK(SzReadNumber32(sd, &numFiles));
+ p->NumFiles = numFiles;
+
+ for (;;)
+ {
+ UInt64 type;
+ UInt64 size;
+ RINOK(ReadID(sd, &type));
+ if (type == k7zIdEnd)
+ break;
+ RINOK(ReadNumber(sd, &size));
+ if (size > sd->Size)
+ return SZ_ERROR_ARCHIVE;
+
+ if (type >= ((UInt32)1 << 8))
+ {
+ SKIP_DATA(sd, size);
+ }
+ else switch ((unsigned)type)
+ {
+ case k7zIdName:
+ {
+ size_t namesSize;
+ const Byte *namesData;
+ Byte external;
+
+ SZ_READ_BYTE(external);
+ if (external == 0)
+ {
+ namesSize = (size_t)size - 1;
+ namesData = sd->Data;
+ }
+ else
+ {
+ UInt32 index;
+ RINOK(SzReadNumber32(sd, &index));
+ if (index >= *numTempBufs)
+ return SZ_ERROR_ARCHIVE;
+ namesData = (tempBufs)[index].data;
+ namesSize = (tempBufs)[index].size;
+ }
+
+ if ((namesSize & 1) != 0)
+ return SZ_ERROR_ARCHIVE;
+ MY_ALLOC(size_t, p->FileNameOffsets, numFiles + 1, allocMain);
+ MY_ALLOC_ZE_AND_CPY(p->FileNames, namesSize, namesData, allocMain);
+ RINOK(SzReadFileNames(p->FileNames, namesSize, numFiles, p->FileNameOffsets))
+ if (external == 0)
+ {
+ SKIP_DATA(sd, namesSize);
+ }
+ break;
+ }
+ case k7zIdEmptyStream:
+ {
+ RINOK(RememberBitVector(sd, numFiles, &emptyStreams));
+ numEmptyStreams = CountDefinedBits(emptyStreams, numFiles);
+ emptyFiles = NULL;
+ break;
+ }
+ case k7zIdEmptyFile:
+ {
+ RINOK(RememberBitVector(sd, numEmptyStreams, &emptyFiles));
+ break;
+ }
+ case k7zIdWinAttrib:
+ {
+ Byte external;
+ CSzData sdSwitch;
+ CSzData *sdPtr;
+ SzBitUi32s_Free(&p->Attribs, allocMain);
+ RINOK(ReadBitVector(sd, numFiles, &p->Attribs.Defs, allocMain));
+
+ SZ_READ_BYTE(external);
+ if (external == 0)
+ sdPtr = sd;
+ else
+ {
+ UInt32 index;
+ RINOK(SzReadNumber32(sd, &index));
+ if (index >= *numTempBufs)
+ return SZ_ERROR_ARCHIVE;
+ sdSwitch.Data = (tempBufs)[index].data;
+ sdSwitch.Size = (tempBufs)[index].size;
+ sdPtr = &sdSwitch;
+ }
+ RINOK(ReadUi32s(sdPtr, numFiles, &p->Attribs, allocMain));
+ break;
+ }
+ /*
+ case k7zParent:
+ {
+ SzBitUi32s_Free(&p->Parents, allocMain);
+ RINOK(ReadBitVector(sd, numFiles, &p->Parents.Defs, allocMain));
+ RINOK(SzReadSwitch(sd));
+ RINOK(ReadUi32s(sd, numFiles, &p->Parents, allocMain));
+ break;
+ }
+ */
+ case k7zIdMTime: RINOK(ReadTime(&p->MTime, numFiles, sd, tempBufs, *numTempBufs, allocMain)); break;
+ case k7zIdCTime: RINOK(ReadTime(&p->CTime, numFiles, sd, tempBufs, *numTempBufs, allocMain)); break;
+ default:
+ {
+ SKIP_DATA(sd, size);
+ }
+ }
+ }
+
+ if (numFiles - numEmptyStreams != ssi.NumTotalSubStreams)
+ return SZ_ERROR_ARCHIVE;
+
+ for (;;)
+ {
+ UInt64 type;
+ RINOK(ReadID(sd, &type));
+ if (type == k7zIdEnd)
+ break;
+ RINOK(SkipData(sd));
+ }
+
+ {
+ UInt32 i;
+ UInt32 emptyFileIndex = 0;
+ UInt32 folderIndex = 0;
+ UInt32 remSubStreams = 0;
+ UInt32 numSubStreams = 0;
+ UInt64 unpackPos = 0;
+ const Byte *digestsDefs = NULL;
+ const Byte *digestsVals = NULL;
+ UInt32 digestsValsIndex = 0;
+ UInt32 digestIndex;
+ Byte allDigestsDefined = 0;
+ Byte isDirMask = 0;
+ Byte crcMask = 0;
+ Byte mask = 0x80;
+
+ MY_ALLOC(UInt32, p->FolderToFile, p->db.NumFolders + 1, allocMain);
+ MY_ALLOC_ZE(UInt32, p->FileToFolder, p->NumFiles, allocMain);
+ MY_ALLOC(UInt64, p->UnpackPositions, p->NumFiles + 1, allocMain);
+ MY_ALLOC_ZE(Byte, p->IsDirs, (p->NumFiles + 7) >> 3, allocMain);
+
+ RINOK(SzBitUi32s_Alloc(&p->CRCs, p->NumFiles, allocMain));
+
+ if (ssi.sdCRCs.Size != 0)
+ {
+ SZ_READ_BYTE_SD(&ssi.sdCRCs, allDigestsDefined);
+ if (allDigestsDefined)
+ digestsVals = ssi.sdCRCs.Data;
+ else
+ {
+ size_t numBytes = (ssi.NumSubDigests + 7) >> 3;
+ digestsDefs = ssi.sdCRCs.Data;
+ digestsVals = digestsDefs + numBytes;
+ }
+ }
+
+ digestIndex = 0;
+
+ for (i = 0; i < numFiles; i++, mask >>= 1)
+ {
+ if (mask == 0)
+ {
+ UInt32 byteIndex = (i - 1) >> 3;
+ p->IsDirs[byteIndex] = isDirMask;
+ p->CRCs.Defs[byteIndex] = crcMask;
+ isDirMask = 0;
+ crcMask = 0;
+ mask = 0x80;
+ }
+
+ p->UnpackPositions[i] = unpackPos;
+ p->CRCs.Vals[i] = 0;
+
+ if (emptyStreams && SzBitArray_Check(emptyStreams, i))
+ {
+ if (emptyFiles)
+ {
+ if (!SzBitArray_Check(emptyFiles, emptyFileIndex))
+ isDirMask |= mask;
+ emptyFileIndex++;
+ }
+ else
+ isDirMask |= mask;
+ if (remSubStreams == 0)
+ {
+ p->FileToFolder[i] = (UInt32)-1;
+ continue;
+ }
+ }
+
+ if (remSubStreams == 0)
+ {
+ for (;;)
+ {
+ if (folderIndex >= p->db.NumFolders)
+ return SZ_ERROR_ARCHIVE;
+ p->FolderToFile[folderIndex] = i;
+ numSubStreams = 1;
+ if (ssi.sdNumSubStreams.Data)
+ {
+ RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &numSubStreams));
+ }
+ remSubStreams = numSubStreams;
+ if (numSubStreams != 0)
+ break;
+ {
+ UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex);
+ unpackPos += folderUnpackSize;
+ if (unpackPos < folderUnpackSize)
+ return SZ_ERROR_ARCHIVE;
+ }
+
+ folderIndex++;
+ }
+ }
+
+ p->FileToFolder[i] = folderIndex;
+
+ if (emptyStreams && SzBitArray_Check(emptyStreams, i))
+ continue;
+
+ if (--remSubStreams == 0)
+ {
+ UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex);
+ UInt64 startFolderUnpackPos = p->UnpackPositions[p->FolderToFile[folderIndex]];
+ if (folderUnpackSize < unpackPos - startFolderUnpackPos)
+ return SZ_ERROR_ARCHIVE;
+ unpackPos = startFolderUnpackPos + folderUnpackSize;
+ if (unpackPos < folderUnpackSize)
+ return SZ_ERROR_ARCHIVE;
+
+ if (numSubStreams == 1 && SzBitWithVals_Check(&p->db.FolderCRCs, i))
+ {
+ p->CRCs.Vals[i] = p->db.FolderCRCs.Vals[folderIndex];
+ crcMask |= mask;
+ }
+ else if (allDigestsDefined || (digestsDefs && SzBitArray_Check(digestsDefs, digestIndex)))
+ {
+ p->CRCs.Vals[i] = GetUi32(digestsVals + (size_t)digestsValsIndex * 4);
+ digestsValsIndex++;
+ crcMask |= mask;
+ }
+
+ folderIndex++;
+ }
+ else
+ {
+ UInt64 v;
+ RINOK(ReadNumber(&ssi.sdSizes, &v));
+ unpackPos += v;
+ if (unpackPos < v)
+ return SZ_ERROR_ARCHIVE;
+ if (allDigestsDefined || (digestsDefs && SzBitArray_Check(digestsDefs, digestIndex)))
+ {
+ p->CRCs.Vals[i] = GetUi32(digestsVals + (size_t)digestsValsIndex * 4);
+ digestsValsIndex++;
+ crcMask |= mask;
+ }
+ }
+ }
+
+ if (mask != 0x80)
+ {
+ UInt32 byteIndex = (i - 1) >> 3;
+ p->IsDirs[byteIndex] = isDirMask;
+ p->CRCs.Defs[byteIndex] = crcMask;
+ }
+
+ p->UnpackPositions[i] = unpackPos;
+
+ if (remSubStreams != 0)
+ return SZ_ERROR_ARCHIVE;
+
+ for (;;)
+ {
+ p->FolderToFile[folderIndex] = i;
+ if (folderIndex >= p->db.NumFolders)
+ break;
+ if (!ssi.sdNumSubStreams.Data)
+ return SZ_ERROR_ARCHIVE;
+ RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &numSubStreams));
+ if (numSubStreams != 0)
+ return SZ_ERROR_ARCHIVE;
+ /*
+ {
+ UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex);
+ unpackPos += folderUnpackSize;
+ if (unpackPos < folderUnpackSize)
+ return SZ_ERROR_ARCHIVE;
+ }
+ */
+ folderIndex++;
+ }
+
+ if (ssi.sdNumSubStreams.Data && ssi.sdNumSubStreams.Size != 0)
+ return SZ_ERROR_ARCHIVE;
+ }
+}
+ return SZ_OK;
+}
+
+
+static SRes SzReadHeader(
+ CSzArEx *p,
+ CSzData *sd,
+ ILookInStream *inStream,
+ ISzAlloc *allocMain,
+ ISzAlloc *allocTemp)
+{
+ UInt32 i;
+ UInt32 numTempBufs = 0;
+ SRes res;
+ CBuf tempBufs[NUM_ADDITIONAL_STREAMS_MAX];
+
+ for (i = 0; i < NUM_ADDITIONAL_STREAMS_MAX; i++)
+ Buf_Init(tempBufs + i);
+
+ res = SzReadHeader2(p, sd, inStream,
+ tempBufs, &numTempBufs,
+ allocMain, allocTemp);
+
+ for (i = 0; i < NUM_ADDITIONAL_STREAMS_MAX; i++)
+ Buf_Free(tempBufs + i, allocTemp);
+
+ RINOK(res);
+
+ if (sd->Size != 0)
+ return SZ_ERROR_FAIL;
+
+ return res;
+}
+
+static SRes SzArEx_Open2(
+ CSzArEx *p,
+ ILookInStream *inStream,
+ ISzAlloc *allocMain,
+ ISzAlloc *allocTemp)
+{
+ Byte header[k7zStartHeaderSize];
+ Int64 startArcPos;
+ UInt64 nextHeaderOffset, nextHeaderSize;
+ size_t nextHeaderSizeT;
+ UInt32 nextHeaderCRC;
+ CBuf buf;
+ SRes res;
+
+ startArcPos = 0;
+ RINOK(inStream->Seek(inStream, &startArcPos, SZ_SEEK_CUR));
+
+ RINOK(LookInStream_Read2(inStream, header, k7zStartHeaderSize, SZ_ERROR_NO_ARCHIVE));
+
+ if (!TestSignatureCandidate(header))
+ return SZ_ERROR_NO_ARCHIVE;
+ if (header[6] != k7zMajorVersion)
+ return SZ_ERROR_UNSUPPORTED;
+
+ nextHeaderOffset = GetUi64(header + 12);
+ nextHeaderSize = GetUi64(header + 20);
+ nextHeaderCRC = GetUi32(header + 28);
+
+ p->startPosAfterHeader = startArcPos + k7zStartHeaderSize;
+
+ if (CrcCalc(header + 12, 20) != GetUi32(header + 8))
+ return SZ_ERROR_CRC;
+
+ nextHeaderSizeT = (size_t)nextHeaderSize;
+ if (nextHeaderSizeT != nextHeaderSize)
+ return SZ_ERROR_MEM;
+ if (nextHeaderSizeT == 0)
+ return SZ_OK;
+ if (nextHeaderOffset > nextHeaderOffset + nextHeaderSize ||
+ nextHeaderOffset > nextHeaderOffset + nextHeaderSize + k7zStartHeaderSize)
+ return SZ_ERROR_NO_ARCHIVE;
+
+ {
+ Int64 pos = 0;
+ RINOK(inStream->Seek(inStream, &pos, SZ_SEEK_END));
+ if ((UInt64)pos < startArcPos + nextHeaderOffset ||
+ (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset ||
+ (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset + nextHeaderSize)
+ return SZ_ERROR_INPUT_EOF;
+ }
+
+ RINOK(LookInStream_SeekTo(inStream, startArcPos + k7zStartHeaderSize + nextHeaderOffset));
+
+ if (!Buf_Create(&buf, nextHeaderSizeT, allocTemp))
+ return SZ_ERROR_MEM;
+
+ res = LookInStream_Read(inStream, buf.data, nextHeaderSizeT);
+
+ if (res == SZ_OK)
+ {
+ res = SZ_ERROR_ARCHIVE;
+ if (CrcCalc(buf.data, nextHeaderSizeT) == nextHeaderCRC)
+ {
+ CSzData sd;
+ UInt64 type;
+ sd.Data = buf.data;
+ sd.Size = buf.size;
+
+ res = ReadID(&sd, &type);
+
+ if (res == SZ_OK && type == k7zIdEncodedHeader)
+ {
+ CSzAr tempAr;
+ CBuf tempBuf;
+ Buf_Init(&tempBuf);
+
+ SzAr_Init(&tempAr);
+ res = SzReadAndDecodePackedStreams(inStream, &sd, &tempBuf, 1, p->startPosAfterHeader, &tempAr, allocTemp);
+ SzAr_Free(&tempAr, allocTemp);
+
+ if (res != SZ_OK)
+ {
+ Buf_Free(&tempBuf, allocTemp);
+ }
+ else
+ {
+ Buf_Free(&buf, allocTemp);
+ buf.data = tempBuf.data;
+ buf.size = tempBuf.size;
+ sd.Data = buf.data;
+ sd.Size = buf.size;
+ res = ReadID(&sd, &type);
+ }
+ }
+
+ if (res == SZ_OK)
+ {
+ if (type == k7zIdHeader)
+ {
+ /*
+ CSzData sd2;
+ unsigned ttt;
+ for (ttt = 0; ttt < 40000; ttt++)
+ {
+ SzArEx_Free(p, allocMain);
+ sd2 = sd;
+ res = SzReadHeader(p, &sd2, inStream, allocMain, allocTemp);
+ if (res != SZ_OK)
+ break;
+ }
+ */
+ res = SzReadHeader(p, &sd, inStream, allocMain, allocTemp);
+ }
+ else
+ res = SZ_ERROR_UNSUPPORTED;
+ }
+ }
+ }
+
+ Buf_Free(&buf, allocTemp);
+ return res;
+}
+
+
+static SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream,
+ ISzAlloc *allocMain, ISzAlloc *allocTemp)
+{
+ SRes res = SzArEx_Open2(p, inStream, allocMain, allocTemp);
+ if (res != SZ_OK)
+ SzArEx_Free(p, allocMain);
+ return res;
+}
+
+
+static SRes SzArEx_Extract(
+ const CSzArEx *p,
+ ILookInStream *inStream,
+ UInt32 fileIndex,
+ UInt32 *blockIndex,
+ Byte **tempBuf,
+ size_t *outBufferSize,
+ size_t *offset,
+ size_t *outSizeProcessed,
+ ISzAlloc *allocMain,
+ ISzAlloc *allocTemp)
+{
+ UInt32 folderIndex = p->FileToFolder[fileIndex];
+ SRes res = SZ_OK;
+
+ *offset = 0;
+ *outSizeProcessed = 0;
+
+ if (folderIndex == (UInt32)-1)
+ {
+ IAlloc_Free(allocMain, *tempBuf);
+ *blockIndex = folderIndex;
+ *tempBuf = NULL;
+ *outBufferSize = 0;
+ return SZ_OK;
+ }
+
+ if (*tempBuf == NULL || *blockIndex != folderIndex)
+ {
+ UInt64 unpackSizeSpec = SzAr_GetFolderUnpackSize(&p->db, folderIndex);
+ /*
+ UInt64 unpackSizeSpec =
+ p->UnpackPositions[p->FolderToFile[folderIndex + 1]] -
+ p->UnpackPositions[p->FolderToFile[folderIndex]];
+ */
+ size_t unpackSize = (size_t)unpackSizeSpec;
+
+ if (unpackSize != unpackSizeSpec)
+ return SZ_ERROR_MEM;
+ *blockIndex = folderIndex;
+ IAlloc_Free(allocMain, *tempBuf);
+ *tempBuf = NULL;
+
+ if (res == SZ_OK)
+ {
+ *outBufferSize = unpackSize;
+ if (unpackSize != 0)
+ {
+ *tempBuf = (Byte *)IAlloc_Alloc(allocMain, unpackSize);
+ if (*tempBuf == NULL)
+ res = SZ_ERROR_MEM;
+ }
+
+ if (res == SZ_OK)
+ {
+ res = SzAr_DecodeFolder(&p->db, folderIndex,
+ inStream, p->dataPos, *tempBuf, unpackSize, allocTemp);
+ }
+ }
+ }
+
+ if (res == SZ_OK)
+ {
+ UInt64 unpackPos = p->UnpackPositions[fileIndex];
+ *offset = (size_t)(unpackPos - p->UnpackPositions[p->FolderToFile[folderIndex]]);
+ *outSizeProcessed = (size_t)(p->UnpackPositions[fileIndex + 1] - unpackPos);
+ if (*offset + *outSizeProcessed > *outBufferSize)
+ return SZ_ERROR_FAIL;
+ if (SzBitWithVals_Check(&p->CRCs, fileIndex))
+ if (CrcCalc(*tempBuf + *offset, *outSizeProcessed) != p->CRCs.Vals[fileIndex])
+ res = SZ_ERROR_CRC;
+ }
+
+ return res;
+}
+
+
+static size_t SzArEx_GetFileNameUtf16(const CSzArEx *p, size_t fileIndex, UInt16 *dest)
+{
+ size_t offs = p->FileNameOffsets[fileIndex];
+ size_t len = p->FileNameOffsets[fileIndex + 1] - offs;
+ if (dest != 0)
+ {
+ size_t i;
+ const Byte *src = p->FileNames + offs * 2;
+ for (i = 0; i < len; i++)
+ dest[i] = GetUi16(src + i * 2);
+ }
+ return len;
+}
+
+/*
+static size_t SzArEx_GetFullNameLen(const CSzArEx *p, size_t fileIndex)
+{
+ size_t len;
+ if (!p->FileNameOffsets)
+ return 1;
+ len = 0;
+ for (;;)
+ {
+ UInt32 parent = (UInt32)(Int32)-1;
+ len += p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex];
+ if SzBitWithVals_Check(&p->Parents, fileIndex)
+ parent = p->Parents.Vals[fileIndex];
+ if (parent == (UInt32)(Int32)-1)
+ return len;
+ fileIndex = parent;
+ }
+}
+
+static UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 *dest)
+{
+ Bool needSlash;
+ if (!p->FileNameOffsets)
+ {
+ *(--dest) = 0;
+ return dest;
+ }
+ needSlash = False;
+ for (;;)
+ {
+ UInt32 parent = (UInt32)(Int32)-1;
+ size_t curLen = p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex];
+ SzArEx_GetFileNameUtf16(p, fileIndex, dest - curLen);
+ if (needSlash)
+ *(dest - 1) = '/';
+ needSlash = True;
+ dest -= curLen;
+
+ if SzBitWithVals_Check(&p->Parents, fileIndex)
+ parent = p->Parents.Vals[fileIndex];
+ if (parent == (UInt32)(Int32)-1)
+ return dest;
+ fileIndex = parent;
+ }
+}
+*/
+
+/* 7zBuf.c -- Byte Buffer
+2013-01-21 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "7zBuf.h"
+*/
+
+static void Buf_Init(CBuf *p)
+{
+ p->data = 0;
+ p->size = 0;
+}
+
+static int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc)
+{
+ p->size = 0;
+ if (size == 0)
+ {
+ p->data = 0;
+ return 1;
+ }
+ p->data = (Byte *)alloc->Alloc(alloc, size);
+ if (p->data != 0)
+ {
+ p->size = size;
+ return 1;
+ }
+ return 0;
+}
+
+static void Buf_Free(CBuf *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->data);
+ p->data = 0;
+ p->size = 0;
+}
+
+/* 7zDec.c -- Decoding from 7z folder
+2015-11-18 : Igor Pavlov : Public domain */
+
+/* #define _7ZIP_PPMD_SUPPPORT */
+
+/*
+#include "Precomp.h"
+
+#include
+
+#include "7z.h"
+#include "7zCrc.h"
+
+#include "Bcj2.h"
+#include "Bra.h"
+#include "CpuArch.h"
+#include "Delta.h"
+#include "LzmaDec.h"
+#include "Lzma2Dec.h"
+#ifdef _7ZIP_PPMD_SUPPPORT
+#include "Ppmd7.h"
+#endif
+*/
+
+#define k_Copy 0
+#define k_Delta 3
+#define k_LZMA2 0x21
+#define k_LZMA 0x30101
+#define k_BCJ 0x3030103
+#define k_BCJ2 0x303011B
+#define k_PPC 0x3030205
+#define k_IA64 0x3030401
+#define k_ARM 0x3030501
+#define k_ARMT 0x3030701
+#define k_SPARC 0x3030805
+
+
+#ifdef _7ZIP_PPMD_SUPPPORT
+
+#define k_PPMD 0x30401
+
+typedef struct
+{
+ IByteIn p;
+ const Byte *cur;
+ const Byte *end;
+ const Byte *begin;
+ UInt64 processed;
+ Bool extra;
+ SRes res;
+ ILookInStream *inStream;
+} CByteInToLook;
+
+static Byte ReadByte(void *pp)
+{
+ CByteInToLook *p = (CByteInToLook *)pp;
+ if (p->cur != p->end)
+ return *p->cur++;
+ if (p->res == SZ_OK)
+ {
+ size_t size = p->cur - p->begin;
+ p->processed += size;
+ p->res = p->inStream->Skip(p->inStream, size);
+ size = (1 << 25);
+ p->res = p->inStream->Look(p->inStream, (const void **)&p->begin, &size);
+ p->cur = p->begin;
+ p->end = p->begin + size;
+ if (size != 0)
+ return *p->cur++;;
+ }
+ p->extra = True;
+ return 0;
+}
+
+static SRes SzDecodePpmd(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream,
+ Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain)
+{
+ CPpmd7 ppmd;
+ CByteInToLook s;
+ SRes res = SZ_OK;
+
+ s.p.Read = ReadByte;
+ s.inStream = inStream;
+ s.begin = s.end = s.cur = NULL;
+ s.extra = False;
+ s.res = SZ_OK;
+ s.processed = 0;
+
+ if (propsSize != 5)
+ return SZ_ERROR_UNSUPPORTED;
+
+ {
+ unsigned order = props[0];
+ UInt32 memSize = GetUi32(props + 1);
+ if (order < PPMD7_MIN_ORDER ||
+ order > PPMD7_MAX_ORDER ||
+ memSize < PPMD7_MIN_MEM_SIZE ||
+ memSize > PPMD7_MAX_MEM_SIZE)
+ return SZ_ERROR_UNSUPPORTED;
+ Ppmd7_Construct(&ppmd);
+ if (!Ppmd7_Alloc(&ppmd, memSize, allocMain))
+ return SZ_ERROR_MEM;
+ Ppmd7_Init(&ppmd, order);
+ }
+ {
+ CPpmd7z_RangeDec rc;
+ Ppmd7z_RangeDec_CreateVTable(&rc);
+ rc.Stream = &s.p;
+ if (!Ppmd7z_RangeDec_Init(&rc))
+ res = SZ_ERROR_DATA;
+ else if (s.extra)
+ res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
+ else
+ {
+ SizeT i;
+ for (i = 0; i < outSize; i++)
+ {
+ int sym = Ppmd7_DecodeSymbol(&ppmd, &rc.p);
+ if (s.extra || sym < 0)
+ break;
+ outBuffer[i] = (Byte)sym;
+ }
+ if (i != outSize)
+ res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
+ else if (s.processed + (s.cur - s.begin) != inSize || !Ppmd7z_RangeDec_IsFinishedOK(&rc))
+ res = SZ_ERROR_DATA;
+ }
+ }
+ Ppmd7_Free(&ppmd, allocMain);
+ return res;
+}
+
+#endif
+
+
+static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream,
+ Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain)
+{
+ CLzmaDec state;
+ SRes res = SZ_OK;
+
+ LzmaDec_Construct(&state);
+ RINOK(LzmaDec_AllocateProbs(&state, props, propsSize, allocMain));
+ state.dic = outBuffer;
+ state.dicBufSize = outSize;
+ LzmaDec_Init(&state);
+
+ for (;;)
+ {
+ const void *inBuf = NULL;
+ size_t lookahead = (1 << 18);
+ if (lookahead > inSize)
+ lookahead = (size_t)inSize;
+ res = inStream->Look(inStream, &inBuf, &lookahead);
+ if (res != SZ_OK)
+ break;
+
+ {
+ SizeT inProcessed = (SizeT)lookahead, dicPos = state.dicPos;
+ ELzmaStatus status;
+ res = LzmaDec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status);
+ lookahead -= inProcessed;
+ inSize -= inProcessed;
+ if (res != SZ_OK)
+ break;
+
+ if (status == LZMA_STATUS_FINISHED_WITH_MARK)
+ {
+ if (outSize != state.dicPos || inSize != 0)
+ res = SZ_ERROR_DATA;
+ break;
+ }
+
+ if (outSize == state.dicPos && inSize == 0 && status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)
+ break;
+
+ if (inProcessed == 0 && dicPos == state.dicPos)
+ {
+ res = SZ_ERROR_DATA;
+ break;
+ }
+
+ res = inStream->Skip((void *)inStream, inProcessed);
+ if (res != SZ_OK)
+ break;
+ }
+ }
+
+ LzmaDec_FreeProbs(&state, allocMain);
+ return res;
+}
+
+
+#ifndef _7Z_NO_METHOD_LZMA2
+
+static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream,
+ Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain)
+{
+ CLzma2Dec state;
+ SRes res = SZ_OK;
+
+ Lzma2Dec_Construct(&state);
+ if (propsSize != 1)
+ return SZ_ERROR_DATA;
+ RINOK(Lzma2Dec_AllocateProbs(&state, props[0], allocMain));
+ state.decoder.dic = outBuffer;
+ state.decoder.dicBufSize = outSize;
+ Lzma2Dec_Init(&state);
+
+ for (;;)
+ {
+ const void *inBuf = NULL;
+ size_t lookahead = (1 << 18);
+ if (lookahead > inSize)
+ lookahead = (size_t)inSize;
+ res = inStream->Look(inStream, &inBuf, &lookahead);
+ if (res != SZ_OK)
+ break;
+
+ {
+ SizeT inProcessed = (SizeT)lookahead, dicPos = state.decoder.dicPos;
+ ELzmaStatus status;
+ res = Lzma2Dec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status);
+ lookahead -= inProcessed;
+ inSize -= inProcessed;
+ if (res != SZ_OK)
+ break;
+
+ if (status == LZMA_STATUS_FINISHED_WITH_MARK)
+ {
+ if (outSize != state.decoder.dicPos || inSize != 0)
+ res = SZ_ERROR_DATA;
+ break;
+ }
+
+ if (inProcessed == 0 && dicPos == state.decoder.dicPos)
+ {
+ res = SZ_ERROR_DATA;
+ break;
+ }
+
+ res = inStream->Skip((void *)inStream, inProcessed);
+ if (res != SZ_OK)
+ break;
+ }
+ }
+
+ Lzma2Dec_FreeProbs(&state, allocMain);
+ return res;
+}
+
+#endif
+
+
+static SRes SzDecodeCopy(UInt64 inSize, ILookInStream *inStream, Byte *outBuffer)
+{
+ while (inSize > 0)
+ {
+ const void *inBuf;
+ size_t curSize = (1 << 18);
+ if (curSize > inSize)
+ curSize = (size_t)inSize;
+ RINOK(inStream->Look(inStream, &inBuf, &curSize));
+ if (curSize == 0)
+ return SZ_ERROR_INPUT_EOF;
+ memcpy(outBuffer, inBuf, curSize);
+ outBuffer += curSize;
+ inSize -= curSize;
+ RINOK(inStream->Skip((void *)inStream, curSize));
+ }
+ return SZ_OK;
+}
+
+static Bool IS_MAIN_METHOD(UInt32 m)
+{
+ switch (m)
+ {
+ case k_Copy:
+ case k_LZMA:
+ #ifndef _7Z_NO_METHOD_LZMA2
+ case k_LZMA2:
+ #endif
+ #ifdef _7ZIP_PPMD_SUPPPORT
+ case k_PPMD:
+ #endif
+ return True;
+ }
+ return False;
+}
+
+static Bool IS_SUPPORTED_CODER(const CSzCoderInfo *c)
+{
+ return
+ c->NumStreams == 1
+ /* && c->MethodID <= (UInt32)0xFFFFFFFF */
+ && IS_MAIN_METHOD((UInt32)c->MethodID);
+}
+
+#define IS_BCJ2(c) ((c)->MethodID == k_BCJ2 && (c)->NumStreams == 4)
+
+static SRes CheckSupportedFolder(const CSzFolder *f)
+{
+ if (f->NumCoders < 1 || f->NumCoders > 4)
+ return SZ_ERROR_UNSUPPORTED;
+ if (!IS_SUPPORTED_CODER(&f->Coders[0]))
+ return SZ_ERROR_UNSUPPORTED;
+ if (f->NumCoders == 1)
+ {
+ if (f->NumPackStreams != 1 || f->PackStreams[0] != 0 || f->NumBonds != 0)
+ return SZ_ERROR_UNSUPPORTED;
+ return SZ_OK;
+ }
+
+
+ #ifndef _7Z_NO_METHODS_FILTERS
+
+ if (f->NumCoders == 2)
+ {
+ const CSzCoderInfo *c = &f->Coders[1];
+ if (
+ /* c->MethodID > (UInt32)0xFFFFFFFF || */
+ c->NumStreams != 1
+ || f->NumPackStreams != 1
+ || f->PackStreams[0] != 0
+ || f->NumBonds != 1
+ || f->Bonds[0].InIndex != 1
+ || f->Bonds[0].OutIndex != 0)
+ return SZ_ERROR_UNSUPPORTED;
+ switch ((UInt32)c->MethodID)
+ {
+ case k_Delta:
+ case k_BCJ:
+ case k_PPC:
+ case k_IA64:
+ case k_SPARC:
+ case k_ARM:
+ case k_ARMT:
+ break;
+ default:
+ return SZ_ERROR_UNSUPPORTED;
+ }
+ return SZ_OK;
+ }
+
+ #endif
+
+
+ if (f->NumCoders == 4)
+ {
+ if (!IS_SUPPORTED_CODER(&f->Coders[1])
+ || !IS_SUPPORTED_CODER(&f->Coders[2])
+ || !IS_BCJ2(&f->Coders[3]))
+ return SZ_ERROR_UNSUPPORTED;
+ if (f->NumPackStreams != 4
+ || f->PackStreams[0] != 2
+ || f->PackStreams[1] != 6
+ || f->PackStreams[2] != 1
+ || f->PackStreams[3] != 0
+ || f->NumBonds != 3
+ || f->Bonds[0].InIndex != 5 || f->Bonds[0].OutIndex != 0
+ || f->Bonds[1].InIndex != 4 || f->Bonds[1].OutIndex != 1
+ || f->Bonds[2].InIndex != 3 || f->Bonds[2].OutIndex != 2)
+ return SZ_ERROR_UNSUPPORTED;
+ return SZ_OK;
+ }
+
+ return SZ_ERROR_UNSUPPORTED;
+}
+
+#define CASE_BRA_CONV(isa) case k_ ## isa: isa ## _Convert(outBuffer, outSize, 0, 0); break;
+
+static SRes SzFolder_Decode2(const CSzFolder *folder,
+ const Byte *propsData,
+ const UInt64 *unpackSizes,
+ const UInt64 *packPositions,
+ ILookInStream *inStream, UInt64 startPos,
+ Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain,
+ Byte *tempBuf[])
+{
+ UInt32 ci;
+ SizeT tempSizes[3] = { 0, 0, 0};
+ SizeT tempSize3 = 0;
+ Byte *tempBuf3 = 0;
+
+ RINOK(CheckSupportedFolder(folder));
+
+ for (ci = 0; ci < folder->NumCoders; ci++)
+ {
+ const CSzCoderInfo *coder = &folder->Coders[ci];
+
+ if (IS_MAIN_METHOD((UInt32)coder->MethodID))
+ {
+ UInt32 si = 0;
+ UInt64 offset;
+ UInt64 inSize;
+ Byte *outBufCur = outBuffer;
+ SizeT outSizeCur = outSize;
+ if (folder->NumCoders == 4)
+ {
+ UInt32 indices[] = { 3, 2, 0 };
+ UInt64 unpackSize = unpackSizes[ci];
+ si = indices[ci];
+ if (ci < 2)
+ {
+ Byte *temp;
+ outSizeCur = (SizeT)unpackSize;
+ if (outSizeCur != unpackSize)
+ return SZ_ERROR_MEM;
+ temp = (Byte *)IAlloc_Alloc(allocMain, outSizeCur);
+ if (!temp && outSizeCur != 0)
+ return SZ_ERROR_MEM;
+ outBufCur = tempBuf[1 - ci] = temp;
+ tempSizes[1 - ci] = outSizeCur;
+ }
+ else if (ci == 2)
+ {
+ if (unpackSize > outSize) /* check it */
+ return SZ_ERROR_PARAM;
+ tempBuf3 = outBufCur = outBuffer + (outSize - (size_t)unpackSize);
+ tempSize3 = outSizeCur = (SizeT)unpackSize;
+ }
+ else
+ return SZ_ERROR_UNSUPPORTED;
+ }
+ offset = packPositions[si];
+ inSize = packPositions[si + 1] - offset;
+ RINOK(LookInStream_SeekTo(inStream, startPos + offset));
+
+ if (coder->MethodID == k_Copy)
+ {
+ if (inSize != outSizeCur) /* check it */
+ return SZ_ERROR_DATA;
+ RINOK(SzDecodeCopy(inSize, inStream, outBufCur));
+ }
+ else if (coder->MethodID == k_LZMA)
+ {
+ RINOK(SzDecodeLzma(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain));
+ }
+ #ifndef _7Z_NO_METHOD_LZMA2
+ else if (coder->MethodID == k_LZMA2)
+ {
+ RINOK(SzDecodeLzma2(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain));
+ }
+ #endif
+ #ifdef _7ZIP_PPMD_SUPPPORT
+ else if (coder->MethodID == k_PPMD)
+ {
+ RINOK(SzDecodePpmd(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain));
+ }
+ #endif
+ else
+ return SZ_ERROR_UNSUPPORTED;
+ }
+ else if (coder->MethodID == k_BCJ2)
+ {
+ UInt64 offset = packPositions[1];
+ UInt64 s3Size = packPositions[2] - offset;
+
+ if (ci != 3)
+ return SZ_ERROR_UNSUPPORTED;
+
+ tempSizes[2] = (SizeT)s3Size;
+ if (tempSizes[2] != s3Size)
+ return SZ_ERROR_MEM;
+ tempBuf[2] = (Byte *)IAlloc_Alloc(allocMain, tempSizes[2]);
+ if (!tempBuf[2] && tempSizes[2] != 0)
+ return SZ_ERROR_MEM;
+
+ RINOK(LookInStream_SeekTo(inStream, startPos + offset));
+ RINOK(SzDecodeCopy(s3Size, inStream, tempBuf[2]));
+
+ if ((tempSizes[0] & 3) != 0 ||
+ (tempSizes[1] & 3) != 0 ||
+ tempSize3 + tempSizes[0] + tempSizes[1] != outSize)
+ return SZ_ERROR_DATA;
+
+ {
+ CBcj2Dec p;
+
+ p.bufs[0] = tempBuf3; p.lims[0] = tempBuf3 + tempSize3;
+ p.bufs[1] = tempBuf[0]; p.lims[1] = tempBuf[0] + tempSizes[0];
+ p.bufs[2] = tempBuf[1]; p.lims[2] = tempBuf[1] + tempSizes[1];
+ p.bufs[3] = tempBuf[2]; p.lims[3] = tempBuf[2] + tempSizes[2];
+
+ p.dest = outBuffer;
+ p.destLim = outBuffer + outSize;
+
+ Bcj2Dec_Init(&p);
+ RINOK(Bcj2Dec_Decode(&p));
+
+ {
+ unsigned i;
+ for (i = 0; i < 4; i++)
+ if (p.bufs[i] != p.lims[i])
+ return SZ_ERROR_DATA;
+
+ if (!Bcj2Dec_IsFinished(&p))
+ return SZ_ERROR_DATA;
+
+ if (p.dest != p.destLim
+ || p.state != BCJ2_STREAM_MAIN)
+ return SZ_ERROR_DATA;
+ }
+ }
+ }
+ #ifndef _7Z_NO_METHODS_FILTERS
+ else if (ci == 1)
+ {
+ if (coder->MethodID == k_Delta)
+ {
+ if (coder->PropsSize != 1)
+ return SZ_ERROR_UNSUPPORTED;
+ {
+ Byte state[DELTA_STATE_SIZE];
+ Delta_Init(state);
+ Delta_Decode(state, (unsigned)(propsData[coder->PropsOffset]) + 1, outBuffer, outSize);
+ }
+ }
+ else
+ {
+ if (coder->PropsSize != 0)
+ return SZ_ERROR_UNSUPPORTED;
+ switch (coder->MethodID)
+ {
+ case k_BCJ:
+ {
+ UInt32 state;
+ x86_Convert_Init(state);
+ x86_Convert(outBuffer, outSize, 0, &state, 0);
+ break;
+ }
+ CASE_BRA_CONV(PPC)
+ CASE_BRA_CONV(IA64)
+ CASE_BRA_CONV(SPARC)
+ CASE_BRA_CONV(ARM)
+ CASE_BRA_CONV(ARMT)
+ default:
+ return SZ_ERROR_UNSUPPORTED;
+ }
+ }
+ }
+ #endif
+ else
+ return SZ_ERROR_UNSUPPORTED;
+ }
+
+ return SZ_OK;
+}
+
+
+static SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex,
+ ILookInStream *inStream, UInt64 startPos,
+ Byte *outBuffer, size_t outSize,
+ ISzAlloc *allocMain)
+{
+ SRes res;
+ CSzFolder folder;
+ CSzData sd;
+
+ const Byte *data = p->CodersData + p->FoCodersOffsets[folderIndex];
+ sd.Data = data;
+ sd.Size = p->FoCodersOffsets[folderIndex + 1] - p->FoCodersOffsets[folderIndex];
+
+ res = SzGetNextFolderItem(&folder, &sd);
+
+ if (res != SZ_OK)
+ return res;
+
+ if (sd.Size != 0
+ || folder.UnpackStream != p->FoToMainUnpackSizeIndex[folderIndex]
+ || outSize != SzAr_GetFolderUnpackSize(p, folderIndex))
+ return SZ_ERROR_FAIL;
+ {
+ unsigned i;
+ Byte *tempBuf[3] = { 0, 0, 0};
+
+ res = SzFolder_Decode2(&folder, data,
+ &p->CoderUnpackSizes[p->FoToCoderUnpackSizes[folderIndex]],
+ p->PackPositions + p->FoStartPackStreamIndex[folderIndex],
+ inStream, startPos,
+ outBuffer, (SizeT)outSize, allocMain, tempBuf);
+
+ for (i = 0; i < 3; i++)
+ IAlloc_Free(allocMain, tempBuf[i]);
+
+ if (res == SZ_OK)
+ if (SzBitWithVals_Check(&p->FolderCRCs, folderIndex))
+ if (CrcCalc(outBuffer, outSize) != p->FolderCRCs.Vals[folderIndex])
+ res = SZ_ERROR_CRC;
+
+ return res;
+ }
+}
+
+/* Bcj2.c -- BCJ2 Decoder (Converter for x86 code)
+2015-08-01 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "Bcj2.h"
+#include "CpuArch.h"
+*/
+
+#define CProb UInt16
+
+#define kTopValue ((UInt32)1 << 24)
+#define kNumModelBits 11
+#define kBitModelTotal (1 << kNumModelBits)
+#define kNumMoveBits 5
+
+#define _IF_BIT_0 ttt = *prob; bound = (p->range >> kNumModelBits) * ttt; if (p->code < bound)
+#define _UPDATE_0 p->range = bound; *prob = (CProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits));
+#define _UPDATE_1 p->range -= bound; p->code -= bound; *prob = (CProb)(ttt - (ttt >> kNumMoveBits));
+
+static void Bcj2Dec_Init(CBcj2Dec *p)
+{
+ unsigned i;
+
+ p->state = BCJ2_DEC_STATE_OK;
+ p->ip = 0;
+ p->temp[3] = 0;
+ p->range = 0;
+ p->code = 0;
+ for (i = 0; i < sizeof(p->probs) / sizeof(p->probs[0]); i++)
+ p->probs[i] = kBitModelTotal >> 1;
+}
+
+static SRes Bcj2Dec_Decode(CBcj2Dec *p)
+{
+ if (p->range <= 5)
+ {
+ p->state = BCJ2_DEC_STATE_OK;
+ for (; p->range != 5; p->range++)
+ {
+ if (p->range == 1 && p->code != 0)
+ return SZ_ERROR_DATA;
+
+ if (p->bufs[BCJ2_STREAM_RC] == p->lims[BCJ2_STREAM_RC])
+ {
+ p->state = BCJ2_STREAM_RC;
+ return SZ_OK;
+ }
+
+ p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++;
+ }
+
+ if (p->code == 0xFFFFFFFF)
+ return SZ_ERROR_DATA;
+
+ p->range = 0xFFFFFFFF;
+ }
+ else if (p->state >= BCJ2_DEC_STATE_ORIG_0)
+ {
+ while (p->state <= BCJ2_DEC_STATE_ORIG_3)
+ {
+ Byte *dest = p->dest;
+ if (dest == p->destLim)
+ return SZ_OK;
+ *dest = p->temp[p->state++ - BCJ2_DEC_STATE_ORIG_0];
+ p->dest = dest + 1;
+ }
+ }
+
+ /*
+ if (BCJ2_IS_32BIT_STREAM(p->state))
+ {
+ const Byte *cur = p->bufs[p->state];
+ if (cur == p->lims[p->state])
+ return SZ_OK;
+ p->bufs[p->state] = cur + 4;
+
+ {
+ UInt32 val;
+ Byte *dest;
+ SizeT rem;
+
+ p->ip += 4;
+ val = GetBe32(cur) - p->ip;
+ dest = p->dest;
+ rem = p->destLim - dest;
+ if (rem < 4)
+ {
+ SizeT i;
+ SetUi32(p->temp, val);
+ for (i = 0; i < rem; i++)
+ dest[i] = p->temp[i];
+ p->dest = dest + rem;
+ p->state = BCJ2_DEC_STATE_ORIG_0 + (unsigned)rem;
+ return SZ_OK;
+ }
+ SetUi32(dest, val);
+ p->temp[3] = (Byte)(val >> 24);
+ p->dest = dest + 4;
+ p->state = BCJ2_DEC_STATE_OK;
+ }
+ }
+ */
+
+ for (;;)
+ {
+ if (BCJ2_IS_32BIT_STREAM(p->state))
+ p->state = BCJ2_DEC_STATE_OK;
+ else
+ {
+ if (p->range < kTopValue)
+ {
+ if (p->bufs[BCJ2_STREAM_RC] == p->lims[BCJ2_STREAM_RC])
+ {
+ p->state = BCJ2_STREAM_RC;
+ return SZ_OK;
+ }
+ p->range <<= 8;
+ p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++;
+ }
+
+ {
+ const Byte *src = p->bufs[BCJ2_STREAM_MAIN];
+ const Byte *srcLim;
+ Byte *dest;
+ SizeT num = p->lims[BCJ2_STREAM_MAIN] - src;
+
+ if (num == 0)
+ {
+ p->state = BCJ2_STREAM_MAIN;
+ return SZ_OK;
+ }
+
+ dest = p->dest;
+ if (num > (SizeT)(p->destLim - dest))
+ {
+ num = p->destLim - dest;
+ if (num == 0)
+ {
+ p->state = BCJ2_DEC_STATE_ORIG;
+ return SZ_OK;
+ }
+ }
+
+ srcLim = src + num;
+
+ if (p->temp[3] == 0x0F && (src[0] & 0xF0) == 0x80)
+ *dest = src[0];
+ else for (;;)
+ {
+ Byte b = *src;
+ *dest = b;
+ if (b != 0x0F)
+ {
+ if ((b & 0xFE) == 0xE8)
+ break;
+ dest++;
+ if (++src != srcLim)
+ continue;
+ break;
+ }
+ dest++;
+ if (++src == srcLim)
+ break;
+ if ((*src & 0xF0) != 0x80)
+ continue;
+ *dest = *src;
+ break;
+ }
+
+ num = src - p->bufs[BCJ2_STREAM_MAIN];
+
+ if (src == srcLim)
+ {
+ p->temp[3] = src[-1];
+ p->bufs[BCJ2_STREAM_MAIN] = src;
+ p->ip += (UInt32)num;
+ p->dest += num;
+ p->state =
+ p->bufs[BCJ2_STREAM_MAIN] ==
+ p->lims[BCJ2_STREAM_MAIN] ?
+ (unsigned)BCJ2_STREAM_MAIN :
+ (unsigned)BCJ2_DEC_STATE_ORIG;
+ return SZ_OK;
+ }
+
+ {
+ UInt32 bound, ttt;
+ CProb *prob;
+ Byte b = src[0];
+ Byte prev = (Byte)(num == 0 ? p->temp[3] : src[-1]);
+
+ p->temp[3] = b;
+ p->bufs[BCJ2_STREAM_MAIN] = src + 1;
+ num++;
+ p->ip += (UInt32)num;
+ p->dest += num;
+
+ prob = p->probs + (unsigned)(b == 0xE8 ? 2 + (unsigned)prev : (b == 0xE9 ? 1 : 0));
+
+ _IF_BIT_0
+ {
+ _UPDATE_0
+ continue;
+ }
+ _UPDATE_1
+
+ }
+ }
+ }
+
+ {
+ UInt32 val;
+ unsigned cj = (p->temp[3] == 0xE8) ? BCJ2_STREAM_CALL : BCJ2_STREAM_JUMP;
+ const Byte *cur = p->bufs[cj];
+ Byte *dest;
+ SizeT rem;
+
+ if (cur == p->lims[cj])
+ {
+ p->state = cj;
+ break;
+ }
+
+ val = GetBe32(cur);
+ p->bufs[cj] = cur + 4;
+
+ p->ip += 4;
+ val -= p->ip;
+ dest = p->dest;
+ rem = p->destLim - dest;
+
+ if (rem < 4)
+ {
+ SizeT i;
+ SetUi32(p->temp, val);
+ for (i = 0; i < rem; i++)
+ dest[i] = p->temp[i];
+ p->dest = dest + rem;
+ p->state = BCJ2_DEC_STATE_ORIG_0 + (unsigned)rem;
+ break;
+ }
+
+ SetUi32(dest, val);
+ p->temp[3] = (Byte)(val >> 24);
+ p->dest = dest + 4;
+ }
+ }
+
+ if (p->range < kTopValue && p->bufs[BCJ2_STREAM_RC] != p->lims[BCJ2_STREAM_RC])
+ {
+ p->range <<= 8;
+ p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++;
+ }
+
+ return SZ_OK;
+}
+
+#undef kTopValue /* reused later. --ryan. */
+#undef kBitModelTotal /* reused later. --ryan. */
+
+
+/* Bra.c -- Converters for RISC code
+2010-04-16 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "Bra.h"
+*/
+
+static SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding)
+{
+ SizeT i;
+ if (size < 4)
+ return 0;
+ size -= 4;
+ ip += 8;
+ for (i = 0; i <= size; i += 4)
+ {
+ if (data[i + 3] == 0xEB)
+ {
+ UInt32 dest;
+ UInt32 src = ((UInt32)data[i + 2] << 16) | ((UInt32)data[i + 1] << 8) | (data[i + 0]);
+ src <<= 2;
+ if (encoding)
+ dest = ip + (UInt32)i + src;
+ else
+ dest = src - (ip + (UInt32)i);
+ dest >>= 2;
+ data[i + 2] = (Byte)(dest >> 16);
+ data[i + 1] = (Byte)(dest >> 8);
+ data[i + 0] = (Byte)dest;
+ }
+ }
+ return i;
+}
+
+static SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding)
+{
+ SizeT i;
+ if (size < 4)
+ return 0;
+ size -= 4;
+ ip += 4;
+ for (i = 0; i <= size; i += 2)
+ {
+ if ((data[i + 1] & 0xF8) == 0xF0 &&
+ (data[i + 3] & 0xF8) == 0xF8)
+ {
+ UInt32 dest;
+ UInt32 src =
+ (((UInt32)data[i + 1] & 0x7) << 19) |
+ ((UInt32)data[i + 0] << 11) |
+ (((UInt32)data[i + 3] & 0x7) << 8) |
+ (data[i + 2]);
+
+ src <<= 1;
+ if (encoding)
+ dest = ip + (UInt32)i + src;
+ else
+ dest = src - (ip + (UInt32)i);
+ dest >>= 1;
+
+ data[i + 1] = (Byte)(0xF0 | ((dest >> 19) & 0x7));
+ data[i + 0] = (Byte)(dest >> 11);
+ data[i + 3] = (Byte)(0xF8 | ((dest >> 8) & 0x7));
+ data[i + 2] = (Byte)dest;
+ i += 2;
+ }
+ }
+ return i;
+}
+
+static SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding)
+{
+ SizeT i;
+ if (size < 4)
+ return 0;
+ size -= 4;
+ for (i = 0; i <= size; i += 4)
+ {
+ if ((data[i] >> 2) == 0x12 && (data[i + 3] & 3) == 1)
+ {
+ UInt32 src = ((UInt32)(data[i + 0] & 3) << 24) |
+ ((UInt32)data[i + 1] << 16) |
+ ((UInt32)data[i + 2] << 8) |
+ ((UInt32)data[i + 3] & (~3));
+
+ UInt32 dest;
+ if (encoding)
+ dest = ip + (UInt32)i + src;
+ else
+ dest = src - (ip + (UInt32)i);
+ data[i + 0] = (Byte)(0x48 | ((dest >> 24) & 0x3));
+ data[i + 1] = (Byte)(dest >> 16);
+ data[i + 2] = (Byte)(dest >> 8);
+ data[i + 3] &= 0x3;
+ data[i + 3] |= dest;
+ }
+ }
+ return i;
+}
+
+static SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding)
+{
+ UInt32 i;
+ if (size < 4)
+ return 0;
+ size -= 4;
+ for (i = 0; i <= size; i += 4)
+ {
+ if ((data[i] == 0x40 && (data[i + 1] & 0xC0) == 0x00) ||
+ (data[i] == 0x7F && (data[i + 1] & 0xC0) == 0xC0))
+ {
+ UInt32 src =
+ ((UInt32)data[i + 0] << 24) |
+ ((UInt32)data[i + 1] << 16) |
+ ((UInt32)data[i + 2] << 8) |
+ ((UInt32)data[i + 3]);
+ UInt32 dest;
+
+ src <<= 2;
+ if (encoding)
+ dest = ip + i + src;
+ else
+ dest = src - (ip + i);
+ dest >>= 2;
+
+ dest = (((0 - ((dest >> 22) & 1)) << 22) & 0x3FFFFFFF) | (dest & 0x3FFFFF) | 0x40000000;
+
+ data[i + 0] = (Byte)(dest >> 24);
+ data[i + 1] = (Byte)(dest >> 16);
+ data[i + 2] = (Byte)(dest >> 8);
+ data[i + 3] = (Byte)dest;
+ }
+ }
+ return i;
+}
+
+/* Bra86.c -- Converter for x86 code (BCJ)
+2013-11-12 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "Bra.h"
+*/
+
+#define Test86MSByte(b) ((((b) + 1) & 0xFE) == 0)
+
+static SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding)
+{
+ SizeT pos = 0;
+ UInt32 mask = *state & 7;
+ if (size < 5)
+ return 0;
+ size -= 4;
+ ip += 5;
+
+ for (;;)
+ {
+ Byte *p = data + pos;
+ const Byte *limit = data + size;
+ for (; p < limit; p++)
+ if ((*p & 0xFE) == 0xE8)
+ break;
+
+ {
+ SizeT d = (SizeT)(p - data - pos);
+ pos = (SizeT)(p - data);
+ if (p >= limit)
+ {
+ *state = (d > 2 ? 0 : mask >> (unsigned)d);
+ return pos;
+ }
+ if (d > 2)
+ mask = 0;
+ else
+ {
+ mask >>= (unsigned)d;
+ if (mask != 0 && (mask > 4 || mask == 3 || Test86MSByte(p[(mask >> 1) + 1])))
+ {
+ mask = (mask >> 1) | 4;
+ pos++;
+ continue;
+ }
+ }
+ }
+
+ if (Test86MSByte(p[4]))
+ {
+ UInt32 v = ((UInt32)p[4] << 24) | ((UInt32)p[3] << 16) | ((UInt32)p[2] << 8) | ((UInt32)p[1]);
+ UInt32 cur = ip + (UInt32)pos;
+ pos += 5;
+ if (encoding)
+ v += cur;
+ else
+ v -= cur;
+ if (mask != 0)
+ {
+ unsigned sh = (mask & 6) << 2;
+ if (Test86MSByte((Byte)(v >> sh)))
+ {
+ v ^= (((UInt32)0x100 << sh) - 1);
+ if (encoding)
+ v += cur;
+ else
+ v -= cur;
+ }
+ mask = 0;
+ }
+ p[1] = (Byte)v;
+ p[2] = (Byte)(v >> 8);
+ p[3] = (Byte)(v >> 16);
+ p[4] = (Byte)(0 - ((v >> 24) & 1));
+ }
+ else
+ {
+ mask = (mask >> 1) | 4;
+ pos++;
+ }
+ }
+}
+
+
+/* BraIA64.c -- Converter for IA-64 code
+2013-11-12 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "Bra.h"
+*/
+static const Byte kBranchTable[32] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 4, 6, 6, 0, 0, 7, 7,
+ 4, 4, 0, 0, 4, 4, 0, 0
+};
+
+static SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding)
+{
+ SizeT i;
+ if (size < 16)
+ return 0;
+ size -= 16;
+ for (i = 0; i <= size; i += 16)
+ {
+ UInt32 instrTemplate = data[i] & 0x1F;
+ UInt32 mask = kBranchTable[instrTemplate];
+ UInt32 bitPos = 5;
+ int slot;
+ for (slot = 0; slot < 3; slot++, bitPos += 41)
+ {
+ UInt32 bytePos, bitRes;
+ UInt64 instruction, instNorm;
+ int j;
+ if (((mask >> slot) & 1) == 0)
+ continue;
+ bytePos = (bitPos >> 3);
+ bitRes = bitPos & 0x7;
+ instruction = 0;
+ for (j = 0; j < 6; j++)
+ instruction += (UInt64)data[i + j + bytePos] << (8 * j);
+
+ instNorm = instruction >> bitRes;
+ if (((instNorm >> 37) & 0xF) == 0x5 && ((instNorm >> 9) & 0x7) == 0)
+ {
+ UInt32 src = (UInt32)((instNorm >> 13) & 0xFFFFF);
+ UInt32 dest;
+ src |= ((UInt32)(instNorm >> 36) & 1) << 20;
+
+ src <<= 4;
+
+ if (encoding)
+ dest = ip + (UInt32)i + src;
+ else
+ dest = src - (ip + (UInt32)i);
+
+ dest >>= 4;
+
+ instNorm &= ~((UInt64)(0x8FFFFF) << 13);
+ instNorm |= ((UInt64)(dest & 0xFFFFF) << 13);
+ instNorm |= ((UInt64)(dest & 0x100000) << (36 - 20));
+
+ instruction &= (1 << bitRes) - 1;
+ instruction |= (instNorm << bitRes);
+ for (j = 0; j < 6; j++)
+ data[i + j + bytePos] = (Byte)(instruction >> (8 * j));
+ }
+ }
+ }
+ return i;
+}
+
+
+/* Delta.c -- Delta converter
+2009-05-26 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "Delta.h"
+*/
+
+static void Delta_Init(Byte *state)
+{
+ unsigned i;
+ for (i = 0; i < DELTA_STATE_SIZE; i++)
+ state[i] = 0;
+}
+
+static void MyMemCpy(Byte *dest, const Byte *src, unsigned size)
+{
+ unsigned i;
+ for (i = 0; i < size; i++)
+ dest[i] = src[i];
+}
+
+static void Delta_Decode(Byte *state, unsigned delta, Byte *data, SizeT size)
+{
+ Byte buf[DELTA_STATE_SIZE];
+ unsigned j = 0;
+ MyMemCpy(buf, state, delta);
+ {
+ SizeT i;
+ for (i = 0; i < size;)
+ {
+ for (j = 0; j < delta && i < size; i++, j++)
+ {
+ buf[j] = data[i] = (Byte)(buf[j] + data[i]);
+ }
+ }
+ }
+ if (j == delta)
+ j = 0;
+ MyMemCpy(state, buf + j, delta - j);
+ MyMemCpy(state + delta - j, buf, j);
+}
+
+/* LzmaDec.c -- LZMA Decoder
+2016-05-16 : Igor Pavlov : Public domain */
+
+/*
+#include "Precomp.h"
+
+#include "LzmaDec.h"
+
+#include
+*/
+
+#define kNumTopBits 24
+#define kTopValue ((UInt32)1 << kNumTopBits)
+
+#define kNumBitModelTotalBits 11
+#define kBitModelTotal (1 << kNumBitModelTotalBits)
+#define kNumMoveBits 5
+
+#define RC_INIT_SIZE 5
+
+#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); }
+
+#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
+#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits));
+#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits));
+#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \
+ { UPDATE_0(p); i = (i + i); A0; } else \
+ { UPDATE_1(p); i = (i + i) + 1; A1; }
+#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;)
+
+#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); }
+#define TREE_DECODE(probs, limit, i) \
+ { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; }
+
+/* #define _LZMA_SIZE_OPT */
+
+#ifdef _LZMA_SIZE_OPT
+#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i)
+#else
+#define TREE_6_DECODE(probs, i) \
+ { i = 1; \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ TREE_GET_BIT(probs, i); \
+ i -= 0x40; }
+#endif
+
+#define NORMAL_LITER_DEC GET_BIT(prob + symbol, symbol)
+#define MATCHED_LITER_DEC \
+ matchByte <<= 1; \
+ bit = (matchByte & offs); \
+ probLit = prob + offs + bit + symbol; \
+ GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit)
+
+#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); }
+
+#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound)
+#define UPDATE_0_CHECK range = bound;
+#define UPDATE_1_CHECK range -= bound; code -= bound;
+#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \
+ { UPDATE_0_CHECK; i = (i + i); A0; } else \
+ { UPDATE_1_CHECK; i = (i + i) + 1; A1; }
+#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;)
+#define TREE_DECODE_CHECK(probs, limit, i) \
+ { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; }
+
+
+#define kNumPosBitsMax 4
+#define kNumPosStatesMax (1 << kNumPosBitsMax)
+
+#define kLenNumLowBits 3
+#define kLenNumLowSymbols (1 << kLenNumLowBits)
+#define kLenNumMidBits 3
+#define kLenNumMidSymbols (1 << kLenNumMidBits)
+#define kLenNumHighBits 8
+#define kLenNumHighSymbols (1 << kLenNumHighBits)
+
+#define LenChoice 0
+#define LenChoice2 (LenChoice + 1)
+#define LenLow (LenChoice2 + 1)
+#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits))
+#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits))
+#define kNumLenProbs (LenHigh + kLenNumHighSymbols)
+
+
+#define kNumStates 12
+#define kNumLitStates 7
+
+#define kStartPosModelIndex 4
+#define kEndPosModelIndex 14
+#define kNumFullDistances (1 << (kEndPosModelIndex >> 1))
+
+#define kNumPosSlotBits 6
+#define kNumLenToPosStates 4
+
+#define kNumAlignBits 4
+#define kAlignTableSize (1 << kNumAlignBits)
+
+#define kMatchMinLen 2
+#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols)
+
+#define IsMatch 0
+#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax))
+#define IsRepG0 (IsRep + kNumStates)
+#define IsRepG1 (IsRepG0 + kNumStates)
+#define IsRepG2 (IsRepG1 + kNumStates)
+#define IsRep0Long (IsRepG2 + kNumStates)
+#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax))
+#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits))
+#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex)
+#define LenCoder (Align + kAlignTableSize)
+#define RepLenCoder (LenCoder + kNumLenProbs)
+#define Literal (RepLenCoder + kNumLenProbs)
+
+#define LZMA_BASE_SIZE 1846
+#define LZMA_LIT_SIZE 0x300
+
+#if Literal != LZMA_BASE_SIZE
+StopCompilingDueBUG
+#endif
+
+#define LzmaProps_GetNumProbs(p) (Literal + ((UInt32)LZMA_LIT_SIZE << ((p)->lc + (p)->lp)))
+
+#define LZMA_DIC_MIN (1 << 12)
+
+/* First LZMA-symbol is always decoded.
+And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization
+Out:
+ Result:
+ SZ_OK - OK
+ SZ_ERROR_DATA - Error
+ p->remainLen:
+ < kMatchSpecLenStart : normal remain
+ = kMatchSpecLenStart : finished
+ = kMatchSpecLenStart + 1 : Flush marker (unused now)
+ = kMatchSpecLenStart + 2 : State Init Marker (unused now)
+*/
+
+static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
+{
+ CLzmaProb *probs = p->probs;
+
+ unsigned state = p->state;
+ UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3];
+ unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1;
+ unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1;
+ unsigned lc = p->prop.lc;
+
+ Byte *dic = p->dic;
+ SizeT dicBufSize = p->dicBufSize;
+ SizeT dicPos = p->dicPos;
+
+ UInt32 processedPos = p->processedPos;
+ UInt32 checkDicSize = p->checkDicSize;
+ unsigned len = 0;
+
+ const Byte *buf = p->buf;
+ UInt32 range = p->range;
+ UInt32 code = p->code;
+
+ do
+ {
+ CLzmaProb *prob;
+ UInt32 bound;
+ unsigned ttt;
+ unsigned posState = processedPos & pbMask;
+
+ prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0(prob)
+ {
+ unsigned symbol;
+ UPDATE_0(prob);
+ prob = probs + Literal;
+ if (processedPos != 0 || checkDicSize != 0)
+ prob += ((UInt32)LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) +
+ (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc))));
+ processedPos++;
+
+ if (state < kNumLitStates)
+ {
+ state -= (state < 4) ? state : 3;
+ symbol = 1;
+ #ifdef _LZMA_SIZE_OPT
+ do { NORMAL_LITER_DEC } while (symbol < 0x100);
+ #else
+ NORMAL_LITER_DEC
+ NORMAL_LITER_DEC
+ NORMAL_LITER_DEC
+ NORMAL_LITER_DEC
+ NORMAL_LITER_DEC
+ NORMAL_LITER_DEC
+ NORMAL_LITER_DEC
+ NORMAL_LITER_DEC
+ #endif
+ }
+ else
+ {
+ unsigned matchByte = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)];
+ unsigned offs = 0x100;
+ state -= (state < 10) ? 3 : 6;
+ symbol = 1;
+ #ifdef _LZMA_SIZE_OPT
+ do
+ {
+ unsigned bit;
+ CLzmaProb *probLit;
+ MATCHED_LITER_DEC
+ }
+ while (symbol < 0x100);
+ #else
+ {
+ unsigned bit;
+ CLzmaProb *probLit;
+ MATCHED_LITER_DEC
+ MATCHED_LITER_DEC
+ MATCHED_LITER_DEC
+ MATCHED_LITER_DEC
+ MATCHED_LITER_DEC
+ MATCHED_LITER_DEC
+ MATCHED_LITER_DEC
+ MATCHED_LITER_DEC
+ }
+ #endif
+ }
+
+ dic[dicPos++] = (Byte)symbol;
+ continue;
+ }
+
+ {
+ UPDATE_1(prob);
+ prob = probs + IsRep + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ state += kNumStates;
+ prob = probs + LenCoder;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ if (checkDicSize == 0 && processedPos == 0)
+ return SZ_ERROR_DATA;
+ prob = probs + IsRepG0 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)];
+ dicPos++;
+ processedPos++;
+ state = state < kNumLitStates ? 9 : 11;
+ continue;
+ }
+ UPDATE_1(prob);
+ }
+ else
+ {
+ UInt32 distance;
+ UPDATE_1(prob);
+ prob = probs + IsRepG1 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ distance = rep1;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ prob = probs + IsRepG2 + state;
+ IF_BIT_0(prob)
+ {
+ UPDATE_0(prob);
+ distance = rep2;
+ }
+ else
+ {
+ UPDATE_1(prob);
+ distance = rep3;
+ rep3 = rep2;
+ }
+ rep2 = rep1;
+ }
+ rep1 = rep0;
+ rep0 = distance;
+ }
+ state = state < kNumLitStates ? 8 : 11;
+ prob = probs + RepLenCoder;
+ }
+
+ #ifdef _LZMA_SIZE_OPT
+ {
+ unsigned lim, offset;
+ CLzmaProb *probLen = prob + LenChoice;
+ IF_BIT_0(probLen)
+ {
+ UPDATE_0(probLen);
+ probLen = prob + LenLow + (posState << kLenNumLowBits);
+ offset = 0;
+ lim = (1 << kLenNumLowBits);
+ }
+ else
+ {
+ UPDATE_1(probLen);
+ probLen = prob + LenChoice2;
+ IF_BIT_0(probLen)
+ {
+ UPDATE_0(probLen);
+ probLen = prob + LenMid + (posState << kLenNumMidBits);
+ offset = kLenNumLowSymbols;
+ lim = (1 << kLenNumMidBits);
+ }
+ else
+ {
+ UPDATE_1(probLen);
+ probLen = prob + LenHigh;
+ offset = kLenNumLowSymbols + kLenNumMidSymbols;
+ lim = (1 << kLenNumHighBits);
+ }
+ }
+ TREE_DECODE(probLen, lim, len);
+ len += offset;
+ }
+ #else
+ {
+ CLzmaProb *probLen = prob + LenChoice;
+ IF_BIT_0(probLen)
+ {
+ UPDATE_0(probLen);
+ probLen = prob + LenLow + (posState << kLenNumLowBits);
+ len = 1;
+ TREE_GET_BIT(probLen, len);
+ TREE_GET_BIT(probLen, len);
+ TREE_GET_BIT(probLen, len);
+ len -= 8;
+ }
+ else
+ {
+ UPDATE_1(probLen);
+ probLen = prob + LenChoice2;
+ IF_BIT_0(probLen)
+ {
+ UPDATE_0(probLen);
+ probLen = prob + LenMid + (posState << kLenNumMidBits);
+ len = 1;
+ TREE_GET_BIT(probLen, len);
+ TREE_GET_BIT(probLen, len);
+ TREE_GET_BIT(probLen, len);
+ }
+ else
+ {
+ UPDATE_1(probLen);
+ probLen = prob + LenHigh;
+ TREE_DECODE(probLen, (1 << kLenNumHighBits), len);
+ len += kLenNumLowSymbols + kLenNumMidSymbols;
+ }
+ }
+ }
+ #endif
+
+ if (state >= kNumStates)
+ {
+ UInt32 distance;
+ prob = probs + PosSlot +
+ ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits);
+ TREE_6_DECODE(prob, distance);
+ if (distance >= kStartPosModelIndex)
+ {
+ unsigned posSlot = (unsigned)distance;
+ unsigned numDirectBits = (unsigned)(((distance >> 1) - 1));
+ distance = (2 | (distance & 1));
+ if (posSlot < kEndPosModelIndex)
+ {
+ distance <<= numDirectBits;
+ prob = probs + SpecPos + distance - posSlot - 1;
+ {
+ UInt32 mask = 1;
+ unsigned i = 1;
+ do
+ {
+ GET_BIT2(prob + i, i, ; , distance |= mask);
+ mask <<= 1;
+ }
+ while (--numDirectBits != 0);
+ }
+ }
+ else
+ {
+ numDirectBits -= kNumAlignBits;
+ do
+ {
+ NORMALIZE
+ range >>= 1;
+
+ {
+ UInt32 t;
+ code -= range;
+ t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */
+ distance = (distance << 1) + (t + 1);
+ code += range & t;
+ }
+ /*
+ distance <<= 1;
+ if (code >= range)
+ {
+ code -= range;
+ distance |= 1;
+ }
+ */
+ }
+ while (--numDirectBits != 0);
+ prob = probs + Align;
+ distance <<= kNumAlignBits;
+ {
+ unsigned i = 1;
+ GET_BIT2(prob + i, i, ; , distance |= 1);
+ GET_BIT2(prob + i, i, ; , distance |= 2);
+ GET_BIT2(prob + i, i, ; , distance |= 4);
+ GET_BIT2(prob + i, i, ; , distance |= 8);
+ }
+ if (distance == (UInt32)0xFFFFFFFF)
+ {
+ len += kMatchSpecLenStart;
+ state -= kNumStates;
+ break;
+ }
+ }
+ }
+
+ rep3 = rep2;
+ rep2 = rep1;
+ rep1 = rep0;
+ rep0 = distance + 1;
+ if (checkDicSize == 0)
+ {
+ if (distance >= processedPos)
+ {
+ p->dicPos = dicPos;
+ return SZ_ERROR_DATA;
+ }
+ }
+ else if (distance >= checkDicSize)
+ {
+ p->dicPos = dicPos;
+ return SZ_ERROR_DATA;
+ }
+ state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3;
+ }
+
+ len += kMatchMinLen;
+
+ {
+ SizeT rem;
+ unsigned curLen;
+ SizeT pos;
+
+ if ((rem = limit - dicPos) == 0)
+ {
+ p->dicPos = dicPos;
+ return SZ_ERROR_DATA;
+ }
+
+ curLen = ((rem < len) ? (unsigned)rem : len);
+ pos = dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0);
+
+ processedPos += curLen;
+
+ len -= curLen;
+ if (curLen <= dicBufSize - pos)
+ {
+ Byte *dest = dic + dicPos;
+ ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos;
+ const Byte *lim = dest + curLen;
+ dicPos += curLen;
+ do
+ *(dest) = (Byte)*(dest + src);
+ while (++dest != lim);
+ }
+ else
+ {
+ do
+ {
+ dic[dicPos++] = dic[pos];
+ if (++pos == dicBufSize)
+ pos = 0;
+ }
+ while (--curLen != 0);
+ }
+ }
+ }
+ }
+ while (dicPos < limit && buf < bufLimit);
+
+ NORMALIZE;
+
+ p->buf = buf;
+ p->range = range;
+ p->code = code;
+ p->remainLen = len;
+ p->dicPos = dicPos;
+ p->processedPos = processedPos;
+ p->reps[0] = rep0;
+ p->reps[1] = rep1;
+ p->reps[2] = rep2;
+ p->reps[3] = rep3;
+ p->state = state;
+
+ return SZ_OK;
+}
+
+static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit)
+{
+ if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart)
+ {
+ Byte *dic = p->dic;
+ SizeT dicPos = p->dicPos;
+ SizeT dicBufSize = p->dicBufSize;
+ unsigned len = p->remainLen;
+ SizeT rep0 = p->reps[0]; /* we use SizeT to avoid the BUG of VC14 for AMD64 */
+ SizeT rem = limit - dicPos;
+ if (rem < len)
+ len = (unsigned)(rem);
+
+ if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len)
+ p->checkDicSize = p->prop.dicSize;
+
+ p->processedPos += len;
+ p->remainLen -= len;
+ while (len != 0)
+ {
+ len--;
+ dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)];
+ dicPos++;
+ }
+ p->dicPos = dicPos;
+ }
+}
+
+static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
+{
+ do
+ {
+ SizeT limit2 = limit;
+ if (p->checkDicSize == 0)
+ {
+ UInt32 rem = p->prop.dicSize - p->processedPos;
+ if (limit - p->dicPos > rem)
+ limit2 = p->dicPos + rem;
+ }
+
+ RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit));
+
+ if (p->checkDicSize == 0 && p->processedPos >= p->prop.dicSize)
+ p->checkDicSize = p->prop.dicSize;
+
+ LzmaDec_WriteRem(p, limit);
+ }
+ while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart);
+
+ if (p->remainLen > kMatchSpecLenStart)
+ p->remainLen = kMatchSpecLenStart;
+
+ return 0;
+}
+
+typedef enum
+{
+ DUMMY_ERROR, /* unexpected end of input stream */
+ DUMMY_LIT,
+ DUMMY_MATCH,
+ DUMMY_REP
+} ELzmaDummy;
+
+static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize)
+{
+ UInt32 range = p->range;
+ UInt32 code = p->code;
+ const Byte *bufLimit = buf + inSize;
+ const CLzmaProb *probs = p->probs;
+ unsigned state = p->state;
+ ELzmaDummy res;
+
+ {
+ const CLzmaProb *prob;
+ UInt32 bound;
+ unsigned ttt;
+ unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1);
+
+ prob = probs + IsMatch + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK
+
+ /* if (bufLimit - buf >= 7) return DUMMY_LIT; */
+
+ prob = probs + Literal;
+ if (p->checkDicSize != 0 || p->processedPos != 0)
+ prob += ((UInt32)LZMA_LIT_SIZE *
+ ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) +
+ (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc))));
+
+ if (state < kNumLitStates)
+ {
+ unsigned symbol = 1;
+ do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100);
+ }
+ else
+ {
+ unsigned matchByte = p->dic[p->dicPos - p->reps[0] +
+ (p->dicPos < p->reps[0] ? p->dicBufSize : 0)];
+ unsigned offs = 0x100;
+ unsigned symbol = 1;
+ do
+ {
+ unsigned bit;
+ const CLzmaProb *probLit;
+ matchByte <<= 1;
+ bit = (matchByte & offs);
+ probLit = prob + offs + bit + symbol;
+ GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit)
+ }
+ while (symbol < 0x100);
+ }
+ res = DUMMY_LIT;
+ }
+ else
+ {
+ unsigned len;
+ UPDATE_1_CHECK;
+
+ prob = probs + IsRep + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ state = 0;
+ prob = probs + LenCoder;
+ res = DUMMY_MATCH;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ res = DUMMY_REP;
+ prob = probs + IsRepG0 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ NORMALIZE_CHECK;
+ return DUMMY_REP;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ }
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ prob = probs + IsRepG1 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ prob = probs + IsRepG2 + state;
+ IF_BIT_0_CHECK(prob)
+ {
+ UPDATE_0_CHECK;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ }
+ }
+ }
+ state = kNumStates;
+ prob = probs + RepLenCoder;
+ }
+ {
+ unsigned limit, offset;
+ const CLzmaProb *probLen = prob + LenChoice;
+ IF_BIT_0_CHECK(probLen)
+ {
+ UPDATE_0_CHECK;
+ probLen = prob + LenLow + (posState << kLenNumLowBits);
+ offset = 0;
+ limit = 1 << kLenNumLowBits;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ probLen = prob + LenChoice2;
+ IF_BIT_0_CHECK(probLen)
+ {
+ UPDATE_0_CHECK;
+ probLen = prob + LenMid + (posState << kLenNumMidBits);
+ offset = kLenNumLowSymbols;
+ limit = 1 << kLenNumMidBits;
+ }
+ else
+ {
+ UPDATE_1_CHECK;
+ probLen = prob + LenHigh;
+ offset = kLenNumLowSymbols + kLenNumMidSymbols;
+ limit = 1 << kLenNumHighBits;
+ }
+ }
+ TREE_DECODE_CHECK(probLen, limit, len);
+ len += offset;
+ }
+
+ if (state < 4)
+ {
+ unsigned posSlot;
+ prob = probs + PosSlot +
+ ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) <<
+ kNumPosSlotBits);
+ TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot);
+ if (posSlot >= kStartPosModelIndex)
+ {
+ unsigned numDirectBits = ((posSlot >> 1) - 1);
+
+ /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */
+
+ if (posSlot < kEndPosModelIndex)
+ {
+ prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1;
+ }
+ else
+ {
+ numDirectBits -= kNumAlignBits;
+ do
+ {
+ NORMALIZE_CHECK
+ range >>= 1;
+ code -= range & (((code - range) >> 31) - 1);
+ /* if (code >= range) code -= range; */
+ }
+ while (--numDirectBits != 0);
+ prob = probs + Align;
+ numDirectBits = kNumAlignBits;
+ }
+ {
+ unsigned i = 1;
+ do
+ {
+ GET_BIT_CHECK(prob + i, i);
+ }
+ while (--numDirectBits != 0);
+ }
+ }
+ }
+ }
+ }
+ NORMALIZE_CHECK;
+ return res;
+}
+
+
+static void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState)
+{
+ p->needFlush = 1;
+ p->remainLen = 0;
+ p->tempBufSize = 0;
+
+ if (initDic)
+ {
+ p->processedPos = 0;
+ p->checkDicSize = 0;
+ p->needInitState = 1;
+ }
+ if (initState)
+ p->needInitState = 1;
+}
+
+static void LzmaDec_Init(CLzmaDec *p)
+{
+ p->dicPos = 0;
+ LzmaDec_InitDicAndState(p, True, True);
+}
+
+static void LzmaDec_InitStateReal(CLzmaDec *p)
+{
+ SizeT numProbs = LzmaProps_GetNumProbs(&p->prop);
+ SizeT i;
+ CLzmaProb *probs = p->probs;
+ for (i = 0; i < numProbs; i++)
+ probs[i] = kBitModelTotal >> 1;
+ p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1;
+ p->state = 0;
+ p->needInitState = 0;
+}
+
+static SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen,
+ ELzmaFinishMode finishMode, ELzmaStatus *status)
+{
+ SizeT inSize = *srcLen;
+ (*srcLen) = 0;
+ LzmaDec_WriteRem(p, dicLimit);
+
+ *status = LZMA_STATUS_NOT_SPECIFIED;
+
+ while (p->remainLen != kMatchSpecLenStart)
+ {
+ int checkEndMarkNow;
+
+ if (p->needFlush)
+ {
+ for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--)
+ p->tempBuf[p->tempBufSize++] = *src++;
+ if (p->tempBufSize < RC_INIT_SIZE)
+ {
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (p->tempBuf[0] != 0)
+ return SZ_ERROR_DATA;
+ p->code =
+ ((UInt32)p->tempBuf[1] << 24)
+ | ((UInt32)p->tempBuf[2] << 16)
+ | ((UInt32)p->tempBuf[3] << 8)
+ | ((UInt32)p->tempBuf[4]);
+ p->range = 0xFFFFFFFF;
+ p->needFlush = 0;
+ p->tempBufSize = 0;
+ }
+
+ checkEndMarkNow = 0;
+ if (p->dicPos >= dicLimit)
+ {
+ if (p->remainLen == 0 && p->code == 0)
+ {
+ *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK;
+ return SZ_OK;
+ }
+ if (finishMode == LZMA_FINISH_ANY)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_OK;
+ }
+ if (p->remainLen != 0)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ checkEndMarkNow = 1;
+ }
+
+ if (p->needInitState)
+ LzmaDec_InitStateReal(p);
+
+ if (p->tempBufSize == 0)
+ {
+ SizeT processed;
+ const Byte *bufLimit;
+ if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
+ {
+ int dummyRes = LzmaDec_TryDummy(p, src, inSize);
+ if (dummyRes == DUMMY_ERROR)
+ {
+ memcpy(p->tempBuf, src, inSize);
+ p->tempBufSize = (unsigned)inSize;
+ (*srcLen) += inSize;
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ bufLimit = src;
+ }
+ else
+ bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX;
+ p->buf = src;
+ if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0)
+ return SZ_ERROR_DATA;
+ processed = (SizeT)(p->buf - src);
+ (*srcLen) += processed;
+ src += processed;
+ inSize -= processed;
+ }
+ else
+ {
+ unsigned rem = p->tempBufSize, lookAhead = 0;
+ while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize)
+ p->tempBuf[rem++] = src[lookAhead++];
+ p->tempBufSize = rem;
+ if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
+ {
+ int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem);
+ if (dummyRes == DUMMY_ERROR)
+ {
+ (*srcLen) += lookAhead;
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_ERROR_DATA;
+ }
+ }
+ p->buf = p->tempBuf;
+ if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0)
+ return SZ_ERROR_DATA;
+
+ {
+ unsigned kkk = (unsigned)(p->buf - p->tempBuf);
+ if (rem < kkk)
+ return SZ_ERROR_FAIL; /* some internal error */
+ rem -= kkk;
+ if (lookAhead < rem)
+ return SZ_ERROR_FAIL; /* some internal error */
+ lookAhead -= rem;
+ }
+ (*srcLen) += lookAhead;
+ src += lookAhead;
+ inSize -= lookAhead;
+ p->tempBufSize = 0;
+ }
+ }
+ if (p->code == 0)
+ *status = LZMA_STATUS_FINISHED_WITH_MARK;
+ return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA;
+}
+
+static void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc)
+{
+ alloc->Free(alloc, p->probs);
+ p->probs = NULL;
+}
+
+static SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size)
+{
+ UInt32 dicSize;
+ Byte d;
+
+ if (size < LZMA_PROPS_SIZE)
+ return SZ_ERROR_UNSUPPORTED;
+ else
+ dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24);
+
+ if (dicSize < LZMA_DIC_MIN)
+ dicSize = LZMA_DIC_MIN;
+ p->dicSize = dicSize;
+
+ d = data[0];
+ if (d >= (9 * 5 * 5))
+ return SZ_ERROR_UNSUPPORTED;
+
+ p->lc = d % 9;
+ d /= 9;
+ p->pb = d / 5;
+ p->lp = d % 5;
+
+ return SZ_OK;
+}
+
+static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc)
+{
+ UInt32 numProbs = LzmaProps_GetNumProbs(propNew);
+ if (!p->probs || numProbs != p->numProbs)
+ {
+ LzmaDec_FreeProbs(p, alloc);
+ p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb));
+ p->numProbs = numProbs;
+ if (!p->probs)
+ return SZ_ERROR_MEM;
+ }
+ return SZ_OK;
+}
+
+static SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc)
+{
+ CLzmaProps propNew;
+ RINOK(LzmaProps_Decode(&propNew, props, propsSize));
+ RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc));
+ p->prop = propNew;
+ return SZ_OK;
+}
+
+/* Lzma2Dec.c -- LZMA2 Decoder
+2015-11-09 : Igor Pavlov : Public domain */
+
+/* #define SHOW_DEBUG_INFO */
+
+/*
+#include "Precomp.h"
+
+#ifdef SHOW_DEBUG_INFO
+#include
+#endif
+
+#include
+
+#include "Lzma2Dec.h"
+*/
+
+/*
+00000000 - EOS
+00000001 U U - Uncompressed Reset Dic
+00000010 U U - Uncompressed No Reset
+100uuuuu U U P P - LZMA no reset
+101uuuuu U U P P - LZMA reset state
+110uuuuu U U P P S - LZMA reset state + new prop
+111uuuuu U U P P S - LZMA reset state + new prop + reset dic
+
+ u, U - Unpack Size
+ P - Pack Size
+ S - Props
+*/
+
+#define LZMA2_CONTROL_LZMA (1 << 7)
+#define LZMA2_CONTROL_COPY_NO_RESET 2
+#define LZMA2_CONTROL_COPY_RESET_DIC 1
+#define LZMA2_CONTROL_EOF 0
+
+#define LZMA2_IS_UNCOMPRESSED_STATE(p) (((p)->control & LZMA2_CONTROL_LZMA) == 0)
+
+#define LZMA2_GET_LZMA_MODE(p) (((p)->control >> 5) & 3)
+#define LZMA2_IS_THERE_PROP(mode) ((mode) >= 2)
+
+#define LZMA2_LCLP_MAX 4
+#define LZMA2_DIC_SIZE_FROM_PROP(p) (((UInt32)2 | ((p) & 1)) << ((p) / 2 + 11))
+
+#ifdef SHOW_DEBUG_INFO
+#define PRF(x) x
+#else
+#define PRF(x)
+#endif
+
+typedef enum
+{
+ LZMA2_STATE_CONTROL,
+ LZMA2_STATE_UNPACK0,
+ LZMA2_STATE_UNPACK1,
+ LZMA2_STATE_PACK0,
+ LZMA2_STATE_PACK1,
+ LZMA2_STATE_PROP,
+ LZMA2_STATE_DATA,
+ LZMA2_STATE_DATA_CONT,
+ LZMA2_STATE_FINISHED,
+ LZMA2_STATE_ERROR
+} ELzma2State;
+
+static SRes Lzma2Dec_GetOldProps(Byte prop, Byte *props)
+{
+ UInt32 dicSize;
+ if (prop > 40)
+ return SZ_ERROR_UNSUPPORTED;
+ dicSize = (prop == 40) ? 0xFFFFFFFF : LZMA2_DIC_SIZE_FROM_PROP(prop);
+ props[0] = (Byte)LZMA2_LCLP_MAX;
+ props[1] = (Byte)(dicSize);
+ props[2] = (Byte)(dicSize >> 8);
+ props[3] = (Byte)(dicSize >> 16);
+ props[4] = (Byte)(dicSize >> 24);
+ return SZ_OK;
+}
+
+static SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAlloc *alloc)
+{
+ Byte props[LZMA_PROPS_SIZE];
+ RINOK(Lzma2Dec_GetOldProps(prop, props));
+ return LzmaDec_AllocateProbs(&p->decoder, props, LZMA_PROPS_SIZE, alloc);
+}
+
+static void Lzma2Dec_Init(CLzma2Dec *p)
+{
+ p->state = LZMA2_STATE_CONTROL;
+ p->needInitDic = True;
+ p->needInitState = True;
+ p->needInitProp = True;
+ LzmaDec_Init(&p->decoder);
+}
+
+static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b)
+{
+ switch (p->state)
+ {
+ case LZMA2_STATE_CONTROL:
+ p->control = b;
+ PRF(printf("\n %4X ", (unsigned)p->decoder.dicPos));
+ PRF(printf(" %2X", (unsigned)b));
+ if (p->control == 0)
+ return LZMA2_STATE_FINISHED;
+ if (LZMA2_IS_UNCOMPRESSED_STATE(p))
+ {
+ if ((p->control & 0x7F) > 2)
+ return LZMA2_STATE_ERROR;
+ p->unpackSize = 0;
+ }
+ else
+ p->unpackSize = (UInt32)(p->control & 0x1F) << 16;
+ return LZMA2_STATE_UNPACK0;
+
+ case LZMA2_STATE_UNPACK0:
+ p->unpackSize |= (UInt32)b << 8;
+ return LZMA2_STATE_UNPACK1;
+
+ case LZMA2_STATE_UNPACK1:
+ p->unpackSize |= (UInt32)b;
+ p->unpackSize++;
+ PRF(printf(" %8u", (unsigned)p->unpackSize));
+ return (LZMA2_IS_UNCOMPRESSED_STATE(p)) ? LZMA2_STATE_DATA : LZMA2_STATE_PACK0;
+
+ case LZMA2_STATE_PACK0:
+ p->packSize = (UInt32)b << 8;
+ return LZMA2_STATE_PACK1;
+
+ case LZMA2_STATE_PACK1:
+ p->packSize |= (UInt32)b;
+ p->packSize++;
+ PRF(printf(" %8u", (unsigned)p->packSize));
+ return LZMA2_IS_THERE_PROP(LZMA2_GET_LZMA_MODE(p)) ? LZMA2_STATE_PROP:
+ (p->needInitProp ? LZMA2_STATE_ERROR : LZMA2_STATE_DATA);
+
+ case LZMA2_STATE_PROP:
+ {
+ unsigned lc, lp;
+ if (b >= (9 * 5 * 5))
+ return LZMA2_STATE_ERROR;
+ lc = b % 9;
+ b /= 9;
+ p->decoder.prop.pb = b / 5;
+ lp = b % 5;
+ if (lc + lp > LZMA2_LCLP_MAX)
+ return LZMA2_STATE_ERROR;
+ p->decoder.prop.lc = lc;
+ p->decoder.prop.lp = lp;
+ p->needInitProp = False;
+ return LZMA2_STATE_DATA;
+ }
+ }
+ return LZMA2_STATE_ERROR;
+}
+
+static void LzmaDec_UpdateWithUncompressed(CLzmaDec *p, const Byte *src, SizeT size)
+{
+ memcpy(p->dic + p->dicPos, src, size);
+ p->dicPos += size;
+ if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= size)
+ p->checkDicSize = p->prop.dicSize;
+ p->processedPos += (UInt32)size;
+}
+
+static void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState);
+
+static SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit,
+ const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status)
+{
+ SizeT inSize = *srcLen;
+ *srcLen = 0;
+ *status = LZMA_STATUS_NOT_SPECIFIED;
+
+ while (p->state != LZMA2_STATE_FINISHED)
+ {
+ SizeT dicPos = p->decoder.dicPos;
+
+ if (p->state == LZMA2_STATE_ERROR)
+ return SZ_ERROR_DATA;
+
+ if (dicPos == dicLimit && finishMode == LZMA_FINISH_ANY)
+ {
+ *status = LZMA_STATUS_NOT_FINISHED;
+ return SZ_OK;
+ }
+
+ if (p->state != LZMA2_STATE_DATA && p->state != LZMA2_STATE_DATA_CONT)
+ {
+ if (*srcLen == inSize)
+ {
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+ (*srcLen)++;
+ p->state = Lzma2Dec_UpdateState(p, *src++);
+
+ if (dicPos == dicLimit && p->state != LZMA2_STATE_FINISHED)
+ {
+ p->state = LZMA2_STATE_ERROR;
+ return SZ_ERROR_DATA;
+ }
+ continue;
+ }
+
+ {
+ SizeT destSizeCur = dicLimit - dicPos;
+ SizeT srcSizeCur = inSize - *srcLen;
+ ELzmaFinishMode curFinishMode = LZMA_FINISH_ANY;
+
+ if (p->unpackSize <= destSizeCur)
+ {
+ destSizeCur = (SizeT)p->unpackSize;
+ curFinishMode = LZMA_FINISH_END;
+ }
+
+ if (LZMA2_IS_UNCOMPRESSED_STATE(p))
+ {
+ if (*srcLen == inSize)
+ {
+ *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+ return SZ_OK;
+ }
+
+ if (p->state == LZMA2_STATE_DATA)
+ {
+ Bool initDic = (p->control == LZMA2_CONTROL_COPY_RESET_DIC);
+ if (initDic)
+ p->needInitProp = p->needInitState = True;
+ else if (p->needInitDic)
+ {
+ p->state = LZMA2_STATE_ERROR;
+ return SZ_ERROR_DATA;
+ }
+ p->needInitDic = False;
+ LzmaDec_InitDicAndState(&p->decoder, initDic, False);
+ }
+
+ if (srcSizeCur > destSizeCur)
+ srcSizeCur = destSizeCur;
+
+ if (srcSizeCur == 0)
+ {
+ p->state = LZMA2_STATE_ERROR;
+ return SZ_ERROR_DATA;
+ }
+
+ LzmaDec_UpdateWithUncompressed(&p->decoder, src, srcSizeCur);
+
+ src += srcSizeCur;
+ *srcLen += srcSizeCur;
+ p->unpackSize -= (UInt32)srcSizeCur;
+ p->state = (p->unpackSize == 0) ? LZMA2_STATE_CONTROL : LZMA2_STATE_DATA_CONT;
+ }
+ else
+ {
+ SizeT outSizeProcessed;
+ SRes res;
+
+ if (p->state == LZMA2_STATE_DATA)
+ {
+ unsigned mode = LZMA2_GET_LZMA_MODE(p);
+ Bool initDic = (mode == 3);
+ Bool initState = (mode != 0);
+ if ((!initDic && p->needInitDic) || (!initState && p->needInitState))
+ {
+ p->state = LZMA2_STATE_ERROR;
+ return SZ_ERROR_DATA;
+ }
+
+ LzmaDec_InitDicAndState(&p->decoder, initDic, initState);
+ p->needInitDic = False;
+ p->needInitState = False;
+ p->state = LZMA2_STATE_DATA_CONT;
+ }
+
+ if (srcSizeCur > p->packSize)
+ srcSizeCur = (SizeT)p->packSize;
+
+ res = LzmaDec_DecodeToDic(&p->decoder, dicPos + destSizeCur, src, &srcSizeCur, curFinishMode, status);
+
+ src += srcSizeCur;
+ *srcLen += srcSizeCur;
+ p->packSize -= (UInt32)srcSizeCur;
+
+ outSizeProcessed = p->decoder.dicPos - dicPos;
+ p->unpackSize -= (UInt32)outSizeProcessed;
+
+ RINOK(res);
+ if (*status == LZMA_STATUS_NEEDS_MORE_INPUT)
+ return res;
+
+ if (srcSizeCur == 0 && outSizeProcessed == 0)
+ {
+ if (*status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
+ || p->unpackSize != 0
+ || p->packSize != 0)
+ {
+ p->state = LZMA2_STATE_ERROR;
+ return SZ_ERROR_DATA;
+ }
+ p->state = LZMA2_STATE_CONTROL;
+ }
+
+ if (*status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)
+ *status = LZMA_STATUS_NOT_FINISHED;
+ }
+ }
+ }
+
+ *status = LZMA_STATUS_FINISHED_WITH_MARK;
+ return SZ_OK;
+}
+
+#endif /* _INCLUDE_PHYSFS_LZMASDK_H_ */
+
+/* end of physfs_lzmasdk.h ... */
+
diff --git a/third-party/physfs/src/physfs_miniz.h b/third-party/physfs/src/physfs_miniz.h
new file mode 100644
index 0000000..e0fddb0
--- /dev/null
+++ b/third-party/physfs/src/physfs_miniz.h
@@ -0,0 +1,710 @@
+/* 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 , 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
+
+typedef PHYSFS_uint8 mz_uint8;
+typedef PHYSFS_sint16 mz_int16;
+typedef PHYSFS_uint16 mz_uint16;
+typedef PHYSFS_uint32 mz_uint32;
+typedef unsigned int mz_uint;
+typedef PHYSFS_uint64 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);
+
+#ifndef MINIZ_LITTLE_ENDIAN /* if not defined by PHYSFS */
+#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
+#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
+
+#define MZ_MAX(a,b) (((a)>(b))?(a):(b))
+#define MZ_MIN(a,b) (((a)<(b))?(a):(b))
+#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))
+#define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj))
+#define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj))
+
+#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
+ #define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
+ #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 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_ARR(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_ARR(total_syms); MZ_CLEAR_ARR(pTable->m_look_up); MZ_CLEAR_ARR(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 = def_alloc_func; */
+ /* if (!pStream->zfree) pStream->zfree = 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
+*/
+
diff --git a/third-party/physfs/src/physfs_platform_android.c b/third-party/physfs/src/physfs_platform_android.c
new file mode 100644
index 0000000..f892fed
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_android.c
@@ -0,0 +1,117 @@
+/*
+ * Android support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_ANDROID
+
+#include
+#include
+#include "physfs_internal.h"
+
+static char *prefpath = NULL;
+
+
+int __PHYSFS_platformInit(void)
+{
+ return 1; /* always succeed. */
+} /* __PHYSFS_platformInit */
+
+
+void __PHYSFS_platformDeinit(void)
+{
+ if (prefpath)
+ {
+ allocator.Free(prefpath);
+ prefpath = NULL;
+ } /* if */
+} /* __PHYSFS_platformDeinit */
+
+
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+ /* no-op. */
+} /* __PHYSFS_platformDetectAvailableCDs */
+
+
+char *__PHYSFS_platformCalcBaseDir(const char *argv0)
+{
+ /* as a cheat, we expect argv0 to be a PHYSFS_AndroidInit* on Android. */
+ PHYSFS_AndroidInit *ainit = (PHYSFS_AndroidInit *) argv0;
+ char *retval = NULL;
+ JNIEnv *jenv = NULL;
+ jobject jcontext;
+
+ if (ainit == NULL)
+ return __PHYSFS_strdup("/"); /* oh well. */
+
+ jenv = (JNIEnv *) ainit->jnienv;
+ jcontext = (jobject) ainit->context;
+
+ if ((*jenv)->PushLocalFrame(jenv, 16) >= 0)
+ {
+ jobject jfileobj = 0;
+ jmethodID jmeth = 0;
+ jthrowable jexception = 0;
+ jstring jstr = 0;
+
+ jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jcontext), "getPackageResourcePath", "()Ljava/lang/String;");
+ jstr = (jstring)(*jenv)->CallObjectMethod(jenv, jcontext, jmeth);
+ jexception = (*jenv)->ExceptionOccurred(jenv); /* this can't throw an exception, right? Just in case. */
+ if (jexception != NULL)
+ (*jenv)->ExceptionClear(jenv);
+ else
+ {
+ const char *path = (*jenv)->GetStringUTFChars(jenv, jstr, NULL);
+ retval = __PHYSFS_strdup(path);
+ (*jenv)->ReleaseStringUTFChars(jenv, jstr, path);
+ } /* else */
+
+ /* We only can rely on the Activity being valid during this function call,
+ so go ahead and grab the prefpath too. */
+ jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jcontext), "getFilesDir", "()Ljava/io/File;");
+ jfileobj = (*jenv)->CallObjectMethod(jenv, jcontext, jmeth);
+ if (jfileobj)
+ {
+ jmeth = (*jenv)->GetMethodID(jenv, (*jenv)->GetObjectClass(jenv, jfileobj), "getCanonicalPath", "()Ljava/lang/String;");
+ jstr = (jstring)(*jenv)->CallObjectMethod(jenv, jfileobj, jmeth);
+ jexception = (*jenv)->ExceptionOccurred(jenv);
+ if (jexception != NULL)
+ (*jenv)->ExceptionClear(jenv);
+ else
+ {
+ const char *path = (*jenv)->GetStringUTFChars(jenv, jstr, NULL);
+ const size_t len = strlen(path) + 2;
+ prefpath = allocator.Malloc(len);
+ if (prefpath)
+ snprintf(prefpath, len, "%s/", path);
+ (*jenv)->ReleaseStringUTFChars(jenv, jstr, path);
+ } /* else */
+ } /* if */
+
+ (*jenv)->PopLocalFrame(jenv, NULL);
+ } /* if */
+
+ /* we can't return NULL because then PhysicsFS will treat argv0 as a string, but it's a non-NULL jobject! */
+ if (retval == NULL)
+ retval = __PHYSFS_strdup("/"); /* we pray this works. */
+
+ return retval;
+} /* __PHYSFS_platformCalcBaseDir */
+
+
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
+{
+ return __PHYSFS_strdup(prefpath ? prefpath : "/");
+} /* __PHYSFS_platformCalcPrefDir */
+
+#endif /* PHYSFS_PLATFORM_ANDROID */
+
+/* end of physfs_platform_android.c ... */
+
diff --git a/third-party/physfs/src/physfs_platform_apple.m b/third-party/physfs/src/physfs_platform_apple.m
new file mode 100644
index 0000000..f98f10d
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_apple.m
@@ -0,0 +1,201 @@
+/*
+ * Apple platform (macOS, iOS, watchOS, etc) support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_APPLE
+
+#include
+#include
+
+#include "physfs_internal.h"
+
+int __PHYSFS_platformInit(void)
+{
+ return 1; /* success. */
+} /* __PHYSFS_platformInit */
+
+
+void __PHYSFS_platformDeinit(void)
+{
+ /* no-op */
+} /* __PHYSFS_platformDeinit */
+
+
+char *__PHYSFS_platformCalcBaseDir(const char *argv0)
+{
+ @autoreleasepool
+ {
+ NSString *path = [[NSBundle mainBundle] bundlePath];
+ BAIL_IF(!path, PHYSFS_ERR_OS_ERROR, NULL);
+ size_t len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ char *retval = (char *) allocator.Malloc(len + 2);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ [path getCString:retval maxLength:len+1 encoding:NSUTF8StringEncoding];
+ retval[len] = '/';
+ retval[len+1] = '\0';
+ return retval; /* whew. */
+ } /* @autoreleasepool */
+} /* __PHYSFS_platformCalcBaseDir */
+
+
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
+{
+ @autoreleasepool
+ {
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, TRUE);
+ BAIL_IF(!paths, PHYSFS_ERR_OS_ERROR, NULL);
+ NSString *path = (NSString *) [paths objectAtIndex:0];
+ BAIL_IF(!path, PHYSFS_ERR_OS_ERROR, NULL);
+ size_t len = [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+ const size_t applen = strlen(app);
+ char *retval = (char *) allocator.Malloc(len + applen + 3);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ [path getCString:retval maxLength:len+1 encoding:NSUTF8StringEncoding];
+ snprintf(retval + len, applen + 3, "/%s/", app);
+ return retval; /* whew. */
+ } /* @autoreleasepool */
+} /* __PHYSFS_platformCalcPrefDir */
+
+
+/* CD-ROM detection code... */
+
+/*
+ * Code based on sample from Apple Developer Connection:
+ * https://developer.apple.com/samplecode/Sample_Code/Devices_and_Hardware/Disks/VolumeToBSDNode/VolumeToBSDNode.c.htm
+ */
+
+#if !defined(PHYSFS_NO_CDROM_SUPPORT)
+
+#include
+#include
+#include
+#include
+#include
+
+static int darwinIsWholeMedia(io_service_t service)
+{
+ int retval = 0;
+ CFTypeRef wholeMedia;
+
+ if (!IOObjectConformsTo(service, kIOMediaClass))
+ return 0;
+
+ wholeMedia = IORegistryEntryCreateCFProperty(service,
+ CFSTR(kIOMediaWholeKey),
+ NULL, 0);
+ if (wholeMedia == NULL)
+ return 0;
+
+ retval = CFBooleanGetValue(wholeMedia);
+ CFRelease(wholeMedia);
+
+ return retval;
+} /* darwinIsWholeMedia */
+
+
+static int darwinIsMountedDisc(char *bsdName, mach_port_t mainPort)
+{
+ int retval = 0;
+ CFMutableDictionaryRef matchingDict;
+ kern_return_t rc;
+ io_iterator_t iter;
+ io_service_t service;
+
+ if ((matchingDict = IOBSDNameMatching(mainPort, 0, bsdName)) == NULL)
+ return 0;
+
+ rc = IOServiceGetMatchingServices(mainPort, matchingDict, &iter);
+ if ((rc != KERN_SUCCESS) || (!iter))
+ return 0;
+
+ service = IOIteratorNext(iter);
+ IOObjectRelease(iter);
+ if (!service)
+ return 0;
+
+ rc = IORegistryEntryCreateIterator(service, kIOServicePlane,
+ kIORegistryIterateRecursively | kIORegistryIterateParents, &iter);
+
+ if (!iter)
+ return 0;
+
+ if (rc != KERN_SUCCESS)
+ {
+ IOObjectRelease(iter);
+ return 0;
+ } /* if */
+
+ IOObjectRetain(service); /* add an extra object reference... */
+
+ do
+ {
+ if (darwinIsWholeMedia(service))
+ {
+ if ( (IOObjectConformsTo(service, kIOCDMediaClass)) ||
+ (IOObjectConformsTo(service, kIODVDMediaClass)) )
+ {
+ retval = 1;
+ } /* if */
+ } /* if */
+ IOObjectRelease(service);
+ } while ((service = IOIteratorNext(iter)) && (!retval));
+
+ IOObjectRelease(iter);
+ IOObjectRelease(service);
+
+ return retval;
+} /* darwinIsMountedDisc */
+
+#endif /* !defined(PHYSFS_NO_CDROM_SUPPORT) */
+
+
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+#if !defined(PHYSFS_NO_CDROM_SUPPORT)
+ /* macOS 12.0 changed "master" names to "main". */
+ typedef kern_return_t (*ioMainPortFn)(mach_port_t, mach_port_t *);
+ static ioMainPortFn ioMainPort = NULL;
+ const char *devPrefix = "/dev/";
+ const int prefixLen = strlen(devPrefix);
+ mach_port_t mainPort = 0;
+ struct statfs *mntbufp;
+ int i, mounts;
+
+ if (ioMainPort == NULL)
+ {
+ ioMainPort = (ioMainPortFn) dlsym(RTLD_DEFAULT, "IOMainPort");
+ if (!ioMainPort)
+ ioMainPort = (ioMainPortFn) dlsym(RTLD_DEFAULT, "IOMasterPort");
+ if (!ioMainPort)
+ return; /* oh well, no CD-ROMs for you. */
+ } /* if */
+
+ if (ioMainPort(MACH_PORT_NULL, &mainPort) != KERN_SUCCESS)
+ BAIL(PHYSFS_ERR_OS_ERROR, ) /*return void*/;
+
+ mounts = getmntinfo(&mntbufp, MNT_WAIT); /* NOT THREAD SAFE! */
+ for (i = 0; i < mounts; i++)
+ {
+ char *dev = mntbufp[i].f_mntfromname;
+ char *mnt = mntbufp[i].f_mntonname;
+ if (strncmp(dev, devPrefix, prefixLen) != 0) /* a virtual device? */
+ continue;
+
+ dev += prefixLen;
+ if (darwinIsMountedDisc(dev, mainPort))
+ cb(data, mnt);
+ } /* for */
+#endif /* !defined(PHYSFS_NO_CDROM_SUPPORT) */
+} /* __PHYSFS_platformDetectAvailableCDs */
+
+#endif /* PHYSFS_PLATFORM_APPLE */
+
+/* end of physfs_platform_apple.m ... */
+
diff --git a/third-party/physfs/src/physfs_platform_haiku.cpp b/third-party/physfs/src/physfs_platform_haiku.cpp
new file mode 100644
index 0000000..fcf8ebd
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_haiku.cpp
@@ -0,0 +1,186 @@
+/*
+ * Haiku platform-dependent support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_HAIKU
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "physfs_internal.h"
+
+int __PHYSFS_platformInit(void)
+{
+ return 1; /* always succeed. */
+} /* __PHYSFS_platformInit */
+
+
+void __PHYSFS_platformDeinit(void)
+{
+ /* no-op */
+} /* __PHYSFS_platformDeinit */
+
+
+static char *getMountPoint(const char *devname, char *buf, size_t bufsize)
+{
+ BVolumeRoster mounts;
+ BVolume vol;
+
+ mounts.Rewind();
+ while (mounts.GetNextVolume(&vol) == B_NO_ERROR)
+ {
+ fs_info fsinfo;
+ fs_stat_dev(vol.Device(), &fsinfo);
+ if (strcmp(devname, fsinfo.device_name) == 0)
+ {
+ BDirectory directory;
+ BEntry entry;
+ BPath path;
+ const char *str;
+
+ if ( (vol.GetRootDirectory(&directory) < B_OK) ||
+ (directory.GetEntry(&entry) < B_OK) ||
+ (entry.GetPath(&path) < B_OK) ||
+ ( (str = path.Path()) == NULL) )
+ return NULL;
+
+ strncpy(buf, str, bufsize-1);
+ buf[bufsize-1] = '\0';
+ return buf;
+ } /* if */
+ } /* while */
+
+ return NULL;
+} /* getMountPoint */
+
+
+ /*
+ * This function is lifted from Simple Directmedia Layer (SDL):
+ * https://www.libsdl.org/ ... this is zlib-licensed code, too.
+ */
+static void tryDir(const char *d, PHYSFS_StringCallback callback, void *data)
+{
+ BDirectory dir;
+ dir.SetTo(d);
+ if (dir.InitCheck() != B_NO_ERROR)
+ return;
+
+ dir.Rewind();
+ BEntry entry;
+ while (dir.GetNextEntry(&entry) >= 0)
+ {
+ BPath path;
+ const char *name;
+ entry_ref e;
+
+ if (entry.GetPath(&path) != B_NO_ERROR)
+ continue;
+
+ name = path.Path();
+
+ if (entry.GetRef(&e) != B_NO_ERROR)
+ continue;
+
+ if (entry.IsDirectory())
+ {
+ if (strcmp(e.name, "floppy") != 0)
+ tryDir(name, callback, data);
+ continue;
+ } /* if */
+
+ const int devfd = open(name, O_RDONLY);
+ if (devfd < 0)
+ continue;
+
+ device_geometry g;
+ const int rc = ioctl(devfd, B_GET_GEOMETRY, &g, sizeof (g));
+ close(devfd);
+ if (rc < 0)
+ continue;
+
+ if (g.device_type != B_CD)
+ continue;
+
+ char mntpnt[B_FILE_NAME_LENGTH];
+ if (getMountPoint(name, mntpnt, sizeof (mntpnt)))
+ callback(data, mntpnt);
+ } /* while */
+} /* tryDir */
+
+
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+ tryDir("/dev/disk", cb, data);
+} /* __PHYSFS_platformDetectAvailableCDs */
+
+
+static team_id getTeamID(void)
+{
+ thread_info info;
+ thread_id tid = find_thread(NULL);
+ get_thread_info(tid, &info);
+ return info.team;
+} /* getTeamID */
+
+
+char *__PHYSFS_platformCalcBaseDir(const char *argv0)
+{
+ image_info info;
+ int32 cookie = 0;
+
+ while (get_next_image_info(0, &cookie, &info) == B_OK)
+ {
+ if (info.type == B_APP_IMAGE)
+ break;
+ } /* while */
+
+ BEntry entry(info.name, true);
+ BPath path;
+ status_t rc = entry.GetPath(&path); /* (path) now has binary's path. */
+ assert(rc == B_OK);
+ rc = path.GetParent(&path); /* chop filename, keep directory. */
+ assert(rc == B_OK);
+ const char *str = path.Path();
+ assert(str != NULL);
+ const size_t len = strlen(str);
+ char *retval = (char *) allocator.Malloc(len + 2);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ strcpy(retval, str);
+ retval[len] = '/';
+ retval[len+1] = '\0';
+ return retval;
+} /* __PHYSFS_platformCalcBaseDir */
+
+
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
+{
+ const char *userdir = __PHYSFS_getUserDir();
+ const char *append = "config/settings/";
+ const size_t len = strlen(userdir) + strlen(append) + strlen(app) + 2;
+ char *retval = (char *) allocator.Malloc(len);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ snprintf(retval, len, "%s%s%s/", userdir, append, app);
+ return retval;
+} /* __PHYSFS_platformCalcPrefDir */
+
+#endif /* PHYSFS_PLATFORM_HAIKU */
+
+/* end of physfs_platform_haiku.cpp ... */
+
diff --git a/third-party/physfs/src/physfs_platform_os2.c b/third-party/physfs/src/physfs_platform_os2.c
new file mode 100644
index 0000000..bf47a9d
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_os2.c
@@ -0,0 +1,812 @@
+/*
+ * OS/2 support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_OS2
+
+#define INCL_DOSMODULEMGR
+#define INCL_DOSSEMAPHORES
+#define INCL_DOSDATETIME
+#define INCL_DOSFILEMGR
+#define INCL_DOSMODULEMGR
+#define INCL_DOSERRORS
+#define INCL_DOSPROCESS
+#define INCL_DOSDEVICES
+#define INCL_DOSDEVIOCTL
+#define INCL_DOSMISC
+#include
+#include
+
+#include
+#include
+#include
+
+#include "physfs_internal.h"
+
+static HMODULE uconvdll = 0;
+static UconvObject uconv = 0;
+static int (_System *pUniCreateUconvObject)(UniChar *, UconvObject *) = NULL;
+static int (_System *pUniFreeUconvObject)(UconvObject *) = NULL;
+static int (_System *pUniUconvToUcs)(UconvObject,void **,size_t *, UniChar**, size_t *, size_t *) = NULL;
+static int (_System *pUniUconvFromUcs)(UconvObject,UniChar **,size_t *,void **,size_t *,size_t *) = NULL;
+
+static PHYSFS_ErrorCode errcodeFromAPIRET(const APIRET rc)
+{
+ switch (rc)
+ {
+ case NO_ERROR: return PHYSFS_ERR_OK; /* not an error. */
+ case ERROR_INTERRUPT: return PHYSFS_ERR_OK; /* not an error. */
+ case ERROR_TIMEOUT: return PHYSFS_ERR_OK; /* not an error. */
+ case ERROR_NOT_ENOUGH_MEMORY: return PHYSFS_ERR_OUT_OF_MEMORY;
+ case ERROR_FILE_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND;
+ case ERROR_PATH_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND;
+ case ERROR_ACCESS_DENIED: return PHYSFS_ERR_PERMISSION;
+ case ERROR_NOT_DOS_DISK: return PHYSFS_ERR_NOT_FOUND;
+ case ERROR_SHARING_VIOLATION: return PHYSFS_ERR_PERMISSION;
+ case ERROR_CANNOT_MAKE: return PHYSFS_ERR_IO; /* maybe this is wrong? */
+ case ERROR_DEVICE_IN_USE: return PHYSFS_ERR_BUSY;
+ case ERROR_OPEN_FAILED: return PHYSFS_ERR_IO; /* maybe this is wrong? */
+ case ERROR_DISK_FULL: return PHYSFS_ERR_NO_SPACE;
+ case ERROR_PIPE_BUSY: return PHYSFS_ERR_BUSY;
+ case ERROR_SHARING_BUFFER_EXCEEDED: return PHYSFS_ERR_IO;
+ case ERROR_FILENAME_EXCED_RANGE: return PHYSFS_ERR_BAD_FILENAME;
+ case ERROR_META_EXPANSION_TOO_LONG: return PHYSFS_ERR_BAD_FILENAME;
+ case ERROR_TOO_MANY_HANDLES: return PHYSFS_ERR_IO;
+ case ERROR_TOO_MANY_OPEN_FILES: return PHYSFS_ERR_IO;
+ case ERROR_NO_MORE_SEARCH_HANDLES: return PHYSFS_ERR_IO;
+ case ERROR_SEEK_ON_DEVICE: return PHYSFS_ERR_IO;
+ case ERROR_NEGATIVE_SEEK: return PHYSFS_ERR_INVALID_ARGUMENT;
+ case ERROR_WRITE_PROTECT: return PHYSFS_ERR_PERMISSION;
+ case ERROR_WRITE_FAULT: return PHYSFS_ERR_IO;
+ case ERROR_UNCERTAIN_MEDIA: return PHYSFS_ERR_IO;
+ case ERROR_PROTECTION_VIOLATION: return PHYSFS_ERR_IO;
+ case ERROR_BROKEN_PIPE: return PHYSFS_ERR_IO;
+
+ /* !!! FIXME: some of these might be PHYSFS_ERR_BAD_FILENAME, etc */
+ case ERROR_LOCK_VIOLATION:
+ case ERROR_GEN_FAILURE:
+ case ERROR_INVALID_PARAMETER:
+ case ERROR_INVALID_NAME:
+ case ERROR_INVALID_DRIVE:
+ case ERROR_INVALID_HANDLE:
+ case ERROR_INVALID_FUNCTION:
+ case ERROR_INVALID_LEVEL:
+ case ERROR_INVALID_CATEGORY:
+ case ERROR_DUPLICATE_NAME:
+ case ERROR_BUFFER_OVERFLOW:
+ case ERROR_BAD_LENGTH:
+ case ERROR_BAD_DRIVER_LEVEL:
+ case ERROR_DIRECT_ACCESS_HANDLE:
+ case ERROR_NOT_OWNER:
+ return PHYSFS_ERR_OS_ERROR;
+
+ default: break;
+ } /* switch */
+
+ return PHYSFS_ERR_OTHER_ERROR;
+} /* errcodeFromAPIRET */
+
+static char *cvtUtf8ToCodepage(const char *utf8str)
+{
+ const size_t len = strlen(utf8str) + 1;
+ const size_t uc2buflen = len * sizeof (UniChar);
+ UniChar *uc2ptr = (UniChar *) __PHYSFS_smallAlloc(uc2buflen);
+ UniChar *uc2str = uc2ptr;
+ char *cpptr = NULL;
+ char *cpstr = NULL;
+ size_t subs = 0;
+ size_t unilen;
+
+ BAIL_IF(!uc2str, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ PHYSFS_utf8ToUcs2(utf8str, (PHYSFS_uint16 *) uc2str, uc2buflen);
+ for (unilen = 0; uc2str[unilen]; unilen++) { /* spin */ }
+ unilen++; /* null terminator. */
+
+ if (!uconvdll)
+ {
+ /* There's really not much we can do on older OS/2s except pray this
+ is latin1-compatible. */
+ size_t i;
+ cpptr = (char *) allocator.Malloc(unilen);
+ cpstr = cpptr;
+ GOTO_IF(!cpptr, PHYSFS_ERR_OUT_OF_MEMORY, failed);
+ for (i = 0; i < unilen; i++)
+ {
+ const UniChar ch = uc2str[i];
+ GOTO_IF(ch > 0xFF, PHYSFS_ERR_BAD_FILENAME, failed);
+ cpptr[i] = (char) ((unsigned char) ch);
+ } /* for */
+
+ __PHYSFS_smallFree(uc2ptr);
+ return cpstr;
+ } /* if */
+ else
+ {
+ int rc;
+ size_t cplen = unilen * 4; /* overallocate, just in case. */
+ cpptr = (char *) allocator.Malloc(cplen);
+ GOTO_IF(!cpptr, PHYSFS_ERR_OUT_OF_MEMORY, failed);
+ cpstr = cpptr;
+
+ rc = pUniUconvFromUcs(uconv, &uc2str, &unilen, (void **) &cpstr, &cplen, &subs);
+ GOTO_IF(rc != ULS_SUCCESS, PHYSFS_ERR_BAD_FILENAME, failed);
+ GOTO_IF(subs > 0, PHYSFS_ERR_BAD_FILENAME, failed);
+ assert(unilen == 0);
+
+ __PHYSFS_smallFree(uc2ptr);
+ return cpptr;
+ } /* else */
+
+failed:
+ __PHYSFS_smallFree(uc2ptr);
+ allocator.Free(cpptr);
+
+ return NULL;
+} /* cvtUtf8ToCodepage */
+
+static char *cvtCodepageToUtf8(const char *cpstr)
+{
+ const size_t len = strlen(cpstr) + 1;
+ char *retvalbuf = (char *) allocator.Malloc(len * 4);
+ char *retval = NULL;
+
+ BAIL_IF(!retvalbuf, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+
+ if (!uconvdll)
+ {
+ /* There's really not much we can do on older OS/2s except pray this
+ is latin1-compatible. */
+ retval = retvalbuf;
+ PHYSFS_utf8FromLatin1(cpstr, retval, len * 4);
+ } /* if */
+ else
+ {
+ int rc;
+ size_t cplen = len;
+ size_t unilen = len;
+ size_t subs = 0;
+ UniChar *uc2ptr = __PHYSFS_smallAlloc(len * sizeof (UniChar));
+ UniChar *uc2str = uc2ptr;
+
+ BAIL_IF(!uc2ptr, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ rc = pUniUconvToUcs(uconv, (void **) &cpstr, &cplen, &uc2str, &unilen, &subs);
+ GOTO_IF(rc != ULS_SUCCESS, PHYSFS_ERR_BAD_FILENAME, done);
+ GOTO_IF(subs > 0, PHYSFS_ERR_BAD_FILENAME, done);
+ assert(cplen == 0);
+ retval = retvalbuf;
+ PHYSFS_utf8FromUcs2((const PHYSFS_uint16 *) uc2ptr, retval, len * 4);
+ done:
+ __PHYSFS_smallFree(uc2ptr);
+ } /* else */
+
+ return retval;
+} /* cvtCodepageToUtf8 */
+
+
+/* (be gentle, this function isn't very robust.) */
+static char *cvtPathToCorrectCase(char *buf)
+{
+ char *retval = buf;
+ char *fname = buf + 3; /* point to first element. */
+ char *ptr = strchr(fname, '\\'); /* find end of first element. */
+
+ buf[0] = toupper(buf[0]); /* capitalize drive letter. */
+
+ /*
+ * Go through each path element, and enumerate its parent dir until
+ * a case-insensitive match is found. If one is (and it SHOULD be)
+ * then overwrite the original element with the correct case.
+ * If there's an error, or the path has vanished for some reason, it
+ * won't hurt to have the original case, so we just keep going.
+ */
+ while ((fname != NULL) && (*fname != '\0'))
+ {
+ char spec[CCHMAXPATH];
+ FILEFINDBUF3 fb;
+ HDIR hdir = HDIR_CREATE;
+ ULONG count = 1;
+ APIRET rc;
+
+ *(fname - 1) = '\0'; /* isolate parent dir string. */
+
+ strcpy(spec, buf); /* copy isolated parent dir... */
+ strcat(spec, "\\*.*"); /* ...and add wildcard search spec. */
+
+ if (ptr != NULL) /* isolate element to find (fname is the start). */
+ *ptr = '\0';
+
+ rc = DosFindFirst(spec, &hdir, FILE_DIRECTORY,
+ &fb, sizeof (fb), &count, FIL_STANDARD);
+ if (rc == NO_ERROR)
+ {
+ while (count == 1) /* while still entries to enumerate... */
+ {
+ int cmp;
+ char *utf8 = cvtCodepageToUtf8(fb.achName);
+ if (!utf8) /* ugh, maybe we'll get lucky with the C runtime. */
+ cmp = stricmp(fb.achName, fname);
+ else
+ {
+ cmp = PHYSFS_utf8stricmp(utf8, fname);
+ allocator.Free(utf8);
+ } /* else */
+
+ if (cmp == 0)
+ {
+ strcpy(fname, fb.achName);
+ break; /* there it is. Overwrite and stop searching. */
+ } /* if */
+
+ DosFindNext(hdir, &fb, sizeof (fb), &count);
+ } /* while */
+ DosFindClose(hdir);
+ } /* if */
+
+ *(fname - 1) = '\\'; /* unisolate parent dir. */
+ fname = ptr; /* point to next element. */
+ if (ptr != NULL)
+ {
+ *ptr = '\\'; /* unisolate element. */
+ ptr = strchr(++fname, '\\'); /* find next element. */
+ } /* if */
+ } /* while */
+
+ return retval;
+} /* cvtPathToCorrectCase */
+
+static void prepUnicodeSupport(void)
+{
+ /* really old OS/2 might not have Unicode support _at all_, so load
+ the system library and do without if it doesn't exist. */
+ int ok = 0;
+ char buf[CCHMAXPATH];
+ UniChar defstr[] = { 0 };
+ if (DosLoadModule(buf, sizeof (buf) - 1, "uconv", &uconvdll) == NO_ERROR)
+ {
+ #define LOAD(x) (DosQueryProcAddr(uconvdll,0,#x,(PFN*)&p##x)==NO_ERROR)
+ ok = LOAD(UniCreateUconvObject) &&
+ LOAD(UniFreeUconvObject) &&
+ LOAD(UniUconvToUcs) &&
+ LOAD(UniUconvFromUcs);
+ #undef LOAD
+ } /* else */
+
+ if (!ok || (pUniCreateUconvObject(defstr, &uconv) != ULS_SUCCESS))
+ {
+ /* oh well, live without it. */
+ if (uconvdll)
+ {
+ if (uconv)
+ pUniFreeUconvObject(uconv);
+ DosFreeModule(uconvdll);
+ uconvdll = 0;
+ } /* if */
+ } /* if */
+} /* prepUnicodeSupport */
+
+
+int __PHYSFS_platformInit(void)
+{
+ prepUnicodeSupport();
+ return 1; /* ready to go! */
+} /* __PHYSFS_platformInit */
+
+
+void __PHYSFS_platformDeinit(void)
+{
+ if (uconvdll)
+ {
+ pUniFreeUconvObject(uconv);
+ uconv = 0;
+ DosFreeModule(uconvdll);
+ uconvdll = 0;
+ } /* if */
+} /* __PHYSFS_platformDeinit */
+
+
+static int discIsInserted(ULONG drive)
+{
+ int rc;
+ char buf[20];
+ DosError(FERR_DISABLEHARDERR | FERR_DISABLEEXCEPTION);
+ rc = DosQueryFSInfo(drive + 1, FSIL_VOLSER, buf, sizeof (buf));
+ DosError(FERR_ENABLEHARDERR | FERR_ENABLEEXCEPTION);
+ return (rc == NO_ERROR);
+} /* is_cdrom_inserted */
+
+
+/* looks like "CD01" in ASCII (littleendian)...used for an ioctl. */
+#define CD01 0x31304443
+
+static int isCdRomDrive(ULONG drive)
+{
+ PHYSFS_uint32 param, data;
+ ULONG ul1, ul2;
+ APIRET rc;
+ HFILE hfile = NULLHANDLE;
+ char drivename[3] = { 0, ':', '\0' };
+
+ drivename[0] = 'A' + drive;
+
+ rc = DosOpen(drivename, &hfile, &ul1, 0, 0,
+ OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW,
+ OPEN_FLAGS_DASD | OPEN_FLAGS_FAIL_ON_ERROR |
+ OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYNONE, NULL);
+ if (rc != NO_ERROR)
+ return 0;
+
+ data = 0;
+ param = PHYSFS_swapULE32(CD01);
+ ul1 = ul2 = sizeof (PHYSFS_uint32);
+ rc = DosDevIOCtl(hfile, IOCTL_CDROMDISK, CDROMDISK_GETDRIVER,
+ ¶m, sizeof (param), &ul1, &data, sizeof (data), &ul2);
+
+ DosClose(hfile);
+ return ((rc == NO_ERROR) && (PHYSFS_swapULE32(data) == CD01));
+} /* isCdRomDrive */
+
+
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+ ULONG dummy = 0;
+ ULONG drivemap = 0;
+ ULONG i, bit;
+ const APIRET rc = DosQueryCurrentDisk(&dummy, &drivemap);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc),);
+
+ for (i = 0, bit = 1; i < 26; i++, bit <<= 1)
+ {
+ if (drivemap & bit) /* this logical drive exists. */
+ {
+ if ((isCdRomDrive(i)) && (discIsInserted(i)))
+ {
+ char drive[4] = "x:\\";
+ drive[0] = ('A' + i);
+ cb(data, drive);
+ } /* if */
+ } /* if */
+ } /* for */
+} /* __PHYSFS_platformDetectAvailableCDs */
+
+
+char *__PHYSFS_platformCalcBaseDir(const char *argv0)
+{
+ char *retval = NULL;
+ char buf[CCHMAXPATH];
+ APIRET rc;
+ PTIB ptib;
+ PPIB ppib;
+ PHYSFS_sint32 len;
+
+ rc = DosGetInfoBlocks(&ptib, &ppib);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
+ rc = DosQueryModuleName(ppib->pib_hmte, sizeof (buf), (PCHAR) buf);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
+ retval = cvtCodepageToUtf8(buf);
+ BAIL_IF_ERRPASS(!retval, NULL);
+
+ /* chop off filename, leave path. */
+ for (len = strlen(retval) - 1; len >= 0; len--)
+ {
+ if (retval[len] == '\\')
+ {
+ retval[len + 1] = '\0';
+ break;
+ } /* if */
+ } /* for */
+
+ assert(len > 0); /* should have been a "x:\\" on the front on string. */
+
+ /* The string is capitalized! Figure out the REAL case... */
+ return cvtPathToCorrectCase(retval);
+} /* __PHYSFS_platformCalcBaseDir */
+
+char *__PHYSFS_platformCalcUserDir(void)
+{
+ return __PHYSFS_platformCalcBaseDir(NULL); /* !!! FIXME: ? */
+} /* __PHYSFS_platformCalcUserDir */
+
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
+{
+ return __PHYSFS_platformCalcBaseDir(NULL); /* !!! FIXME: ? */
+} /* __PHYSFS_platformCalcPrefDir */
+
+PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
+ PHYSFS_EnumerateCallback callback,
+ const char *origdir, void *callbackdata)
+{
+ PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
+ size_t utf8len = strlen(dirname);
+ char *utf8 = (char *) __PHYSFS_smallAlloc(utf8len + 5);
+ char *cpspec = NULL;
+ FILEFINDBUF3 fb;
+ HDIR hdir = HDIR_CREATE;
+ ULONG count = 1;
+ APIRET rc;
+
+ BAIL_IF(!utf8, PHYSFS_ERR_OUT_OF_MEMORY, PHYSFS_ENUM_ERROR);
+
+ strcpy(utf8, dirname);
+ if (utf8[utf8len - 1] != '\\')
+ strcpy(utf8 + utf8len, "\\*.*");
+ else
+ strcpy(utf8 + utf8len, "*.*");
+
+ cpspec = cvtUtf8ToCodepage(utf8);
+ __PHYSFS_smallFree(utf8);
+ BAIL_IF_ERRPASS(!cpspec, PHYSFS_ENUM_ERROR);
+
+ rc = DosFindFirst(cpspec, &hdir,
+ FILE_DIRECTORY | FILE_ARCHIVED |
+ FILE_READONLY | FILE_HIDDEN | FILE_SYSTEM,
+ &fb, sizeof (fb), &count, FIL_STANDARD);
+ allocator.Free(cpspec);
+
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), PHYSFS_ENUM_ERROR);
+
+ while (count == 1)
+ {
+ if ((strcmp(fb.achName, ".") != 0) && (strcmp(fb.achName, "..") != 0))
+ {
+ utf8 = cvtCodepageToUtf8(fb.achName);
+ if (!utf8)
+ retval = PHYSFS_ENUM_ERROR;
+ else
+ {
+ retval = callback(callbackdata, origdir, utf8);
+ allocator.Free(utf8);
+ if (retval == PHYSFS_ENUM_ERROR)
+ PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK);
+ } /* else */
+ } /* if */
+
+ if (retval != PHYSFS_ENUM_OK)
+ break;
+
+ DosFindNext(hdir, &fb, sizeof (fb), &count);
+ } /* while */
+
+ DosFindClose(hdir);
+
+ return retval;
+} /* __PHYSFS_platformEnumerate */
+
+
+char *__PHYSFS_platformCurrentDir(void)
+{
+ char *retval;
+ char *cpstr;
+ char *utf8;
+ ULONG currentDisk;
+ ULONG dummy;
+ ULONG pathSize = 0;
+ APIRET rc;
+ BYTE byte;
+
+ rc = DosQueryCurrentDisk(¤tDisk, &dummy);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), NULL);
+
+ /* The first call just tells us how much space we need for the string. */
+ rc = DosQueryCurrentDir(currentDisk, &byte, &pathSize);
+ pathSize++; /* Add space for null terminator. */
+ cpstr = (char *) __PHYSFS_smallAlloc(pathSize);
+ BAIL_IF(cpstr == NULL, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+
+ /* Actually get the string this time. */
+ rc = DosQueryCurrentDir(currentDisk, (PBYTE) cpstr, &pathSize);
+ if (rc != NO_ERROR)
+ {
+ __PHYSFS_smallFree(cpstr);
+ BAIL(errcodeFromAPIRET(rc), NULL);
+ } /* if */
+
+ utf8 = cvtCodepageToUtf8(cpstr);
+ __PHYSFS_smallFree(cpstr);
+ BAIL_IF_ERRPASS(utf8 == NULL, NULL);
+
+ /* +4 for "x:\\" drive selector and null terminator. */
+ retval = (char *) allocator.Malloc(strlen(utf8) + 4);
+ if (retval == NULL)
+ {
+ allocator.Free(utf8);
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ } /* if */
+
+ retval[0] = ('A' + (currentDisk - 1));
+ retval[1] = ':';
+ retval[2] = '\\';
+ strcpy(retval + 3, utf8);
+
+ allocator.Free(utf8);
+
+ return retval;
+} /* __PHYSFS_platformCurrentDir */
+
+
+int __PHYSFS_platformMkDir(const char *filename)
+{
+ APIRET rc;
+ char *cpstr = cvtUtf8ToCodepage(filename);
+ BAIL_IF_ERRPASS(!cpstr, 0);
+ rc = DosCreateDir(cpstr, NULL);
+ allocator.Free(cpstr);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
+ return 1;
+} /* __PHYSFS_platformMkDir */
+
+
+static HFILE openFile(const char *filename, const ULONG flags, const ULONG mode)
+{
+ char *cpfname = cvtUtf8ToCodepage(filename);
+ ULONG action = 0;
+ HFILE hfile = NULLHANDLE;
+ APIRET rc;
+
+ BAIL_IF_ERRPASS(!cpfname, 0);
+
+ rc = DosOpen(cpfname, &hfile, &action, 0, FILE_NORMAL, flags, mode, NULL);
+ allocator.Free(cpfname);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
+
+ return hfile;
+} /* openFile */
+
+void *__PHYSFS_platformOpenRead(const char *filename)
+{
+ /*
+ * File must be opened SHARE_DENYWRITE and ACCESS_READONLY, otherwise
+ * DosQueryFileInfo() will fail if we try to get a file length, etc.
+ */
+ return (void *) openFile(filename,
+ OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW,
+ OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_LOCALITY |
+ OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYWRITE |
+ OPEN_ACCESS_READONLY);
+} /* __PHYSFS_platformOpenRead */
+
+
+void *__PHYSFS_platformOpenWrite(const char *filename)
+{
+ return (void *) openFile(filename,
+ OPEN_ACTION_REPLACE_IF_EXISTS |
+ OPEN_ACTION_CREATE_IF_NEW,
+ OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_LOCALITY |
+ OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYWRITE);
+} /* __PHYSFS_platformOpenWrite */
+
+
+void *__PHYSFS_platformOpenAppend(const char *filename)
+{
+ APIRET rc;
+ ULONG dummy = 0;
+ HFILE hfile;
+
+ /*
+ * File must be opened SHARE_DENYWRITE and ACCESS_READWRITE, otherwise
+ * DosQueryFileInfo() will fail if we try to get a file length, etc.
+ */
+ hfile = openFile(filename,
+ OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW,
+ OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_LOCALITY |
+ OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYWRITE |
+ OPEN_ACCESS_READWRITE);
+ BAIL_IF_ERRPASS(!hfile, NULL);
+
+ rc = DosSetFilePtr(hfile, 0, FILE_END, &dummy);
+ if (rc != NO_ERROR)
+ {
+ DosClose(hfile);
+ BAIL(errcodeFromAPIRET(rc), NULL);
+ } /* if */
+
+ return ((void *) hfile);
+} /* __PHYSFS_platformOpenAppend */
+
+
+PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buf, PHYSFS_uint64 len)
+{
+ ULONG br = 0;
+ APIRET rc;
+ BAIL_IF(!__PHYSFS_ui64FitsAddressSpace(len),PHYSFS_ERR_INVALID_ARGUMENT,-1);
+ rc = DosRead((HFILE) opaque, buf, (ULONG) len, &br);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), (br > 0) ? ((PHYSFS_sint64) br) : -1);
+ return (PHYSFS_sint64) br;
+} /* __PHYSFS_platformRead */
+
+
+PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buf,
+ PHYSFS_uint64 len)
+{
+ ULONG bw = 0;
+ APIRET rc;
+ BAIL_IF(!__PHYSFS_ui64FitsAddressSpace(len),PHYSFS_ERR_INVALID_ARGUMENT,-1);
+ rc = DosWrite((HFILE) opaque, (void *) buf, (ULONG) len, &bw);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), (bw > 0) ? ((PHYSFS_sint64) bw) : -1);
+ return (PHYSFS_sint64) bw;
+} /* __PHYSFS_platformWrite */
+
+
+int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos)
+{
+ ULONG dummy;
+ HFILE hfile = (HFILE) opaque;
+ LONG dist = (LONG) pos;
+ APIRET rc;
+
+ /* hooray for 32-bit filesystem limits! :) */
+ BAIL_IF((PHYSFS_uint64) dist != pos, PHYSFS_ERR_INVALID_ARGUMENT, 0);
+ rc = DosSetFilePtr(hfile, dist, FILE_BEGIN, &dummy);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
+ return 1;
+} /* __PHYSFS_platformSeek */
+
+
+PHYSFS_sint64 __PHYSFS_platformTell(void *opaque)
+{
+ ULONG pos;
+ HFILE hfile = (HFILE) opaque;
+ const APIRET rc = DosSetFilePtr(hfile, 0, FILE_CURRENT, &pos);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), -1);
+ return ((PHYSFS_sint64) pos);
+} /* __PHYSFS_platformTell */
+
+
+PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
+{
+ FILESTATUS3 fs;
+ HFILE hfile = (HFILE) opaque;
+ const APIRET rc = DosQueryFileInfo(hfile, FIL_STANDARD, &fs, sizeof (fs));
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), -1);
+ return ((PHYSFS_sint64) fs.cbFile);
+} /* __PHYSFS_platformFileLength */
+
+
+int __PHYSFS_platformFlush(void *opaque)
+{
+ const APIRET rc = DosResetBuffer((HFILE) opaque);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
+ return 1;
+} /* __PHYSFS_platformFlush */
+
+
+void __PHYSFS_platformClose(void *opaque)
+{
+ DosClose((HFILE) opaque); /* ignore errors. You should have flushed! */
+} /* __PHYSFS_platformClose */
+
+
+int __PHYSFS_platformDelete(const char *path)
+{
+ char *cppath = cvtUtf8ToCodepage(path);
+ FILESTATUS3 fs;
+ APIRET rc;
+ int retval = 0;
+
+ BAIL_IF_ERRPASS(!cppath, 0);
+ rc = DosQueryPathInfo(cppath, FIL_STANDARD, &fs, sizeof (fs));
+ GOTO_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), done);
+ rc = (fs.attrFile & FILE_DIRECTORY) ? DosDeleteDir(path) : DosDelete(path);
+ GOTO_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), done);
+ retval = 1; /* success */
+
+done:
+ allocator.Free(cppath);
+ return retval;
+} /* __PHYSFS_platformDelete */
+
+
+/* Convert to a format PhysicsFS can grok... */
+PHYSFS_sint64 os2TimeToUnixTime(const FDATE *date, const FTIME *time)
+{
+ struct tm tm;
+
+ tm.tm_sec = ((PHYSFS_uint32) time->twosecs) * 2;
+ tm.tm_min = time->minutes;
+ tm.tm_hour = time->hours;
+ tm.tm_mday = date->day;
+ tm.tm_mon = date->month;
+ tm.tm_year = ((PHYSFS_uint32) date->year) + 80;
+ tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/;
+ tm.tm_yday = -1;
+ tm.tm_isdst = -1;
+
+ return (PHYSFS_sint64) mktime(&tm);
+} /* os2TimeToUnixTime */
+
+
+int __PHYSFS_platformStat(const char *filename, PHYSFS_Stat *stat, const int follow)
+{
+ char *cpfname = cvtUtf8ToCodepage(filename);
+ FILESTATUS3 fs;
+ int retval = 0;
+ APIRET rc;
+
+ BAIL_IF_ERRPASS(!cpfname, 0);
+
+ rc = DosQueryPathInfo(cpfname, FIL_STANDARD, &fs, sizeof (fs));
+ GOTO_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), done);
+
+ if (fs.attrFile & FILE_DIRECTORY)
+ {
+ stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
+ stat->filesize = 0;
+ } /* if */
+ else
+ {
+ stat->filetype = PHYSFS_FILETYPE_REGULAR;
+ stat->filesize = fs.cbFile;
+ } /* else */
+
+ stat->modtime = os2TimeToUnixTime(&fs.fdateLastWrite, &fs.ftimeLastWrite);
+ if (stat->modtime < 0)
+ stat->modtime = 0;
+
+ stat->accesstime = os2TimeToUnixTime(&fs.fdateLastAccess, &fs.ftimeLastAccess);
+ if (stat->accesstime < 0)
+ stat->accesstime = 0;
+
+ stat->createtime = os2TimeToUnixTime(&fs.fdateCreation, &fs.ftimeCreation);
+ if (stat->createtime < 0)
+ stat->createtime = 0;
+
+ stat->readonly = ((fs.attrFile & FILE_READONLY) == FILE_READONLY);
+ return 1; /* success */
+
+done:
+ allocator.Free(cpfname);
+ return retval;
+} /* __PHYSFS_platformStat */
+
+
+void *__PHYSFS_platformGetThreadID(void)
+{
+ PTIB ptib;
+ PPIB ppib;
+
+ /*
+ * Allegedly, this API never fails, but we'll punt and return a
+ * default value (zero might as well do) if it does.
+ */
+ const APIRET rc = DosGetInfoBlocks(&ptib, &ppib);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), 0);
+ return ((void *) ptib->tib_ordinal);
+} /* __PHYSFS_platformGetThreadID */
+
+
+void *__PHYSFS_platformCreateMutex(void)
+{
+ HMTX hmtx = NULLHANDLE;
+ const APIRET rc = DosCreateMutexSem(NULL, &hmtx, 0, 0);
+ BAIL_IF(rc != NO_ERROR, errcodeFromAPIRET(rc), NULL);
+ return ((void *) hmtx);
+} /* __PHYSFS_platformCreateMutex */
+
+
+void __PHYSFS_platformDestroyMutex(void *mutex)
+{
+ DosCloseMutexSem((HMTX) mutex);
+} /* __PHYSFS_platformDestroyMutex */
+
+
+int __PHYSFS_platformGrabMutex(void *mutex)
+{
+ /* Do _NOT_ set the physfs error message in here! */
+ return (DosRequestMutexSem((HMTX) mutex, SEM_INDEFINITE_WAIT) == NO_ERROR);
+} /* __PHYSFS_platformGrabMutex */
+
+
+void __PHYSFS_platformReleaseMutex(void *mutex)
+{
+ DosReleaseMutexSem((HMTX) mutex);
+} /* __PHYSFS_platformReleaseMutex */
+
+#endif /* PHYSFS_PLATFORM_OS2 */
+
+/* end of physfs_platform_os2.c ... */
diff --git a/third-party/physfs/src/physfs_platform_posix.c b/third-party/physfs/src/physfs_platform_posix.c
new file mode 100644
index 0000000..7ba5e10
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_posix.c
@@ -0,0 +1,449 @@
+/*
+ * Posix-esque support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_POSIX
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "physfs_internal.h"
+
+
+static PHYSFS_ErrorCode errcodeFromErrnoError(const int err)
+{
+ switch (err)
+ {
+ case 0: return PHYSFS_ERR_OK;
+ case EACCES: return PHYSFS_ERR_PERMISSION;
+ case EPERM: return PHYSFS_ERR_PERMISSION;
+ case EDQUOT: return PHYSFS_ERR_NO_SPACE;
+ case EIO: return PHYSFS_ERR_IO;
+ case ELOOP: return PHYSFS_ERR_SYMLINK_LOOP;
+ case EMLINK: return PHYSFS_ERR_NO_SPACE;
+ case ENAMETOOLONG: return PHYSFS_ERR_BAD_FILENAME;
+ case ENOENT: return PHYSFS_ERR_NOT_FOUND;
+ case ENOSPC: return PHYSFS_ERR_NO_SPACE;
+ case ENOTDIR: return PHYSFS_ERR_NOT_FOUND;
+ case EISDIR: return PHYSFS_ERR_NOT_A_FILE;
+ case EROFS: return PHYSFS_ERR_READ_ONLY;
+ case ETXTBSY: return PHYSFS_ERR_BUSY;
+ case EBUSY: return PHYSFS_ERR_BUSY;
+ case ENOMEM: return PHYSFS_ERR_OUT_OF_MEMORY;
+ case ENOTEMPTY: return PHYSFS_ERR_DIR_NOT_EMPTY;
+ default: return PHYSFS_ERR_OS_ERROR;
+ } /* switch */
+} /* errcodeFromErrnoError */
+
+
+static inline PHYSFS_ErrorCode errcodeFromErrno(void)
+{
+ return errcodeFromErrnoError(errno);
+} /* errcodeFromErrno */
+
+
+static char *getUserDirByUID(void)
+{
+ uid_t uid = getuid();
+ struct passwd *pw;
+ char *retval = NULL;
+
+ pw = getpwuid(uid);
+ if ((pw != NULL) && (pw->pw_dir != NULL) && (*pw->pw_dir != '\0'))
+ {
+ const size_t dlen = strlen(pw->pw_dir);
+ const size_t add_dirsep = (pw->pw_dir[dlen-1] != '/') ? 1 : 0;
+ retval = (char *) allocator.Malloc(dlen + 1 + add_dirsep);
+ if (retval != NULL)
+ {
+ strcpy(retval, pw->pw_dir);
+ if (add_dirsep)
+ {
+ retval[dlen] = '/';
+ retval[dlen+1] = '\0';
+ } /* if */
+ } /* if */
+ } /* if */
+
+ return retval;
+} /* getUserDirByUID */
+
+
+char *__PHYSFS_platformCalcUserDir(void)
+{
+ char *retval = NULL;
+ char *envr = getenv("HOME");
+
+ /* if the environment variable was set, make sure it's really a dir. */
+ if (envr != NULL)
+ {
+ struct stat statbuf;
+ if ((stat(envr, &statbuf) != -1) && (S_ISDIR(statbuf.st_mode)))
+ {
+ const size_t envrlen = strlen(envr);
+ const size_t add_dirsep = (envr[envrlen-1] != '/') ? 1 : 0;
+ retval = allocator.Malloc(envrlen + 1 + add_dirsep);
+ if (retval)
+ {
+ strcpy(retval, envr);
+ if (add_dirsep)
+ {
+ retval[envrlen] = '/';
+ retval[envrlen+1] = '\0';
+ } /* if */
+ } /* if */
+ } /* if */
+ } /* if */
+
+ if (retval == NULL)
+ retval = getUserDirByUID();
+
+ return retval;
+} /* __PHYSFS_platformCalcUserDir */
+
+
+PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
+ PHYSFS_EnumerateCallback callback,
+ const char *origdir, void *callbackdata)
+{
+ DIR *dir;
+ struct dirent *ent;
+ PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
+
+ dir = opendir(dirname);
+ BAIL_IF(dir == NULL, errcodeFromErrno(), PHYSFS_ENUM_ERROR);
+
+ while ((retval == PHYSFS_ENUM_OK) && ((ent = readdir(dir)) != NULL))
+ {
+ const char *name = ent->d_name;
+ if (name[0] == '.') /* ignore "." and ".." */
+ {
+ if ((name[1] == '\0') || ((name[1] == '.') && (name[2] == '\0')))
+ continue;
+ } /* if */
+
+ retval = callback(callbackdata, origdir, name);
+ if (retval == PHYSFS_ENUM_ERROR)
+ PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK);
+ } /* while */
+
+ closedir(dir);
+
+ return retval;
+} /* __PHYSFS_platformEnumerate */
+
+
+int __PHYSFS_platformMkDir(const char *path)
+{
+ const int rc = mkdir(path, S_IRWXU);
+ BAIL_IF(rc == -1, errcodeFromErrno(), 0);
+ return 1;
+} /* __PHYSFS_platformMkDir */
+
+
+#if !defined(O_CLOEXEC) && defined(FD_CLOEXEC)
+static inline void set_CLOEXEC(int fildes)
+{
+ int flags = fcntl(fildes, F_GETFD);
+ if (flags != -1) {
+ fcntl(fildes, F_SETFD, flags | FD_CLOEXEC);
+ }
+}
+#endif
+
+static void *doOpen(const char *filename, int mode)
+{
+ const int appending = (mode & O_APPEND);
+ int fd;
+ int *retval;
+
+ errno = 0;
+
+ /* O_APPEND doesn't actually behave as we'd like. */
+ mode &= ~O_APPEND;
+
+#ifdef O_CLOEXEC
+ /* Add O_CLOEXEC if defined */
+ mode |= O_CLOEXEC;
+#endif
+
+ do {
+ fd = open(filename, mode, S_IRUSR | S_IWUSR);
+ } while ((fd < 0) && (errno == EINTR));
+ BAIL_IF(fd < 0, errcodeFromErrno(), NULL);
+
+#if !defined(O_CLOEXEC) && defined(FD_CLOEXEC)
+ set_CLOEXEC(fd);
+#endif
+
+ if (appending)
+ {
+ if (lseek(fd, 0, SEEK_END) < 0)
+ {
+ const int err = errno;
+ close(fd);
+ BAIL(errcodeFromErrnoError(err), NULL);
+ } /* if */
+ } /* if */
+
+ retval = (int *) allocator.Malloc(sizeof (int));
+ if (!retval)
+ {
+ close(fd);
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ } /* if */
+
+ *retval = fd;
+ return ((void *) retval);
+} /* doOpen */
+
+
+void *__PHYSFS_platformOpenRead(const char *filename)
+{
+ return doOpen(filename, O_RDONLY);
+} /* __PHYSFS_platformOpenRead */
+
+
+void *__PHYSFS_platformOpenWrite(const char *filename)
+{
+ return doOpen(filename, O_WRONLY | O_CREAT | O_TRUNC);
+} /* __PHYSFS_platformOpenWrite */
+
+
+void *__PHYSFS_platformOpenAppend(const char *filename)
+{
+ return doOpen(filename, O_WRONLY | O_CREAT | O_APPEND);
+} /* __PHYSFS_platformOpenAppend */
+
+
+PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buffer,
+ PHYSFS_uint64 len)
+{
+ const int fd = *((int *) opaque);
+ ssize_t rc = 0;
+
+ if (!__PHYSFS_ui64FitsAddressSpace(len))
+ BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
+
+ do {
+ rc = read(fd, buffer, (size_t) len);
+ } while ((rc == -1) && (errno == EINTR));
+ BAIL_IF(rc == -1, errcodeFromErrno(), -1);
+ assert(rc >= 0);
+ assert(rc <= len);
+ return (PHYSFS_sint64) rc;
+} /* __PHYSFS_platformRead */
+
+
+PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
+ PHYSFS_uint64 len)
+{
+ const int fd = *((int *) opaque);
+ ssize_t rc = 0;
+
+ if (!__PHYSFS_ui64FitsAddressSpace(len))
+ BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
+
+ do {
+ rc = write(fd, (void *) buffer, (size_t) len);
+ } while ((rc == -1) && (errno == EINTR));
+ BAIL_IF(rc == -1, errcodeFromErrno(), rc);
+ assert(rc >= 0);
+ assert(rc <= len);
+ return (PHYSFS_sint64) rc;
+} /* __PHYSFS_platformWrite */
+
+
+int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos)
+{
+ const int fd = *((int *) opaque);
+ const off_t rc = lseek(fd, (off_t) pos, SEEK_SET);
+ BAIL_IF(rc == -1, errcodeFromErrno(), 0);
+ return 1;
+} /* __PHYSFS_platformSeek */
+
+
+PHYSFS_sint64 __PHYSFS_platformTell(void *opaque)
+{
+ const int fd = *((int *) opaque);
+ PHYSFS_sint64 retval;
+ retval = (PHYSFS_sint64) lseek(fd, 0, SEEK_CUR);
+ BAIL_IF(retval == -1, errcodeFromErrno(), -1);
+ return retval;
+} /* __PHYSFS_platformTell */
+
+
+PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
+{
+ const int fd = *((int *) opaque);
+ struct stat statbuf;
+ BAIL_IF(fstat(fd, &statbuf) == -1, errcodeFromErrno(), -1);
+ return ((PHYSFS_sint64) statbuf.st_size);
+} /* __PHYSFS_platformFileLength */
+
+
+int __PHYSFS_platformFlush(void *opaque)
+{
+ const int fd = *((int *) opaque);
+ int rc = -1;
+ if ((fcntl(fd, F_GETFL) & O_ACCMODE) != O_RDONLY) {
+ do {
+ rc = fsync(fd);
+ } while ((rc == -1) && (errno == EINTR));
+ BAIL_IF(rc == -1, errcodeFromErrno(), 0);
+ }
+ return 1;
+} /* __PHYSFS_platformFlush */
+
+
+void __PHYSFS_platformClose(void *opaque)
+{
+ const int fd = *((int *) opaque);
+ int rc = -1;
+ do {
+ rc = close(fd); /* we don't check this. You should have used flush! */
+ } while ((rc == -1) && (errno == EINTR));
+ allocator.Free(opaque);
+} /* __PHYSFS_platformClose */
+
+
+int __PHYSFS_platformDelete(const char *path)
+{
+ BAIL_IF(remove(path) == -1, errcodeFromErrno(), 0);
+ return 1;
+} /* __PHYSFS_platformDelete */
+
+
+int __PHYSFS_platformStat(const char *fname, PHYSFS_Stat *st, const int follow)
+{
+ struct stat statbuf;
+ const int rc = follow ? stat(fname, &statbuf) : lstat(fname, &statbuf);
+ BAIL_IF(rc == -1, errcodeFromErrno(), 0);
+
+ if (S_ISREG(statbuf.st_mode))
+ {
+ st->filetype = PHYSFS_FILETYPE_REGULAR;
+ st->filesize = statbuf.st_size;
+ } /* if */
+
+ else if(S_ISDIR(statbuf.st_mode))
+ {
+ st->filetype = PHYSFS_FILETYPE_DIRECTORY;
+ st->filesize = 0;
+ } /* else if */
+
+ else if(S_ISLNK(statbuf.st_mode))
+ {
+ st->filetype = PHYSFS_FILETYPE_SYMLINK;
+ st->filesize = 0;
+ } /* else if */
+
+ else
+ {
+ st->filetype = PHYSFS_FILETYPE_OTHER;
+ st->filesize = statbuf.st_size;
+ } /* else */
+
+ st->modtime = statbuf.st_mtime;
+ st->createtime = statbuf.st_ctime;
+ st->accesstime = statbuf.st_atime;
+
+ st->readonly = (access(fname, W_OK) == -1);
+ return 1;
+} /* __PHYSFS_platformStat */
+
+
+typedef struct
+{
+ pthread_mutex_t mutex;
+ pthread_t owner;
+ PHYSFS_uint32 count;
+} PthreadMutex;
+
+
+void *__PHYSFS_platformGetThreadID(void)
+{
+ return ( (void *) ((size_t) pthread_self()) );
+} /* __PHYSFS_platformGetThreadID */
+
+
+void *__PHYSFS_platformCreateMutex(void)
+{
+ int rc;
+ PthreadMutex *m = (PthreadMutex *) allocator.Malloc(sizeof (PthreadMutex));
+ BAIL_IF(!m, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ rc = pthread_mutex_init(&m->mutex, NULL);
+ if (rc != 0)
+ {
+ allocator.Free(m);
+ BAIL(PHYSFS_ERR_OS_ERROR, NULL);
+ } /* if */
+
+ m->count = 0;
+ m->owner = (pthread_t) 0xDEADBEEF;
+ return ((void *) m);
+} /* __PHYSFS_platformCreateMutex */
+
+
+void __PHYSFS_platformDestroyMutex(void *mutex)
+{
+ PthreadMutex *m = (PthreadMutex *) mutex;
+
+ /* Destroying a locked mutex is a bug, but we'll try to be helpful. */
+ if ((m->owner == pthread_self()) && (m->count > 0))
+ pthread_mutex_unlock(&m->mutex);
+
+ pthread_mutex_destroy(&m->mutex);
+ allocator.Free(m);
+} /* __PHYSFS_platformDestroyMutex */
+
+
+int __PHYSFS_platformGrabMutex(void *mutex)
+{
+ PthreadMutex *m = (PthreadMutex *) mutex;
+ pthread_t tid = pthread_self();
+ if (m->owner != tid)
+ {
+ if (pthread_mutex_lock(&m->mutex) != 0)
+ return 0;
+ m->owner = tid;
+ } /* if */
+
+ m->count++;
+ return 1;
+} /* __PHYSFS_platformGrabMutex */
+
+
+void __PHYSFS_platformReleaseMutex(void *mutex)
+{
+ PthreadMutex *m = (PthreadMutex *) mutex;
+ assert(m->owner == pthread_self()); /* catch programming errors. */
+ assert(m->count > 0); /* catch programming errors. */
+ if (m->owner == pthread_self())
+ {
+ if (--m->count == 0)
+ {
+ m->owner = (pthread_t) 0xDEADBEEF;
+ pthread_mutex_unlock(&m->mutex);
+ } /* if */
+ } /* if */
+} /* __PHYSFS_platformReleaseMutex */
+
+#endif /* PHYSFS_PLATFORM_POSIX */
+
+/* end of physfs_platform_posix.c ... */
+
diff --git a/third-party/physfs/src/physfs_platform_qnx.c b/third-party/physfs/src/physfs_platform_qnx.c
new file mode 100644
index 0000000..46f9aa7
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_qnx.c
@@ -0,0 +1,169 @@
+/*
+ * QNX support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+/* This is tested against QNX 7 at the moment. */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_QNX
+
+#include
+#include
+#include
+#include
+
+#include "physfs_internal.h"
+
+int __PHYSFS_platformInit(void)
+{
+ return 1; /* always succeed. */
+} /* __PHYSFS_platformInit */
+
+
+void __PHYSFS_platformDeinit(void)
+{
+ /* no-op */
+} /* __PHYSFS_platformDeinit */
+
+
+char *__PHYSFS_platformCalcBaseDir(const char *argv0)
+{
+ char *retval = (char *) allocator.Malloc(PATH_MAX+1);
+ if (retval == NULL)
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ else
+ {
+ const int fd = open("/proc/self/exefile", O_RDONLY);
+ const ssize_t br = (fd == -1) ? -1 : read(fd, retval, PATH_MAX);
+ char *ptr;
+
+ if (fd != -1)
+ close(fd);
+
+ if ((br < 0) || (br > PATH_MAX))
+ {
+ allocator.Free(retval);
+ BAIL(PHYSFS_ERR_OS_ERROR, NULL);
+ } /* if */
+
+ retval[br] = '\0';
+ ptr = strrchr(retval, '/');
+ if (ptr == NULL) /* uhoh! */
+ {
+ allocator.Free(retval);
+ BAIL(PHYSFS_ERR_OS_ERROR, NULL);
+ } /* if */
+
+ ptr[1] = '\0'; /* chop off filename, leave dirs and '/' */
+
+ ptr = (char *) allocator.Realloc(retval, (ptr - retval) + 2);
+ if (ptr != NULL) /* just shrinking buffer; don't care if it failed. */
+ retval = ptr;
+ } /* else */
+
+ return retval;
+} /* __PHYSFS_platformCalcBaseDir */
+
+
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
+{
+ /* !!! FIXME: this might be wrong; I don't know if there's a better method
+ on QNX, or if it follows XDG specs, etc. */
+ char *retval = NULL;
+ const char *home = __PHYSFS_getUserDir();
+ if (home)
+ {
+ const size_t len = strlen(home) + strlen(app) + 3;
+ retval = (char *) allocator.Malloc(len);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ snprintf(retval, len, "%s.%s/", home, app);
+ } /* if */
+ return retval;
+} /* __PHYSFS_platformCalcPrefDir */
+
+
+#if !PHYSFS_NO_CDROM_SUPPORT
+#include
+#include
+#include
+#include
+#include
+
+static void checkPathForCD(const char *path, PHYSFS_StringCallback cb, void *d)
+{
+ struct stat statbuf;
+ int fd;
+
+ /* The devctl() thing is QNX-specific. In this case, we query what is
+ probably the mountpoint for the device. statvfs() on that mountpoint
+ will tell use its filesystem type. */
+
+ if ( (stat(path, &statbuf) == 0) &&
+ (S_ISBLK(statbuf.st_mode)) &&
+ ((fd = open(path, O_RDONLY | O_NONBLOCK)) != -1) )
+ {
+ char mnt[256] = { 0 };
+ const int rc = devctl(fd, DCMD_FSYS_MOUNTED_BY, mnt, sizeof (mnt), 0);
+ close(fd);
+ if ( (rc == EOK) && (mnt[0]) )
+ {
+ struct statvfs statvfsbuf;
+ if (statvfs(mnt, &statvfsbuf) == 0)
+ {
+ /* I don't know if this is a complete or accurate list. */
+ const char *fstype = statvfsbuf.f_basetype;
+ const int iscd = ( (strcmp(fstype, "cd") == 0) ||
+ (strcmp(fstype, "udf") == 0) );
+ if (iscd)
+ cb(d, mnt);
+ } /* if */
+ } /* if */
+ } /* if */
+} /* checkPathForCD */
+
+static void checkDevForCD(const char *dev, PHYSFS_StringCallback cb, void *d)
+{
+ size_t len;
+ char *path;
+
+ if (dev[0] == '.') /* ignore "." and ".." */
+ {
+ if ((dev[1] == '\0') || ((dev[1] == '.') && (dev[2] == '\0')))
+ return;
+ } /* if */
+
+ len = strlen(dev) + 6;
+ path = (char *) __PHYSFS_smallAlloc(len);
+ if (!path)
+ return; /* oh well. */
+
+ snprintf(path, len, "/dev/%s", dev);
+ checkPathForCD(path, cb, d);
+ __PHYSFS_smallFree(path);
+} /* checkDevForCD */
+#endif /* !PHYSFS_NO_CDROM_SUPPORT */
+
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+#if !PHYSFS_NO_CDROM_SUPPORT
+ DIR *dirp = opendir("/dev");
+ if (dirp)
+ {
+ struct dirent *dent;
+ while ((dent = readdir(dirp)) != NULL)
+ checkDevForCD(dent->d_name, cb, data);
+ closedir(dirp);
+ } /* if */
+#endif
+} /* __PHYSFS_platformDetectAvailableCDs */
+
+#endif /* PHYSFS_PLATFORM_QNX */
+
+/* end of physfs_platform_qnx.c ... */
+
diff --git a/third-party/physfs/src/physfs_platform_unix.c b/third-party/physfs/src/physfs_platform_unix.c
new file mode 100644
index 0000000..10d93a7
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_unix.c
@@ -0,0 +1,367 @@
+/*
+ * Unix support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_UNIX
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if PHYSFS_NO_CDROM_SUPPORT
+#elif PHYSFS_PLATFORM_LINUX
+# define PHYSFS_HAVE_MNTENT_H 1
+#elif defined __CYGWIN__
+# define PHYSFS_HAVE_MNTENT_H 1
+#elif PHYSFS_PLATFORM_SOLARIS
+# define PHYSFS_HAVE_SYS_MNTTAB_H 1
+#elif PHYSFS_PLATFORM_BSD
+# define PHYSFS_HAVE_SYS_UCRED_H 1
+#else
+# warning No CD-ROM support included. Either define your platform here,
+# warning or define PHYSFS_NO_CDROM_SUPPORT=1 to confirm this is intentional.
+#endif
+
+#ifdef PHYSFS_HAVE_SYS_UCRED_H
+# ifdef PHYSFS_HAVE_MNTENT_H
+# undef PHYSFS_HAVE_MNTENT_H /* don't do both... */
+# endif
+# include
+# include
+#endif
+
+#ifdef PHYSFS_HAVE_MNTENT_H
+#include
+#endif
+
+#ifdef PHYSFS_HAVE_SYS_MNTTAB_H
+#include
+#endif
+
+#ifdef PHYSFS_PLATFORM_FREEBSD
+#include
+#endif
+
+
+#include "physfs_internal.h"
+
+int __PHYSFS_platformInit(void)
+{
+ return 1; /* always succeed. */
+} /* __PHYSFS_platformInit */
+
+
+void __PHYSFS_platformDeinit(void)
+{
+ /* no-op */
+} /* __PHYSFS_platformDeinit */
+
+
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+#if (defined PHYSFS_NO_CDROM_SUPPORT)
+ /* no-op. */
+
+#elif (defined PHYSFS_HAVE_SYS_UCRED_H)
+ int i;
+ struct statfs *mntbufp = NULL;
+ int mounts = getmntinfo(&mntbufp, MNT_NOWAIT);
+
+ for (i = 0; i < mounts; i++)
+ {
+ int add_it = 0;
+
+ if (strcmp(mntbufp[i].f_fstypename, "iso9660") == 0)
+ add_it = 1;
+ else if (strcmp( mntbufp[i].f_fstypename, "cd9660") == 0)
+ add_it = 1;
+
+ /* add other mount types here */
+
+ if (add_it)
+ cb(data, mntbufp[i].f_mntonname);
+ } /* for */
+
+#elif (defined PHYSFS_HAVE_MNTENT_H)
+ FILE *mounts = NULL;
+ struct mntent *ent = NULL;
+
+ mounts = setmntent("/etc/mtab", "r");
+ BAIL_IF(mounts == NULL, PHYSFS_ERR_IO, /*return void*/);
+
+ while ( (ent = getmntent(mounts)) != NULL )
+ {
+ int add_it = 0;
+ if (strcmp(ent->mnt_type, "iso9660") == 0)
+ add_it = 1;
+ else if (strcmp(ent->mnt_type, "udf") == 0)
+ add_it = 1;
+
+ /* !!! FIXME: these might pick up floppy drives, right? */
+ else if (strcmp(ent->mnt_type, "auto") == 0)
+ add_it = 1;
+ else if (strcmp(ent->mnt_type, "supermount") == 0)
+ add_it = 1;
+
+ /* add other mount types here */
+
+ if (add_it)
+ cb(data, ent->mnt_dir);
+ } /* while */
+
+ endmntent(mounts);
+
+#elif (defined PHYSFS_HAVE_SYS_MNTTAB_H)
+ FILE *mounts = fopen(MNTTAB, "r");
+ struct mnttab ent;
+
+ BAIL_IF(mounts == NULL, PHYSFS_ERR_IO, /*return void*/);
+ while (getmntent(mounts, &ent) == 0)
+ {
+ int add_it = 0;
+ if (strcmp(ent.mnt_fstype, "hsfs") == 0)
+ add_it = 1;
+
+ /* add other mount types here */
+
+ if (add_it)
+ cb(data, ent.mnt_mountp);
+ } /* while */
+
+ fclose(mounts);
+#endif
+} /* __PHYSFS_platformDetectAvailableCDs */
+
+
+/*
+ * See where program (bin) resides in the $PATH specified by (envr).
+ * returns a copy of the first element in envr that contains it, or NULL
+ * if it doesn't exist or there were other problems. PHYSFS_SetError() is
+ * called if we have a problem.
+ *
+ * (envr) will be scribbled over, and you are expected to allocator.Free() the
+ * return value when you're done with it.
+ */
+static char *findBinaryInPath(const char *bin, char *envr)
+{
+ size_t alloc_size = 0;
+ char *exe = NULL;
+ char *start = envr;
+ char *ptr;
+
+ assert(bin != NULL);
+ assert(envr != NULL);
+
+ do
+ {
+ size_t size;
+ size_t binlen;
+
+ ptr = strchr(start, ':'); /* find next $PATH separator. */
+ if (ptr)
+ *ptr = '\0';
+
+ binlen = strlen(bin);
+ size = strlen(start) + binlen + 2;
+ if (size >= alloc_size)
+ {
+ char *x = (char *) allocator.Realloc(exe, size);
+ if (!x)
+ {
+ if (exe != NULL)
+ allocator.Free(exe);
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ } /* if */
+
+ alloc_size = size;
+ exe = x;
+ } /* if */
+
+ /* build full binary path... */
+ strcpy(exe, start);
+ if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/'))
+ strcat(exe, "/");
+ strcat(exe, bin);
+
+ if (access(exe, X_OK) == 0) /* Exists as executable? We're done. */
+ {
+ exe[(size - binlen) - 1] = '\0'; /* chop off filename, leave '/' */
+ return exe;
+ } /* if */
+
+ start = ptr + 1; /* start points to beginning of next element. */
+ } while (ptr != NULL);
+
+ if (exe != NULL)
+ allocator.Free(exe);
+
+ return NULL; /* doesn't exist in path. */
+} /* findBinaryInPath */
+
+
+static char *readSymLink(const char *path)
+{
+ ssize_t len = 64;
+ ssize_t rc = -1;
+ char *retval = NULL;
+
+ while (1)
+ {
+ char *ptr = (char *) allocator.Realloc(retval, (size_t) len);
+ if (ptr == NULL)
+ break; /* out of memory. */
+ retval = ptr;
+
+ rc = readlink(path, retval, len);
+ if (rc == -1)
+ break; /* not a symlink, i/o error, etc. */
+
+ else if (rc < len)
+ {
+ retval[rc] = '\0'; /* readlink doesn't null-terminate. */
+ return retval; /* we're good to go. */
+ } /* else if */
+
+ len *= 2; /* grow buffer, try again. */
+ } /* while */
+
+ if (retval != NULL)
+ allocator.Free(retval);
+ return NULL;
+} /* readSymLink */
+
+
+char *__PHYSFS_platformCalcBaseDir(const char *argv0)
+{
+ char *retval = NULL;
+ const char *envr = NULL;
+
+ /* Try to avoid using argv0 unless forced to. Try system-specific stuff. */
+
+ #if defined(PHYSFS_PLATFORM_FREEBSD)
+ {
+ char fullpath[PATH_MAX];
+ size_t buflen = sizeof (fullpath);
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
+ if (sysctl(mib, 4, fullpath, &buflen, NULL, 0) != -1)
+ retval = __PHYSFS_strdup(fullpath);
+ }
+ #elif defined(PHYSFS_PLATFORM_SOLARIS)
+ {
+ const char *path = getexecname();
+ if ((path != NULL) && (path[0] == '/')) /* must be absolute path... */
+ retval = __PHYSFS_strdup(path);
+ }
+ #endif
+
+ /* If there's a Linux-like /proc filesystem, you can get the full path to
+ * the current process from a symlink in there.
+ */
+
+ if (!retval && (access("/proc", F_OK) == 0))
+ {
+ retval = readSymLink("/proc/self/exe");
+ if (!retval) retval = readSymLink("/proc/curproc/file");
+ if (!retval) retval = readSymLink("/proc/curproc/exe");
+ if (retval == NULL)
+ {
+ /* older kernels don't have /proc/self ... try PID version... */
+ const unsigned long long pid = (unsigned long long) getpid();
+ char path[64];
+ const int rc = (int) snprintf(path,sizeof(path),"/proc/%llu/exe",pid);
+ if ( (rc > 0) && (rc < sizeof(path)) )
+ retval = readSymLink(path);
+ } /* if */
+ } /* if */
+
+ if (retval != NULL) /* chop off filename. */
+ {
+ char *ptr = strrchr(retval, '/');
+ if (ptr != NULL)
+ *(ptr+1) = '\0';
+ else /* shouldn't happen, but just in case... */
+ {
+ allocator.Free(retval);
+ retval = NULL;
+ } /* else */
+ } /* if */
+
+ /* No /proc/self/exe, etc, but we have an argv[0] we can parse? */
+ if ((retval == NULL) && (argv0 != NULL))
+ {
+ /* fast path: default behaviour can handle this. */
+ if (strchr(argv0, '/') != NULL)
+ return NULL; /* higher level parses out real path from argv0. */
+
+ /* If there's no dirsep on argv0, then look through $PATH for it. */
+ envr = getenv("PATH");
+ if (envr != NULL)
+ {
+ char *path = (char *) __PHYSFS_smallAlloc(strlen(envr) + 1);
+ BAIL_IF(!path, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ strcpy(path, envr);
+ retval = findBinaryInPath(argv0, path);
+ __PHYSFS_smallFree(path);
+ } /* if */
+ } /* if */
+
+ if (retval != NULL)
+ {
+ /* try to shrink buffer... */
+ char *ptr = (char *) allocator.Realloc(retval, strlen(retval) + 1);
+ if (ptr != NULL)
+ retval = ptr; /* oh well if it failed. */
+ } /* if */
+
+ return retval;
+} /* __PHYSFS_platformCalcBaseDir */
+
+
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
+{
+ /*
+ * We use XDG's base directory spec, even if you're not on Linux.
+ * This isn't strictly correct, but the results are relatively sane
+ * in any case.
+ *
+ * https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ */
+ const char *envr = getenv("XDG_DATA_HOME");
+ const char *append = "/";
+ char *retval = NULL;
+ size_t len = 0;
+
+ if (!envr)
+ {
+ /* You end up with "$HOME/.local/share/Game Name 2" */
+ envr = __PHYSFS_getUserDir();
+ BAIL_IF_ERRPASS(!envr, NULL); /* oh well. */
+ append = ".local/share/";
+ } /* if */
+
+ len = strlen(envr) + strlen(append) + strlen(app) + 2;
+ retval = (char *) allocator.Malloc(len);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ snprintf(retval, len, "%s%s%s/", envr, append, app);
+ return retval;
+} /* __PHYSFS_platformCalcPrefDir */
+
+#endif /* PHYSFS_PLATFORM_UNIX */
+
+/* end of physfs_platform_unix.c ... */
+
diff --git a/third-party/physfs/src/physfs_platform_windows.c b/third-party/physfs/src/physfs_platform_windows.c
new file mode 100644
index 0000000..652300c
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_windows.c
@@ -0,0 +1,1028 @@
+/*
+ * Windows support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon, and made sane by Gregory S. Read.
+ */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_WINDOWS
+
+/* Forcibly disable UNICODE macro, since we manage this ourselves. */
+#ifdef UNICODE
+#undef UNICODE
+#endif
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS 1
+#endif
+
+#define WIN32_LEAN_AND_MEAN 1
+#include
+
+#ifndef PHYSFS_PLATFORM_WINRT
+#include
+#include
+#endif
+
+#if !defined(PHYSFS_NO_CDROM_SUPPORT)
+#include
+#endif
+
+#include
+#include
+#include
+
+#ifdef allocator /* apparently Windows 10 SDK conflicts here. */
+#undef allocator
+#endif
+
+#include "physfs_internal.h"
+
+/*
+ * Users without the platform SDK don't have this defined. The original docs
+ * for SetFilePointer() just said to compare with 0xFFFFFFFF, so this should
+ * work as desired.
+ */
+#define PHYSFS_INVALID_SET_FILE_POINTER 0xFFFFFFFF
+
+/* just in case... */
+#define PHYSFS_INVALID_FILE_ATTRIBUTES 0xFFFFFFFF
+
+/* Not defined before the Vista SDK. */
+#define PHYSFS_FILE_ATTRIBUTE_REPARSE_POINT 0x400
+#define PHYSFS_IO_REPARSE_TAG_SYMLINK 0xA000000C
+
+
+#define UTF8_TO_UNICODE_STACK(w_assignto, str) { \
+ if (str == NULL) \
+ w_assignto = NULL; \
+ else { \
+ const size_t len = (PHYSFS_uint64) ((strlen(str) + 1) * 2); \
+ w_assignto = (WCHAR *) __PHYSFS_smallAlloc(len); \
+ if (w_assignto != NULL) \
+ PHYSFS_utf8ToUtf16(str, (PHYSFS_uint16 *) w_assignto, len); \
+ } \
+} \
+
+/* Note this counts WCHARs, not codepoints! */
+static PHYSFS_uint64 wStrLen(const WCHAR *wstr)
+{
+ PHYSFS_uint64 len = 0;
+ while (*(wstr++))
+ len++;
+ return len;
+} /* wStrLen */
+
+static char *unicodeToUtf8Heap(const WCHAR *w_str)
+{
+ char *retval = NULL;
+ if (w_str != NULL)
+ {
+ void *ptr = NULL;
+ const PHYSFS_uint64 len = (wStrLen(w_str) * 4) + 1;
+ retval = allocator.Malloc(len);
+ BAIL_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ PHYSFS_utf8FromUtf16((const PHYSFS_uint16 *) w_str, retval, len);
+ ptr = allocator.Realloc(retval, strlen(retval) + 1); /* shrink. */
+ if (ptr != NULL)
+ retval = (char *) ptr;
+ } /* if */
+ return retval;
+} /* unicodeToUtf8Heap */
+
+
+/* Some older APIs aren't in WinRT (only the "Ex" version, etc).
+ Since non-WinRT might not have the "Ex" version, we tapdance to use
+ the perfectly-fine-and-available-even-on-Win95 API on non-WinRT targets. */
+
+static inline HANDLE winFindFirstFileW(const WCHAR *path, LPWIN32_FIND_DATAW d)
+{
+ #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0501) // Windows XP+
+ return FindFirstFileExW(path, FindExInfoStandard, d,
+ FindExSearchNameMatch, NULL, 0);
+ #else
+ return FindFirstFileW(path, d);
+ #endif
+} /* winFindFirstFileW */
+
+static inline BOOL winInitializeCriticalSection(LPCRITICAL_SECTION lpcs)
+{
+ #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0600) // Windows Vista+
+ return InitializeCriticalSectionEx(lpcs, 2000, 0);
+ #else
+ InitializeCriticalSection(lpcs);
+ return TRUE;
+ #endif
+} /* winInitializeCriticalSection */
+
+static inline HANDLE winCreateFileW(const WCHAR *wfname, const DWORD mode,
+ const DWORD creation)
+{
+ const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0602) // Windows 8+
+ return CreateFile2(wfname, mode, share, creation, NULL);
+ #else
+ return CreateFileW(wfname, mode, share, NULL, creation,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ #endif
+} /* winCreateFileW */
+
+static BOOL winSetFilePointer(HANDLE h, const PHYSFS_sint64 pos,
+ PHYSFS_sint64 *_newpos, const DWORD whence)
+{
+ #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0501) // Windows XP+
+ LARGE_INTEGER lipos;
+ LARGE_INTEGER linewpos;
+ BOOL rc;
+ lipos.QuadPart = (LONGLONG) pos;
+ rc = SetFilePointerEx(h, lipos, &linewpos, whence);
+ if (_newpos)
+ *_newpos = (PHYSFS_sint64) linewpos.QuadPart;
+ return rc;
+ #else
+ const LONG low = (LONG) (pos & 0xFFFFFFFF);
+ LONG high = (LONG) ((pos >> 32) & 0xFFFFFFFF);
+ const DWORD rc = SetFilePointer(h, low, &high, whence);
+ /* 0xFFFFFFFF could be valid, so you have to check GetLastError too! */
+ if (_newpos)
+ *_newpos = ((PHYSFS_sint64) rc) | (((PHYSFS_sint64) high) << 32);
+ if ((rc == PHYSFS_INVALID_SET_FILE_POINTER) && (GetLastError() != NO_ERROR))
+ return FALSE;
+ return TRUE;
+ #endif
+} /* winSetFilePointer */
+
+static PHYSFS_sint64 winGetFileSize(HANDLE h)
+{
+ #if defined(PHYSFS_PLATFORM_WINRT) || (_WIN32_WINNT >= 0x0600) // Windows Vista+
+ FILE_STANDARD_INFO info;
+ const BOOL rc = GetFileInformationByHandleEx(h, FileStandardInfo,
+ &info, sizeof (info));
+ return rc ? (PHYSFS_sint64) info.EndOfFile.QuadPart : -1;
+ #else
+ DWORD high = 0;
+ const DWORD rc = GetFileSize(h, &high);
+ if ((rc == PHYSFS_INVALID_SET_FILE_POINTER) && (GetLastError() != NO_ERROR))
+ return -1;
+ return (PHYSFS_sint64) ((((PHYSFS_uint64) high) << 32) | rc);
+ #endif
+} /* winGetFileSize */
+
+
+static PHYSFS_ErrorCode errcodeFromWinApiError(const DWORD err)
+{
+ /*
+ * win32 error codes are sort of a tricky thing; Microsoft intentionally
+ * doesn't list which ones a given API might trigger, there are several
+ * with overlapping and unclear meanings...and there's 16 thousand of
+ * them in Windows 7. It looks like the ones we care about are in the
+ * first 500, but I can't say this list is perfect; we might miss
+ * important values or misinterpret others.
+ *
+ * Don't treat this list as anything other than a work in progress.
+ */
+ switch (err)
+ {
+ case ERROR_SUCCESS: return PHYSFS_ERR_OK;
+ case ERROR_ACCESS_DENIED: return PHYSFS_ERR_PERMISSION;
+ case ERROR_NETWORK_ACCESS_DENIED: return PHYSFS_ERR_PERMISSION;
+ case ERROR_NOT_READY: return PHYSFS_ERR_IO;
+ case ERROR_CRC: return PHYSFS_ERR_IO;
+ case ERROR_SEEK: return PHYSFS_ERR_IO;
+ case ERROR_SECTOR_NOT_FOUND: return PHYSFS_ERR_IO;
+ case ERROR_NOT_DOS_DISK: return PHYSFS_ERR_IO;
+ case ERROR_WRITE_FAULT: return PHYSFS_ERR_IO;
+ case ERROR_READ_FAULT: return PHYSFS_ERR_IO;
+ case ERROR_DEV_NOT_EXIST: return PHYSFS_ERR_IO;
+ case ERROR_BUFFER_OVERFLOW: return PHYSFS_ERR_BAD_FILENAME;
+ case ERROR_INVALID_NAME: return PHYSFS_ERR_BAD_FILENAME;
+ case ERROR_BAD_PATHNAME: return PHYSFS_ERR_BAD_FILENAME;
+ case ERROR_DIRECTORY: return PHYSFS_ERR_BAD_FILENAME;
+ case ERROR_FILE_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND;
+ case ERROR_PATH_NOT_FOUND: return PHYSFS_ERR_NOT_FOUND;
+ case ERROR_DELETE_PENDING: return PHYSFS_ERR_NOT_FOUND;
+ case ERROR_INVALID_DRIVE: return PHYSFS_ERR_NOT_FOUND;
+ case ERROR_HANDLE_DISK_FULL: return PHYSFS_ERR_NO_SPACE;
+ case ERROR_DISK_FULL: return PHYSFS_ERR_NO_SPACE;
+ case ERROR_WRITE_PROTECT: return PHYSFS_ERR_READ_ONLY;
+ case ERROR_LOCK_VIOLATION: return PHYSFS_ERR_BUSY;
+ case ERROR_SHARING_VIOLATION: return PHYSFS_ERR_BUSY;
+ case ERROR_CURRENT_DIRECTORY: return PHYSFS_ERR_BUSY;
+ case ERROR_DRIVE_LOCKED: return PHYSFS_ERR_BUSY;
+ case ERROR_PATH_BUSY: return PHYSFS_ERR_BUSY;
+ case ERROR_BUSY: return PHYSFS_ERR_BUSY;
+ case ERROR_NOT_ENOUGH_MEMORY: return PHYSFS_ERR_OUT_OF_MEMORY;
+ case ERROR_OUTOFMEMORY: return PHYSFS_ERR_OUT_OF_MEMORY;
+ case ERROR_DIR_NOT_EMPTY: return PHYSFS_ERR_DIR_NOT_EMPTY;
+ default: return PHYSFS_ERR_OS_ERROR;
+ } /* switch */
+} /* errcodeFromWinApiError */
+
+static inline PHYSFS_ErrorCode errcodeFromWinApi(void)
+{
+ return errcodeFromWinApiError(GetLastError());
+} /* errcodeFromWinApi */
+
+
+#if defined(PHYSFS_NO_CDROM_SUPPORT)
+#define detectAvailableCDs(cb, data)
+#define deinitCDThread()
+#else
+static HANDLE detectCDThreadHandle = NULL;
+static HWND detectCDHwnd = NULL;
+static volatile DWORD drivesWithMediaBitmap = 0;
+
+typedef BOOL (WINAPI *fnSTEM)(DWORD, LPDWORD b);
+
+static DWORD pollDiscDrives(void)
+{
+ /* Try to use SetThreadErrorMode(), which showed up in Windows 7. */
+ HANDLE lib = LoadLibraryA("kernel32.dll");
+ fnSTEM stem = NULL;
+ char drive[4] = { 'x', ':', '\\', '\0' };
+ DWORD oldErrorMode = 0;
+ DWORD drives = 0;
+ DWORD i;
+
+ if (lib)
+ stem = (fnSTEM) GetProcAddress(lib, "SetThreadErrorMode");
+
+ if (stem)
+ stem(SEM_FAILCRITICALERRORS, &oldErrorMode);
+ else
+ oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
+
+ /* Do detection. This may block if a disc is spinning up. */
+ for (i = 'A'; i <= 'Z'; i++)
+ {
+ DWORD tmp = 0;
+ drive[0] = (char) i;
+ if (GetDriveTypeA(drive) != DRIVE_CDROM)
+ continue;
+
+ /* If this function succeeds, there's media in the drive */
+ if (GetVolumeInformationA(drive, NULL, 0, NULL, NULL, &tmp, NULL, 0))
+ drives |= (1 << (i - 'A'));
+ } /* for */
+
+ if (stem)
+ stem(oldErrorMode, NULL);
+ else
+ SetErrorMode(oldErrorMode);
+
+ if (lib)
+ FreeLibrary(lib);
+
+ return drives;
+} /* pollDiscDrives */
+
+
+static LRESULT CALLBACK detectCDWndProc(HWND hwnd, UINT msg,
+ WPARAM wp, LPARAM lparam)
+{
+ PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR) lparam;
+ PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME) lparam;
+ const int removed = (wp == DBT_DEVICEREMOVECOMPLETE);
+
+ if (msg == WM_DESTROY)
+ return 0;
+ else if ((msg != WM_DEVICECHANGE) ||
+ ((wp != DBT_DEVICEARRIVAL) && (wp != DBT_DEVICEREMOVECOMPLETE)) ||
+ (lpdb->dbch_devicetype != DBT_DEVTYP_VOLUME) ||
+ ((lpdbv->dbcv_flags & DBTF_MEDIA) == 0))
+ {
+ return DefWindowProcW(hwnd, msg, wp, lparam);
+ } /* else if */
+
+ if (removed)
+ drivesWithMediaBitmap &= ~lpdbv->dbcv_unitmask;
+ else
+ drivesWithMediaBitmap |= lpdbv->dbcv_unitmask;
+
+ return TRUE;
+} /* detectCDWndProc */
+
+
+static DWORD WINAPI detectCDThread(LPVOID arg)
+{
+ HANDLE initialDiscDetectionComplete = *((HANDLE *) arg);
+ const char *classname = "PhysicsFSDetectCDCatcher";
+ const char *winname = "PhysicsFSDetectCDMsgWindow";
+ HINSTANCE hInstance = GetModuleHandleW(NULL);
+ ATOM class_atom = 0;
+ WNDCLASSEXA wce;
+ MSG msg;
+
+ memset(&wce, '\0', sizeof (wce));
+ wce.cbSize = sizeof (wce);
+ wce.lpfnWndProc = detectCDWndProc;
+ wce.lpszClassName = classname;
+ wce.hInstance = hInstance;
+ class_atom = RegisterClassExA(&wce);
+ if (class_atom == 0)
+ {
+ SetEvent(initialDiscDetectionComplete); /* let main thread go on. */
+ return 0;
+ } /* if */
+
+ detectCDHwnd = CreateWindowExA(0, classname, winname, WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
+
+ if (detectCDHwnd == NULL)
+ {
+ SetEvent(initialDiscDetectionComplete); /* let main thread go on. */
+ UnregisterClassA(classname, hInstance);
+ return 0;
+ } /* if */
+
+ /* We'll get events when discs come and go from now on. */
+
+ /* Do initial detection, possibly blocking awhile... */
+ drivesWithMediaBitmap = pollDiscDrives();
+
+ SetEvent(initialDiscDetectionComplete); /* let main thread go on. */
+
+ do
+ {
+ const BOOL rc = GetMessageW(&msg, detectCDHwnd, 0, 0);
+ if ((rc == 0) || (rc == -1))
+ break; /* don't care if WM_QUIT or error break this loop. */
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ } while (1);
+
+ /* we've been asked to quit. */
+ DestroyWindow(detectCDHwnd);
+ UnregisterClassA(classname, hInstance);
+ return 0;
+} /* detectCDThread */
+
+static void detectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+ char drive_str[4] = { 'x', ':', '\\', '\0' };
+ DWORD drives = 0;
+ DWORD i;
+
+ /*
+ * If you poll a drive while a user is inserting a disc, the OS will
+ * block this thread until the drive has spun up. So we swallow the risk
+ * once for initial detection, and spin a thread that will get device
+ * events thereafter, for apps that use this interface to poll for
+ * disc insertion.
+ */
+ if (!detectCDThreadHandle)
+ {
+ HANDLE initialDetectDone = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!initialDetectDone)
+ return; /* oh well. */
+
+ detectCDThreadHandle = CreateThread(NULL, 0, detectCDThread,
+ &initialDetectDone, 0, NULL);
+ if (detectCDThreadHandle)
+ WaitForSingleObject(initialDetectDone, INFINITE);
+ CloseHandle(initialDetectDone);
+
+ if (!detectCDThreadHandle)
+ return; /* oh well. */
+ } /* if */
+
+ drives = drivesWithMediaBitmap; /* whatever the thread has seen, we take. */
+ for (i = 'A'; i <= 'Z'; i++)
+ {
+ if (drives & (1 << (i - 'A')))
+ {
+ drive_str[0] = (char) i;
+ cb(data, drive_str);
+ } /* if */
+ } /* for */
+} /* detectAvailableCDs */
+
+static void deinitCDThread(void)
+{
+ if (detectCDThreadHandle)
+ {
+ if (detectCDHwnd)
+ PostMessageW(detectCDHwnd, WM_QUIT, 0, 0);
+ CloseHandle(detectCDThreadHandle);
+ detectCDThreadHandle = NULL;
+ drivesWithMediaBitmap = 0;
+ } /* if */
+} /* deinitCDThread */
+#endif
+
+
+void __PHYSFS_platformDetectAvailableCDs(PHYSFS_StringCallback cb, void *data)
+{
+ detectAvailableCDs(cb, data);
+} /* __PHYSFS_platformDetectAvailableCDs */
+
+#ifdef PHYSFS_PLATFORM_WINRT
+static char *calcDirAppendSep(const WCHAR *wdir)
+{
+ size_t len;
+ void *ptr;
+ char *retval;
+ BAIL_IF(!wdir, errcodeFromWinApi(), NULL);
+ retval = unicodeToUtf8Heap(wdir);
+ BAIL_IF_ERRPASS(!retval, NULL);
+ len = strlen(retval);
+ ptr = allocator.Realloc(retval, len + 2);
+ if (!ptr)
+ {
+ allocator.Free(retval);
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ } /* if */
+ retval = (char *) ptr;
+ retval[len] = '\\';
+ retval[len+1] = '\0';
+ return retval;
+} /* calcDirAppendSep */
+#endif
+
+char *__PHYSFS_platformCalcBaseDir(const char *argv0)
+{
+#ifdef PHYSFS_PLATFORM_WINRT
+ return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcBaseDir());
+#else
+ char *retval = NULL;
+ DWORD buflen = 64;
+ LPWSTR modpath = NULL;
+
+ while (1)
+ {
+ DWORD rc;
+ void *ptr;
+
+ if ( (ptr = allocator.Realloc(modpath, buflen*sizeof(WCHAR))) == NULL )
+ {
+ allocator.Free(modpath);
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ } /* if */
+ modpath = (LPWSTR) ptr;
+
+ rc = GetModuleFileNameW(NULL, modpath, buflen);
+ if (rc == 0)
+ {
+ allocator.Free(modpath);
+ BAIL(errcodeFromWinApi(), NULL);
+ } /* if */
+
+ if (rc < buflen)
+ {
+ buflen = rc;
+ break;
+ } /* if */
+
+ buflen *= 2;
+ } /* while */
+
+ if (buflen > 0) /* just in case... */
+ {
+ WCHAR *ptr = (modpath + buflen) - 1;
+ while (ptr != modpath)
+ {
+ if (*ptr == '\\')
+ break;
+ ptr--;
+ } /* while */
+
+ if ((ptr == modpath) && (*ptr != '\\'))
+ PHYSFS_setErrorCode(PHYSFS_ERR_OTHER_ERROR); /* oh well. */
+ else
+ {
+ *(ptr+1) = '\0'; /* chop off filename. */
+ retval = unicodeToUtf8Heap(modpath);
+ } /* else */
+ } /* else */
+ allocator.Free(modpath);
+
+ return retval; /* w00t. */
+#endif
+} /* __PHYSFS_platformCalcBaseDir */
+
+
+char *__PHYSFS_platformCalcPrefDir(const char *org, const char *app)
+{
+#ifdef PHYSFS_PLATFORM_WINRT
+ return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcPrefDir());
+#else
+ /*
+ * Vista and later has a new API for this, but SHGetFolderPath works there,
+ * and apparently just wraps the new API. This is the new way to do it:
+ *
+ * SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE,
+ * NULL, &wszPath);
+ */
+
+ WCHAR path[MAX_PATH];
+ char *utf8 = NULL;
+ size_t len = 0;
+ char *retval = NULL;
+
+ if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE,
+ NULL, 0, path)))
+ BAIL(PHYSFS_ERR_OS_ERROR, NULL);
+
+ utf8 = unicodeToUtf8Heap(path);
+ BAIL_IF_ERRPASS(!utf8, NULL);
+ len = strlen(utf8) + strlen(org) + strlen(app) + 4;
+ retval = allocator.Malloc(len);
+ if (!retval)
+ {
+ allocator.Free(utf8);
+ BAIL(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ } /* if */
+
+ snprintf(retval, len, "%s\\%s\\%s\\", utf8, org, app);
+ allocator.Free(utf8);
+ return retval;
+#endif
+} /* __PHYSFS_platformCalcPrefDir */
+
+
+char *__PHYSFS_platformCalcUserDir(void)
+{
+#ifdef PHYSFS_PLATFORM_WINRT
+ return calcDirAppendSep((const WCHAR *) __PHYSFS_winrtCalcPrefDir());
+#else
+ typedef BOOL (WINAPI *fnGetUserProfDirW)(HANDLE, LPWSTR, LPDWORD);
+ fnGetUserProfDirW pGetDir = NULL;
+ HANDLE lib = NULL;
+ HANDLE accessToken = NULL; /* Security handle to process */
+ char *retval = NULL;
+
+ lib = LoadLibraryA("userenv.dll");
+ BAIL_IF(!lib, errcodeFromWinApi(), NULL);
+ pGetDir=(fnGetUserProfDirW) GetProcAddress(lib,"GetUserProfileDirectoryW");
+ GOTO_IF(!pGetDir, errcodeFromWinApi(), done);
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &accessToken))
+ GOTO(errcodeFromWinApi(), done);
+ else
+ {
+ DWORD psize = 0;
+ LPWSTR wstr = NULL;
+ BOOL rc = 0;
+
+ /*
+ * Should fail. Will write the size of the profile path in
+ * psize. Also note that the second parameter can't be
+ * NULL or the function fails on Windows XP, but has to be NULL on
+ * Windows 10 or it will fail. :(
+ */
+ rc = pGetDir(accessToken, NULL, &psize);
+ GOTO_IF(rc, PHYSFS_ERR_OS_ERROR, done); /* should have failed! */
+
+ if (psize == 0) /* probably on Windows XP, try a different way. */
+ {
+ WCHAR x = 0;
+ rc = pGetDir(accessToken, &x, &psize);
+ GOTO_IF(rc, PHYSFS_ERR_OS_ERROR, done); /* should have failed! */
+ GOTO_IF(!psize, PHYSFS_ERR_OS_ERROR, done); /* Uhoh... */
+ } /* if */
+
+ /* Allocate memory for the profile directory */
+ wstr = (LPWSTR) __PHYSFS_smallAlloc((psize + 1) * sizeof (WCHAR));
+ if (wstr != NULL)
+ {
+ if (pGetDir(accessToken, wstr, &psize))
+ {
+ /* Make sure it ends in a dirsep. We allocated +1 for this. */
+ if (wstr[psize - 2] != '\\')
+ {
+ wstr[psize - 1] = '\\';
+ wstr[psize - 0] = '\0';
+ } /* if */
+ retval = unicodeToUtf8Heap(wstr);
+ } /* if */
+ __PHYSFS_smallFree(wstr);
+ } /* if */
+ } /* if */
+
+done:
+ if (accessToken)
+ CloseHandle(accessToken);
+ FreeLibrary(lib);
+ return retval; /* We made it: hit the showers. */
+#endif
+} /* __PHYSFS_platformCalcUserDir */
+
+
+int __PHYSFS_platformInit(void)
+{
+ return 1; /* It's all good */
+} /* __PHYSFS_platformInit */
+
+
+void __PHYSFS_platformDeinit(void)
+{
+ deinitCDThread();
+} /* __PHYSFS_platformDeinit */
+
+
+void *__PHYSFS_platformGetThreadID(void)
+{
+ return ( (void *) ((size_t) GetCurrentThreadId()) );
+} /* __PHYSFS_platformGetThreadID */
+
+
+PHYSFS_EnumerateCallbackResult __PHYSFS_platformEnumerate(const char *dirname,
+ PHYSFS_EnumerateCallback callback,
+ const char *origdir, void *callbackdata)
+{
+ PHYSFS_EnumerateCallbackResult retval = PHYSFS_ENUM_OK;
+ HANDLE dir = INVALID_HANDLE_VALUE;
+ WIN32_FIND_DATAW entw;
+ size_t len = strlen(dirname);
+ char *searchPath = NULL;
+ WCHAR *wSearchPath = NULL;
+
+ /* Allocate a new string for path, maybe '\\', "*", and NULL terminator */
+ searchPath = (char *) __PHYSFS_smallAlloc(len + 3);
+ BAIL_IF(!searchPath, PHYSFS_ERR_OUT_OF_MEMORY, PHYSFS_ENUM_ERROR);
+
+ /* Copy current dirname */
+ strcpy(searchPath, dirname);
+
+ /* if there's no '\\' at the end of the path, stick one in there. */
+ if (searchPath[len - 1] != '\\')
+ {
+ searchPath[len++] = '\\';
+ searchPath[len] = '\0';
+ } /* if */
+
+ /* Append the "*" to the end of the string */
+ strcat(searchPath, "*");
+
+ UTF8_TO_UNICODE_STACK(wSearchPath, searchPath);
+ __PHYSFS_smallFree(searchPath);
+ BAIL_IF_ERRPASS(!wSearchPath, PHYSFS_ENUM_ERROR);
+
+ dir = winFindFirstFileW(wSearchPath, &entw);
+ __PHYSFS_smallFree(wSearchPath);
+ BAIL_IF(dir==INVALID_HANDLE_VALUE, errcodeFromWinApi(), PHYSFS_ENUM_ERROR);
+
+ do
+ {
+ const WCHAR *fn = entw.cFileName;
+ char *utf8;
+
+ if (fn[0] == '.') /* ignore "." and ".." */
+ {
+ if ((fn[1] == '\0') || ((fn[1] == '.') && (fn[2] == '\0')))
+ continue;
+ } /* if */
+
+ utf8 = unicodeToUtf8Heap(fn);
+ if (utf8 == NULL)
+ retval = -1;
+ else
+ {
+ retval = callback(callbackdata, origdir, utf8);
+ allocator.Free(utf8);
+ if (retval == PHYSFS_ENUM_ERROR)
+ PHYSFS_setErrorCode(PHYSFS_ERR_APP_CALLBACK);
+ } /* else */
+ } while ((retval == PHYSFS_ENUM_OK) && (FindNextFileW(dir, &entw) != 0));
+
+ FindClose(dir);
+
+ return retval;
+} /* __PHYSFS_platformEnumerate */
+
+
+int __PHYSFS_platformMkDir(const char *path)
+{
+ WCHAR *wpath;
+ DWORD rc;
+ UTF8_TO_UNICODE_STACK(wpath, path);
+ rc = CreateDirectoryW(wpath, NULL);
+ __PHYSFS_smallFree(wpath);
+ BAIL_IF(rc == 0, errcodeFromWinApi(), 0);
+ return 1;
+} /* __PHYSFS_platformMkDir */
+
+
+static HANDLE doOpen(const char *fname, DWORD mode, DWORD creation)
+{
+ HANDLE fileh;
+ WCHAR *wfname;
+
+ UTF8_TO_UNICODE_STACK(wfname, fname);
+ BAIL_IF(!wfname, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+
+ fileh = winCreateFileW(wfname, mode, creation);
+ __PHYSFS_smallFree(wfname);
+
+ if (fileh == INVALID_HANDLE_VALUE)
+ BAIL(errcodeFromWinApi(), INVALID_HANDLE_VALUE);
+
+ return fileh;
+} /* doOpen */
+
+
+void *__PHYSFS_platformOpenRead(const char *filename)
+{
+ HANDLE h = doOpen(filename, GENERIC_READ, OPEN_EXISTING);
+ return (h == INVALID_HANDLE_VALUE) ? NULL : (void *) h;
+} /* __PHYSFS_platformOpenRead */
+
+
+void *__PHYSFS_platformOpenWrite(const char *filename)
+{
+ HANDLE h = doOpen(filename, GENERIC_WRITE, CREATE_ALWAYS);
+ return (h == INVALID_HANDLE_VALUE) ? NULL : (void *) h;
+} /* __PHYSFS_platformOpenWrite */
+
+
+void *__PHYSFS_platformOpenAppend(const char *filename)
+{
+ HANDLE h = doOpen(filename, GENERIC_WRITE, OPEN_ALWAYS);
+ BAIL_IF_ERRPASS(h == INVALID_HANDLE_VALUE, NULL);
+
+ if (!winSetFilePointer(h, 0, NULL, FILE_END))
+ {
+ const PHYSFS_ErrorCode err = errcodeFromWinApi();
+ CloseHandle(h);
+ BAIL(err, NULL);
+ } /* if */
+
+ return (void *) h;
+} /* __PHYSFS_platformOpenAppend */
+
+
+PHYSFS_sint64 __PHYSFS_platformRead(void *opaque, void *buf, PHYSFS_uint64 len)
+{
+ HANDLE h = (HANDLE) opaque;
+ PHYSFS_sint64 totalRead = 0;
+
+ if (!__PHYSFS_ui64FitsAddressSpace(len))
+ BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
+
+ while (len > 0)
+ {
+ const DWORD thislen = (len > 0xFFFFFFFF) ? 0xFFFFFFFF : (DWORD) len;
+ DWORD numRead = 0;
+ if (!ReadFile(h, buf, thislen, &numRead, NULL))
+ BAIL(errcodeFromWinApi(), -1);
+ len -= (PHYSFS_uint64) numRead;
+ totalRead += (PHYSFS_sint64) numRead;
+ if (numRead != thislen)
+ break;
+ } /* while */
+
+ return totalRead;
+} /* __PHYSFS_platformRead */
+
+
+PHYSFS_sint64 __PHYSFS_platformWrite(void *opaque, const void *buffer,
+ PHYSFS_uint64 len)
+{
+ HANDLE h = (HANDLE) opaque;
+ PHYSFS_sint64 totalWritten = 0;
+
+ if (!__PHYSFS_ui64FitsAddressSpace(len))
+ BAIL(PHYSFS_ERR_INVALID_ARGUMENT, -1);
+
+ while (len > 0)
+ {
+ const DWORD thislen = (len > 0xFFFFFFFF) ? 0xFFFFFFFF : (DWORD) len;
+ DWORD numWritten = 0;
+ if (!WriteFile(h, buffer, thislen, &numWritten, NULL))
+ BAIL(errcodeFromWinApi(), -1);
+ len -= (PHYSFS_uint64) numWritten;
+ totalWritten += (PHYSFS_sint64) numWritten;
+ if (numWritten != thislen)
+ break;
+ } /* while */
+
+ return totalWritten;
+} /* __PHYSFS_platformWrite */
+
+
+int __PHYSFS_platformSeek(void *opaque, PHYSFS_uint64 pos)
+{
+ HANDLE h = (HANDLE) opaque;
+ const PHYSFS_sint64 spos = (PHYSFS_sint64) pos;
+ BAIL_IF(!winSetFilePointer(h,spos,NULL,FILE_BEGIN), errcodeFromWinApi(), 0);
+ return 1; /* No error occured */
+} /* __PHYSFS_platformSeek */
+
+
+PHYSFS_sint64 __PHYSFS_platformTell(void *opaque)
+{
+ HANDLE h = (HANDLE) opaque;
+ PHYSFS_sint64 pos = 0;
+ BAIL_IF(!winSetFilePointer(h,0,&pos,FILE_CURRENT), errcodeFromWinApi(), -1);
+ return pos;
+} /* __PHYSFS_platformTell */
+
+
+PHYSFS_sint64 __PHYSFS_platformFileLength(void *opaque)
+{
+ HANDLE h = (HANDLE) opaque;
+ const PHYSFS_sint64 retval = winGetFileSize(h);
+ BAIL_IF(retval < 0, errcodeFromWinApi(), -1);
+ return retval;
+} /* __PHYSFS_platformFileLength */
+
+
+int __PHYSFS_platformFlush(void *opaque)
+{
+ HANDLE h = (HANDLE) opaque;
+ BAIL_IF(!FlushFileBuffers(h), errcodeFromWinApi(), 0);
+ return 1;
+} /* __PHYSFS_platformFlush */
+
+
+void __PHYSFS_platformClose(void *opaque)
+{
+ HANDLE h = (HANDLE) opaque;
+ (void) CloseHandle(h); /* ignore errors. You should have flushed! */
+} /* __PHYSFS_platformClose */
+
+
+static int doPlatformDelete(LPWSTR wpath)
+{
+ WIN32_FILE_ATTRIBUTE_DATA info;
+ if (!GetFileAttributesExW(wpath, GetFileExInfoStandard, &info))
+ BAIL(errcodeFromWinApi(), 0);
+ else
+ {
+ const int isdir = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+ const BOOL rc = isdir ? RemoveDirectoryW(wpath) : DeleteFileW(wpath);
+ BAIL_IF(!rc, errcodeFromWinApi(), 0);
+ } /* else */
+ return 1; /* if you made it here, it worked. */
+} /* doPlatformDelete */
+
+
+int __PHYSFS_platformDelete(const char *path)
+{
+ int retval = 0;
+ LPWSTR wpath = NULL;
+ UTF8_TO_UNICODE_STACK(wpath, path);
+ BAIL_IF(!wpath, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ retval = doPlatformDelete(wpath);
+ __PHYSFS_smallFree(wpath);
+ return retval;
+} /* __PHYSFS_platformDelete */
+
+
+void *__PHYSFS_platformCreateMutex(void)
+{
+ LPCRITICAL_SECTION lpcs;
+ lpcs = (LPCRITICAL_SECTION) allocator.Malloc(sizeof (CRITICAL_SECTION));
+ BAIL_IF(!lpcs, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+
+ if (!winInitializeCriticalSection(lpcs))
+ {
+ allocator.Free(lpcs);
+ BAIL(errcodeFromWinApi(), NULL);
+ } /* if */
+
+ return lpcs;
+} /* __PHYSFS_platformCreateMutex */
+
+
+void __PHYSFS_platformDestroyMutex(void *mutex)
+{
+ DeleteCriticalSection((LPCRITICAL_SECTION) mutex);
+ allocator.Free(mutex);
+} /* __PHYSFS_platformDestroyMutex */
+
+
+int __PHYSFS_platformGrabMutex(void *mutex)
+{
+ EnterCriticalSection((LPCRITICAL_SECTION) mutex);
+ return 1;
+} /* __PHYSFS_platformGrabMutex */
+
+
+void __PHYSFS_platformReleaseMutex(void *mutex)
+{
+ LeaveCriticalSection((LPCRITICAL_SECTION) mutex);
+} /* __PHYSFS_platformReleaseMutex */
+
+
+static PHYSFS_sint64 FileTimeToPhysfsTime(const FILETIME *ft)
+{
+ SYSTEMTIME st_utc;
+ SYSTEMTIME st_localtz;
+ TIME_ZONE_INFORMATION tzi;
+ DWORD tzid;
+ PHYSFS_sint64 retval;
+ struct tm tm;
+ BOOL rc;
+
+ BAIL_IF(!FileTimeToSystemTime(ft, &st_utc), errcodeFromWinApi(), -1);
+ tzid = GetTimeZoneInformation(&tzi);
+ BAIL_IF(tzid == TIME_ZONE_ID_INVALID, errcodeFromWinApi(), -1);
+ rc = SystemTimeToTzSpecificLocalTime(&tzi, &st_utc, &st_localtz);
+ BAIL_IF(!rc, errcodeFromWinApi(), -1);
+
+ /* Convert to a format that mktime() can grok... */
+ tm.tm_sec = st_localtz.wSecond;
+ tm.tm_min = st_localtz.wMinute;
+ tm.tm_hour = st_localtz.wHour;
+ tm.tm_mday = st_localtz.wDay;
+ tm.tm_mon = st_localtz.wMonth - 1;
+ tm.tm_year = st_localtz.wYear - 1900;
+ tm.tm_wday = -1 /*st_localtz.wDayOfWeek*/;
+ tm.tm_yday = -1;
+ tm.tm_isdst = -1;
+
+ /* Convert to a format PhysicsFS can grok... */
+ retval = (PHYSFS_sint64) mktime(&tm);
+ BAIL_IF(retval == -1, PHYSFS_ERR_OS_ERROR, -1);
+ return retval;
+} /* FileTimeToPhysfsTime */
+
+
+/* check for symlinks. These exist in NTFS 3.1 (WinXP), even though
+ they aren't really available to userspace before Vista. I wonder
+ what would happen if you put an NTFS disk with a symlink on it
+ into an XP machine, though; would this flag get set?
+ NTFS symlinks are a form of "reparse point" (junction, volume mount,
+ etc), so if the REPARSE_POINT attribute is set, check for the symlink
+ tag thereafter. This assumes you already read in the file attributes. */
+static int isSymlink(const WCHAR *wpath, const DWORD attr)
+{
+ WIN32_FIND_DATAW w32dw;
+ HANDLE h;
+
+ if ((attr & PHYSFS_FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+ return 0; /* not a reparse point? Definitely not a symlink. */
+
+ h = winFindFirstFileW(wpath, &w32dw);
+ if (h == INVALID_HANDLE_VALUE)
+ return 0; /* ...maybe the file just vanished...? */
+
+ FindClose(h);
+ return (w32dw.dwReserved0 == PHYSFS_IO_REPARSE_TAG_SYMLINK);
+} /* isSymlink */
+
+
+int __PHYSFS_platformStat(const char *filename, PHYSFS_Stat *st, const int follow)
+{
+ WIN32_FILE_ATTRIBUTE_DATA winstat;
+ WCHAR *wstr = NULL;
+ DWORD err = 0;
+ BOOL rc = 0;
+ int issymlink = 0;
+
+ UTF8_TO_UNICODE_STACK(wstr, filename);
+ BAIL_IF(!wstr, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ rc = GetFileAttributesExW(wstr, GetFileExInfoStandard, &winstat);
+
+ if (!rc)
+ err = GetLastError();
+ else /* check for symlink while wstr is still available */
+ issymlink = !follow && isSymlink(wstr, winstat.dwFileAttributes);
+
+ __PHYSFS_smallFree(wstr);
+ BAIL_IF(!rc, errcodeFromWinApiError(err), 0);
+
+ st->modtime = FileTimeToPhysfsTime(&winstat.ftLastWriteTime);
+ st->accesstime = FileTimeToPhysfsTime(&winstat.ftLastAccessTime);
+ st->createtime = FileTimeToPhysfsTime(&winstat.ftCreationTime);
+
+ if (issymlink)
+ {
+ st->filetype = PHYSFS_FILETYPE_SYMLINK;
+ st->filesize = 0;
+ } /* if */
+
+ else if (winstat.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ st->filetype = PHYSFS_FILETYPE_DIRECTORY;
+ st->filesize = 0;
+ } /* else if */
+
+ else if (winstat.dwFileAttributes & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_DEVICE))
+ {
+ st->filetype = PHYSFS_FILETYPE_OTHER;
+ st->filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow;
+ } /* else if */
+
+ else
+ {
+ st->filetype = PHYSFS_FILETYPE_REGULAR;
+ st->filesize = (((PHYSFS_uint64) winstat.nFileSizeHigh) << 32) | winstat.nFileSizeLow;
+ } /* else */
+
+ st->readonly = ((winstat.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
+
+ return 1;
+} /* __PHYSFS_platformStat */
+
+#endif /* PHYSFS_PLATFORM_WINDOWS */
+
+/* end of physfs_platform_windows.c ... */
+
+
diff --git a/third-party/physfs/src/physfs_platform_winrt.cpp b/third-party/physfs/src/physfs_platform_winrt.cpp
new file mode 100644
index 0000000..f50fc9d
--- /dev/null
+++ b/third-party/physfs/src/physfs_platform_winrt.cpp
@@ -0,0 +1,41 @@
+/*
+ * Windows Runtime (WinRT) support routines for PhysicsFS.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file originally written by Martin "T-Bone" Ahrnbom, but was mostly
+ * merged into physfs_platform_windows.c by Ryan C. Gordon (so please harass
+ * Ryan about bugs and not Martin).
+ */
+
+/* (There used to be instructions on how to make a WinRT project, but at
+ this point, either CMake will do it for you or you should just drop
+ PhysicsFS's sources into your existing project. --ryan.) */
+
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_platforms.h"
+
+#ifdef PHYSFS_PLATFORM_WINRT
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS 1
+#endif
+#include
+
+#include "physfs_internal.h"
+
+const void *__PHYSFS_winrtCalcBaseDir(void)
+{
+ return Windows::ApplicationModel::Package::Current->InstalledLocation->Path->Data();
+} /* __PHYSFS_winrtCalcBaseDir */
+
+const void *__PHYSFS_winrtCalcPrefDir(void)
+{
+ return Windows::Storage::ApplicationData::Current->LocalFolder->Path->Data();
+} /* __PHYSFS_winrtCalcBaseDir */
+
+
+#endif /* PHYSFS_PLATFORM_WINRT */
+
+/* end of physfs_platform_winrt.cpp ... */
+
diff --git a/third-party/physfs/src/physfs_platforms.h b/third-party/physfs/src/physfs_platforms.h
new file mode 100644
index 0000000..1ac17d9
--- /dev/null
+++ b/third-party/physfs/src/physfs_platforms.h
@@ -0,0 +1,80 @@
+#ifndef _INCL_PHYSFS_PLATFORMS
+#define _INCL_PHYSFS_PLATFORMS
+
+#ifndef __PHYSICSFS_INTERNAL__
+#error Do not include this header from your applications.
+#endif
+
+/*
+ * These only define the platforms to determine which files in the platforms
+ * directory should be compiled. For example, technically BeOS can be called
+ * a "unix" system, but since it doesn't use unix.c, we don't define
+ * PHYSFS_PLATFORM_UNIX on that system.
+ */
+
+#if (defined __HAIKU__)
+# define PHYSFS_PLATFORM_HAIKU 1
+# define PHYSFS_PLATFORM_POSIX 1
+#elif ((defined __BEOS__) || (defined __beos__))
+# error BeOS support was dropped since PhysicsFS 2.1. Sorry. Try Haiku!
+#elif (defined _WIN32_WCE) || (defined _WIN64_WCE)
+# error PocketPC support was dropped since PhysicsFS 2.1. Sorry. Try WinRT!
+#elif (defined(_MSC_VER) && (_MSC_VER >= 1700) && !_USING_V110_SDK71_) /* _MSC_VER==1700 for MSVC 2012 */
+# include
+# define PHYSFS_PLATFORM_WINDOWS 1
+# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+# define PHYSFS_NO_CDROM_SUPPORT 1
+# define PHYSFS_PLATFORM_WINRT 1
+# endif
+#elif (((defined _WIN32) || (defined _WIN64)) && (!defined __CYGWIN__))
+# define PHYSFS_PLATFORM_WINDOWS 1
+#elif defined(__OS2__) || defined(OS2)
+# define PHYSFS_PLATFORM_OS2 1
+#elif ((defined __MACH__) && (defined __APPLE__))
+/* To check if iOS or not, we need to include this file */
+# include
+# if ((TARGET_IPHONE_SIMULATOR) || (TARGET_OS_IPHONE))
+# define PHYSFS_NO_CDROM_SUPPORT 1
+# endif
+# define PHYSFS_PLATFORM_APPLE 1
+# define PHYSFS_PLATFORM_POSIX 1
+#elif defined(macintosh)
+# error Classic Mac OS support was dropped from PhysicsFS 2.0. Move to OS X.
+#elif defined(__ANDROID__)
+ # define PHYSFS_PLATFORM_LINUX 1
+ # define PHYSFS_PLATFORM_ANDROID 1
+ # define PHYSFS_PLATFORM_POSIX 1
+ # define PHYSFS_NO_CDROM_SUPPORT 1
+#elif defined(__linux)
+# define PHYSFS_PLATFORM_LINUX 1
+# define PHYSFS_PLATFORM_UNIX 1
+# define PHYSFS_PLATFORM_POSIX 1
+#elif defined(__sun) || defined(sun)
+# define PHYSFS_PLATFORM_SOLARIS 1
+# define PHYSFS_PLATFORM_UNIX 1
+# define PHYSFS_PLATFORM_POSIX 1
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
+# define PHYSFS_PLATFORM_FREEBSD 1
+# define PHYSFS_PLATFORM_BSD 1
+# define PHYSFS_PLATFORM_UNIX 1
+# define PHYSFS_PLATFORM_POSIX 1
+#elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__)
+# define PHYSFS_PLATFORM_BSD 1
+# define PHYSFS_PLATFORM_UNIX 1
+# define PHYSFS_PLATFORM_POSIX 1
+#elif defined(__EMSCRIPTEN__)
+# define PHYSFS_NO_CDROM_SUPPORT 1
+# define PHYSFS_PLATFORM_UNIX 1
+# define PHYSFS_PLATFORM_POSIX 1
+#elif defined(__QNX__)
+# define PHYSFS_PLATFORM_QNX 1
+# define PHYSFS_PLATFORM_POSIX 1
+#elif defined(unix) || defined(__unix__)
+# define PHYSFS_PLATFORM_UNIX 1
+# define PHYSFS_PLATFORM_POSIX 1
+#else
+# error Unknown platform.
+#endif
+
+#endif /* include-once blocker. */
+
diff --git a/third-party/physfs/src/physfs_unicode.c b/third-party/physfs/src/physfs_unicode.c
new file mode 100644
index 0000000..bab4f8b
--- /dev/null
+++ b/third-party/physfs/src/physfs_unicode.c
@@ -0,0 +1,572 @@
+#define __PHYSICSFS_INTERNAL__
+#include "physfs_internal.h"
+
+#include "physfs_casefolding.h"
+
+
+/*
+ * From rfc3629, the UTF-8 spec:
+ * https://www.ietf.org/rfc/rfc3629.txt
+ *
+ * Char. number range | UTF-8 octet sequence
+ * (hexadecimal) | (binary)
+ * --------------------+---------------------------------------------
+ * 0000 0000-0000 007F | 0xxxxxxx
+ * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
+ * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
+ * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+
+
+/*
+ * This may not be the best value, but it's one that isn't represented
+ * in Unicode (0x10FFFF is the largest codepoint value). We return this
+ * value from __PHYSFS_utf8codepoint() if there's bogus bits in the
+ * stream. __PHYSFS_utf8codepoint() will turn this value into something
+ * reasonable (like a question mark), for text that wants to try to recover,
+ * whereas utf8valid() will use the value to determine if a string has bad
+ * bits.
+ */
+#define UNICODE_BOGUS_CHAR_VALUE 0xFFFFFFFF
+
+/*
+ * This is the codepoint we currently return when there was bogus bits in a
+ * UTF-8 string. May not fly in Asian locales?
+ */
+#define UNICODE_BOGUS_CHAR_CODEPOINT '?'
+
+PHYSFS_uint32 __PHYSFS_utf8codepoint(const char **_str)
+{
+ const char *str = *_str;
+ PHYSFS_uint32 retval = 0;
+ PHYSFS_uint32 octet = (PHYSFS_uint32) ((PHYSFS_uint8) *str);
+ PHYSFS_uint32 octet2, octet3, octet4;
+
+ if (octet == 0) /* null terminator, end of string. */
+ return 0;
+
+ else if (octet < 128) /* one octet char: 0 to 127 */
+ {
+ (*_str)++; /* skip to next possible start of codepoint. */
+ return octet;
+ } /* else if */
+
+ else if ((octet > 127) && (octet < 192)) /* bad (starts with 10xxxxxx). */
+ {
+ /*
+ * Apparently each of these is supposed to be flagged as a bogus
+ * char, instead of just resyncing to the next valid codepoint.
+ */
+ (*_str)++; /* skip to next possible start of codepoint. */
+ return UNICODE_BOGUS_CHAR_VALUE;
+ } /* else if */
+
+ else if (octet < 224) /* two octets */
+ {
+ (*_str)++; /* advance at least one byte in case of an error */
+ octet -= (128+64);
+ octet2 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ *_str += 1; /* skip to next possible start of codepoint. */
+ retval = ((octet << 6) | (octet2 - 128));
+ if ((retval >= 0x80) && (retval <= 0x7FF))
+ return retval;
+ } /* else if */
+
+ else if (octet < 240) /* three octets */
+ {
+ (*_str)++; /* advance at least one byte in case of an error */
+ octet -= (128+64+32);
+ octet2 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet3 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ *_str += 2; /* skip to next possible start of codepoint. */
+ retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) );
+
+ /* There are seven "UTF-16 surrogates" that are illegal in UTF-8. */
+ switch (retval)
+ {
+ case 0xD800:
+ case 0xDB7F:
+ case 0xDB80:
+ case 0xDBFF:
+ case 0xDC00:
+ case 0xDF80:
+ case 0xDFFF:
+ return UNICODE_BOGUS_CHAR_VALUE;
+ } /* switch */
+
+ /* 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge. */
+ if ((retval >= 0x800) && (retval <= 0xFFFD))
+ return retval;
+ } /* else if */
+
+ else if (octet < 248) /* four octets */
+ {
+ (*_str)++; /* advance at least one byte in case of an error */
+ octet -= (128+64+32+16);
+ octet2 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet3 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet4 = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet4 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ *_str += 3; /* skip to next possible start of codepoint. */
+ retval = ( ((octet << 18)) | ((octet2 - 128) << 12) |
+ ((octet3 - 128) << 6) | ((octet4 - 128)) );
+ if ((retval >= 0x10000) && (retval <= 0x10FFFF))
+ return retval;
+ } /* else if */
+
+ /*
+ * Five and six octet sequences became illegal in rfc3629.
+ * We throw the codepoint away, but parse them to make sure we move
+ * ahead the right number of bytes and don't overflow the buffer.
+ */
+
+ else if (octet < 252) /* five octets */
+ {
+ (*_str)++; /* advance at least one byte in case of an error */
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ *_str += 4; /* skip to next possible start of codepoint. */
+ return UNICODE_BOGUS_CHAR_VALUE;
+ } /* else if */
+
+ else /* six octets */
+ {
+ (*_str)++; /* advance at least one byte in case of an error */
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ octet = (PHYSFS_uint32) ((PHYSFS_uint8) *(++str));
+ if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
+ return UNICODE_BOGUS_CHAR_VALUE;
+
+ *_str += 6; /* skip to next possible start of codepoint. */
+ return UNICODE_BOGUS_CHAR_VALUE;
+ } /* else if */
+
+ return UNICODE_BOGUS_CHAR_VALUE;
+} /* __PHYSFS_utf8codepoint */
+
+static inline PHYSFS_uint32 utf8codepoint(const char **_str)
+{
+ return __PHYSFS_utf8codepoint(_str);
+} /* utf8codepoint */
+
+static PHYSFS_uint32 utf16codepoint(const PHYSFS_uint16 **_str)
+{
+ const PHYSFS_uint16 *src = *_str;
+ PHYSFS_uint32 cp = (PHYSFS_uint32) *(src++);
+
+ if (cp == 0) /* null terminator, end of string. */
+ return 0;
+ /* Orphaned second half of surrogate pair? */
+ else if ((cp >= 0xDC00) && (cp <= 0xDFFF))
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+ else if ((cp >= 0xD800) && (cp <= 0xDBFF)) /* start surrogate pair! */
+ {
+ const PHYSFS_uint32 pair = (PHYSFS_uint32) *src;
+ if (pair == 0)
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+ else if ((pair < 0xDC00) || (pair > 0xDFFF))
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+ else
+ {
+ src++; /* eat the other surrogate. */
+ cp = 0x10000 + (((cp - 0xD800) << 10) | (pair - 0xDC00));
+ } /* else */
+ } /* else if */
+
+ *_str = src;
+ return cp;
+} /* utf16codepoint */
+
+static PHYSFS_uint32 utf32codepoint(const PHYSFS_uint32 **_str)
+{
+ const PHYSFS_uint32 *src = *_str;
+ PHYSFS_uint32 cp = *(src++);
+
+ if (cp == 0) /* null terminator, end of string. */
+ return 0;
+ else if (cp > 0x10FFF)
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+
+ *_str = src;
+ return cp;
+} /* utf32codepoint */
+
+
+void PHYSFS_utf8ToUcs4(const char *src, PHYSFS_uint32 *dst, PHYSFS_uint64 len)
+{
+ len -= sizeof (PHYSFS_uint32); /* save room for null char. */
+ while (len >= sizeof (PHYSFS_uint32))
+ {
+ PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
+ if (cp == 0)
+ break;
+ else if (cp == UNICODE_BOGUS_CHAR_VALUE)
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+ *(dst++) = cp;
+ len -= sizeof (PHYSFS_uint32);
+ } /* while */
+
+ *dst = 0;
+} /* PHYSFS_utf8ToUcs4 */
+
+
+void PHYSFS_utf8ToUcs2(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len)
+{
+ len -= sizeof (PHYSFS_uint16); /* save room for null char. */
+ while (len >= sizeof (PHYSFS_uint16))
+ {
+ PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
+ if (cp == 0)
+ break;
+ else if (cp == UNICODE_BOGUS_CHAR_VALUE)
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+
+ if (cp > 0xFFFF) /* UTF-16 surrogates (bogus chars in UCS-2) */
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+
+ *(dst++) = cp;
+ len -= sizeof (PHYSFS_uint16);
+ } /* while */
+
+ *dst = 0;
+} /* PHYSFS_utf8ToUcs2 */
+
+
+void PHYSFS_utf8ToUtf16(const char *src, PHYSFS_uint16 *dst, PHYSFS_uint64 len)
+{
+ len -= sizeof (PHYSFS_uint16); /* save room for null char. */
+ while (len >= sizeof (PHYSFS_uint16))
+ {
+ PHYSFS_uint32 cp = __PHYSFS_utf8codepoint(&src);
+ if (cp == 0)
+ break;
+ else if (cp == UNICODE_BOGUS_CHAR_VALUE)
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+
+ if (cp > 0xFFFF) /* encode as surrogate pair */
+ {
+ if (len < (sizeof (PHYSFS_uint16) * 2))
+ break; /* not enough room for the pair, stop now. */
+
+ cp -= 0x10000; /* Make this a 20-bit value */
+
+ *(dst++) = 0xD800 + ((cp >> 10) & 0x3FF);
+ len -= sizeof (PHYSFS_uint16);
+
+ cp = 0xDC00 + (cp & 0x3FF);
+ } /* if */
+
+ *(dst++) = cp;
+ len -= sizeof (PHYSFS_uint16);
+ } /* while */
+
+ *dst = 0;
+} /* PHYSFS_utf8ToUtf16 */
+
+static void utf8fromcodepoint(PHYSFS_uint32 cp, char **_dst, PHYSFS_uint64 *_len)
+{
+ char *dst = *_dst;
+ PHYSFS_uint64 len = *_len;
+
+ if (len == 0)
+ return;
+
+ if (cp > 0x10FFFF)
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+ else if ((cp == 0xFFFE) || (cp == 0xFFFF)) /* illegal values. */
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+ else
+ {
+ /* There are seven "UTF-16 surrogates" that are illegal in UTF-8. */
+ switch (cp)
+ {
+ case 0xD800:
+ case 0xDB7F:
+ case 0xDB80:
+ case 0xDBFF:
+ case 0xDC00:
+ case 0xDF80:
+ case 0xDFFF:
+ cp = UNICODE_BOGUS_CHAR_CODEPOINT;
+ } /* switch */
+ } /* else */
+
+ /* Do the encoding... */
+ if (cp < 0x80)
+ {
+ *(dst++) = (char) cp;
+ len--;
+ } /* if */
+
+ else if (cp < 0x800)
+ {
+ if (len < 2)
+ len = 0;
+ else
+ {
+ *(dst++) = (char) ((cp >> 6) | 128 | 64);
+ *(dst++) = (char) (cp & 0x3F) | 128;
+ len -= 2;
+ } /* else */
+ } /* else if */
+
+ else if (cp < 0x10000)
+ {
+ if (len < 3)
+ len = 0;
+ else
+ {
+ *(dst++) = (char) ((cp >> 12) | 128 | 64 | 32);
+ *(dst++) = (char) ((cp >> 6) & 0x3F) | 128;
+ *(dst++) = (char) (cp & 0x3F) | 128;
+ len -= 3;
+ } /* else */
+ } /* else if */
+
+ else
+ {
+ if (len < 4)
+ len = 0;
+ else
+ {
+ *(dst++) = (char) ((cp >> 18) | 128 | 64 | 32 | 16);
+ *(dst++) = (char) ((cp >> 12) & 0x3F) | 128;
+ *(dst++) = (char) ((cp >> 6) & 0x3F) | 128;
+ *(dst++) = (char) (cp & 0x3F) | 128;
+ len -= 4;
+ } /* else if */
+ } /* else */
+
+ *_dst = dst;
+ *_len = len;
+} /* utf8fromcodepoint */
+
+#define UTF8FROMTYPE(typ, src, dst, len) \
+ if (len == 0) return; \
+ len--; \
+ while (len) \
+ { \
+ const PHYSFS_uint32 cp = (PHYSFS_uint32) ((typ) (*(src++))); \
+ if (cp == 0) break; \
+ utf8fromcodepoint(cp, &dst, &len); \
+ } \
+ *dst = '\0'; \
+
+void PHYSFS_utf8FromUcs4(const PHYSFS_uint32 *src, char *dst, PHYSFS_uint64 len)
+{
+ UTF8FROMTYPE(PHYSFS_uint32, src, dst, len);
+} /* PHYSFS_utf8FromUcs4 */
+
+void PHYSFS_utf8FromUcs2(const PHYSFS_uint16 *src, char *dst, PHYSFS_uint64 len)
+{
+ UTF8FROMTYPE(PHYSFS_uint64, src, dst, len);
+} /* PHYSFS_utf8FromUcs2 */
+
+/* latin1 maps to unicode codepoints directly, we just utf-8 encode it. */
+void PHYSFS_utf8FromLatin1(const char *src, char *dst, PHYSFS_uint64 len)
+{
+ UTF8FROMTYPE(PHYSFS_uint8, src, dst, len);
+} /* PHYSFS_utf8FromLatin1 */
+
+#undef UTF8FROMTYPE
+
+
+void PHYSFS_utf8FromUtf16(const PHYSFS_uint16 *src, char *dst, PHYSFS_uint64 len)
+{
+ if (len == 0)
+ return;
+
+ len--;
+ while (len)
+ {
+ const PHYSFS_uint32 cp = utf16codepoint(&src);
+ if (!cp)
+ break;
+ utf8fromcodepoint(cp, &dst, &len);
+ } /* while */
+
+ *dst = '\0';
+} /* PHYSFS_utf8FromUtf16 */
+
+
+int PHYSFS_caseFold(const PHYSFS_uint32 from, PHYSFS_uint32 *to)
+{
+ int i;
+
+ if (from < 128) /* low-ASCII, easy! */
+ {
+ if ((from >= 'A') && (from <= 'Z'))
+ *to = from - ('A' - 'a');
+ else
+ *to = from;
+ return 1;
+ } /* if */
+
+ else if (from <= 0xFFFF)
+ {
+ const PHYSFS_uint8 hash = ((from ^ (from >> 8)) & 0xFF);
+ const PHYSFS_uint16 from16 = (PHYSFS_uint16) from;
+
+ {
+ const CaseFoldHashBucket1_16 *bucket = &case_fold_hash1_16[hash];
+ const int count = (int) bucket->count;
+ for (i = 0; i < count; i++)
+ {
+ const CaseFoldMapping1_16 *mapping = &bucket->list[i];
+ if (mapping->from == from16)
+ {
+ *to = mapping->to0;
+ return 1;
+ } /* if */
+ } /* for */
+ }
+
+ {
+ const CaseFoldHashBucket2_16 *bucket = &case_fold_hash2_16[hash & 15];
+ const int count = (int) bucket->count;
+ for (i = 0; i < count; i++)
+ {
+ const CaseFoldMapping2_16 *mapping = &bucket->list[i];
+ if (mapping->from == from16)
+ {
+ to[0] = mapping->to0;
+ to[1] = mapping->to1;
+ return 2;
+ } /* if */
+ } /* for */
+ }
+
+ {
+ const CaseFoldHashBucket3_16 *bucket = &case_fold_hash3_16[hash & 3];
+ const int count = (int) bucket->count;
+ for (i = 0; i < count; i++)
+ {
+ const CaseFoldMapping3_16 *mapping = &bucket->list[i];
+ if (mapping->from == from16)
+ {
+ to[0] = mapping->to0;
+ to[1] = mapping->to1;
+ to[2] = mapping->to2;
+ return 3;
+ } /* if */
+ } /* for */
+ }
+ } /* else if */
+
+ else /* codepoint that doesn't fit in 16 bits. */
+ {
+ const PHYSFS_uint8 hash = ((from ^ (from >> 8)) & 0xFF);
+ const CaseFoldHashBucket1_32 *bucket = &case_fold_hash1_32[hash & 15];
+ const int count = (int) bucket->count;
+ for (i = 0; i < count; i++)
+ {
+ const CaseFoldMapping1_32 *mapping = &bucket->list[i];
+ if (mapping->from == from)
+ {
+ *to = mapping->to0;
+ return 1;
+ } /* if */
+ } /* for */
+ } /* else */
+
+
+ /* Not found...there's no remapping for this codepoint. */
+ *to = from;
+ return 1;
+} /* PHYSFS_caseFold */
+
+
+#define UTFSTRICMP(bits) \
+ PHYSFS_uint32 folded1[3], folded2[3]; \
+ int head1 = 0, tail1 = 0, head2 = 0, tail2 = 0; \
+ while (1) { \
+ PHYSFS_uint32 cp1, cp2; \
+ if (head1 != tail1) { \
+ cp1 = folded1[tail1++]; \
+ } else { \
+ head1 = PHYSFS_caseFold(utf##bits##codepoint(&str1), folded1); \
+ cp1 = folded1[0]; \
+ tail1 = 1; \
+ } \
+ if (head2 != tail2) { \
+ cp2 = folded2[tail2++]; \
+ } else { \
+ head2 = PHYSFS_caseFold(utf##bits##codepoint(&str2), folded2); \
+ cp2 = folded2[0]; \
+ tail2 = 1; \
+ } \
+ if (cp1 < cp2) { \
+ return -1; \
+ } else if (cp1 > cp2) { \
+ return 1; \
+ } else if (cp1 == 0) { \
+ break; /* complete match. */ \
+ } \
+ } \
+ return 0
+
+int PHYSFS_utf8stricmp(const char *str1, const char *str2)
+{
+ UTFSTRICMP(8);
+} /* PHYSFS_utf8stricmp */
+
+int PHYSFS_utf16stricmp(const PHYSFS_uint16 *str1, const PHYSFS_uint16 *str2)
+{
+ UTFSTRICMP(16);
+} /* PHYSFS_utf16stricmp */
+
+int PHYSFS_ucs4stricmp(const PHYSFS_uint32 *str1, const PHYSFS_uint32 *str2)
+{
+ UTFSTRICMP(32);
+} /* PHYSFS_ucs4stricmp */
+
+#undef UTFSTRICMP
+
+/* end of physfs_unicode.c ... */
+
diff --git a/third-party/physfs/test/test_physfs.c b/third-party/physfs/test/test_physfs.c
new file mode 100644
index 0000000..3d0fbbe
--- /dev/null
+++ b/third-party/physfs/test/test_physfs.c
@@ -0,0 +1,1651 @@
+/**
+ * Test program for PhysicsFS. May only work on Unix.
+ *
+ * Please see the file LICENSE.txt in the source's root directory.
+ *
+ * This file written by Ryan C. Gordon.
+ */
+
+#define _CRT_SECURE_NO_WARNINGS 1
+
+#include
+#include
+#include
+#include
+
+#if (defined __MWERKS__)
+#include
+#endif
+
+#if (defined PHYSFS_HAVE_READLINE)
+#include
+#include
+#include
+#endif
+
+#include
+
+/* Define this, so the compiler doesn't complain about using old APIs. */
+#define PHYSFS_DEPRECATED
+
+#include "physfs.h"
+
+#define TEST_VERSION_MAJOR 3
+#define TEST_VERSION_MINOR 2
+#define TEST_VERSION_PATCH 0
+
+static FILE *history_file = NULL;
+static PHYSFS_uint32 do_buffer_size = 0;
+
+static void output_versions(void)
+{
+ PHYSFS_Version compiled;
+ PHYSFS_Version linked;
+
+ PHYSFS_VERSION(&compiled);
+ PHYSFS_getLinkedVersion(&linked);
+
+ printf("test_physfs version %d.%d.%d.\n"
+ " Compiled against PhysicsFS version %d.%d.%d,\n"
+ " and linked against %d.%d.%d.\n\n",
+ TEST_VERSION_MAJOR, TEST_VERSION_MINOR, TEST_VERSION_PATCH,
+ (int) compiled.major, (int) compiled.minor, (int) compiled.patch,
+ (int) linked.major, (int) linked.minor, (int) linked.patch);
+} /* output_versions */
+
+
+static void output_archivers(void)
+{
+ const PHYSFS_ArchiveInfo **rc = PHYSFS_supportedArchiveTypes();
+ const PHYSFS_ArchiveInfo **i;
+
+ printf("Supported archive types:\n");
+ if (*rc == NULL)
+ printf(" * Apparently, NONE!\n");
+ else
+ {
+ for (i = rc; *i != NULL; i++)
+ {
+ printf(" * %s: %s\n Written by %s.\n %s\n",
+ (*i)->extension, (*i)->description,
+ (*i)->author, (*i)->url);
+ printf(" %s symbolic links.\n",
+ (*i)->supportsSymlinks ? "Supports" : "Does not support");
+ } /* for */
+ } /* else */
+
+ printf("\n");
+} /* output_archivers */
+
+
+static int cmd_quit(char *args)
+{
+ return 0;
+} /* cmd_quit */
+
+
+static int cmd_init(char *args)
+{
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ if (PHYSFS_init(args))
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_init */
+
+
+static int cmd_deinit(char *args)
+{
+ if (PHYSFS_deinit())
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_deinit */
+
+
+static int cmd_addarchive(char *args)
+{
+ char *ptr = strrchr(args, ' ');
+ int appending = atoi(ptr + 1);
+ *ptr = '\0';
+
+ if (*args == '\"')
+ {
+ args++;
+ *(ptr - 1) = '\0';
+ } /* if */
+
+ /*printf("[%s], [%d]\n", args, appending);*/
+
+ if (PHYSFS_mount(args, NULL, appending))
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_addarchive */
+
+
+/* wrap free() to avoid calling convention wankery. */
+static void freeBuf(void *buf)
+{
+ free(buf);
+} /* freeBuf */
+
+typedef enum
+{
+ MNTTYPE_PATH,
+ MNTTYPE_MEMORY,
+ MNTTYPE_HANDLE
+} MountType;
+
+static int cmd_mount_internal(char *args, const MountType mnttype)
+{
+ char *ptr;
+ char *mntpoint = NULL;
+ int appending = 0;
+ int rc = 0;
+
+ if (*args == '\"')
+ {
+ args++;
+ ptr = strchr(args, '\"');
+ if (ptr == NULL)
+ {
+ printf("missing string terminator in argument.\n");
+ return 1;
+ } /* if */
+ *(ptr) = '\0';
+ } /* if */
+ else
+ {
+ ptr = strchr(args, ' ');
+ *ptr = '\0';
+ } /* else */
+
+ mntpoint = ptr + 1;
+ if (*mntpoint == '\"')
+ {
+ mntpoint++;
+ ptr = strchr(mntpoint, '\"');
+ if (ptr == NULL)
+ {
+ printf("missing string terminator in argument.\n");
+ return 1;
+ } /* if */
+ *(ptr) = '\0';
+ } /* if */
+ else
+ {
+ ptr = strchr(mntpoint, ' ');
+ *(ptr) = '\0';
+ } /* else */
+ appending = atoi(ptr + 1);
+
+ /*printf("[%s], [%s], [%d]\n", args, mntpoint, appending);*/
+
+ if (mnttype == MNTTYPE_PATH)
+ rc = PHYSFS_mount(args, mntpoint, appending);
+
+ else if (mnttype == MNTTYPE_HANDLE)
+ {
+ PHYSFS_File *f = PHYSFS_openRead(args);
+ if (f == NULL)
+ {
+ printf("PHYSFS_openRead('%s') failed. reason: %s.\n", args, PHYSFS_getLastError());
+ return 1;
+ } /* if */
+
+ rc = PHYSFS_mountHandle(f, args, mntpoint, appending);
+ if (!rc)
+ PHYSFS_close(f);
+ } /* else if */
+
+ else if (mnttype == MNTTYPE_MEMORY)
+ {
+ FILE *in = fopen(args, "rb");
+ void *buf = NULL;
+ long len = 0;
+
+ if (in == NULL)
+ {
+ printf("Failed to open %s to read into memory: %s.\n", args, strerror(errno));
+ return 1;
+ } /* if */
+
+ if ( (fseek(in, 0, SEEK_END) != 0) || ((len = ftell(in)) < 0) )
+ {
+ printf("Failed to find size of %s to read into memory: %s.\n", args, strerror(errno));
+ fclose(in);
+ return 1;
+ } /* if */
+
+ buf = malloc(len);
+ if (buf == NULL)
+ {
+ printf("Failed to allocate space to read %s into memory: %s.\n", args, strerror(errno));
+ fclose(in);
+ return 1;
+ } /* if */
+
+ if ((fseek(in, 0, SEEK_SET) != 0) || (fread(buf, len, 1, in) != 1))
+ {
+ printf("Failed to read %s into memory: %s.\n", args, strerror(errno));
+ fclose(in);
+ free(buf);
+ return 1;
+ } /* if */
+
+ fclose(in);
+
+ rc = PHYSFS_mountMemory(buf, len, freeBuf, args, mntpoint, appending);
+ } /* else */
+
+ if (rc)
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_mount_internal */
+
+
+static int cmd_mount(char *args)
+{
+ return cmd_mount_internal(args, MNTTYPE_PATH);
+} /* cmd_mount */
+
+
+static int cmd_mount_mem(char *args)
+{
+ return cmd_mount_internal(args, MNTTYPE_MEMORY);
+} /* cmd_mount_mem */
+
+
+static int cmd_mount_handle(char *args)
+{
+ return cmd_mount_internal(args, MNTTYPE_HANDLE);
+} /* cmd_mount_handle */
+
+static int cmd_getmountpoint(char *args)
+{
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ printf("Dir [%s] is mounted at [%s].\n", args, PHYSFS_getMountPoint(args));
+ return 1;
+} /* cmd_getmountpoint */
+
+
+static int cmd_setroot(char *args)
+{
+ char *archive;
+ char *subdir;
+ char *ptr;
+
+ archive = args;
+ if (*archive == '\"')
+ {
+ archive++;
+ ptr = strchr(archive, '\"');
+ if (ptr == NULL)
+ {
+ printf("missing string terminator in argument.\n");
+ return 1;
+ } /* if */
+ *(ptr) = '\0';
+ } /* if */
+ else
+ {
+ ptr = strchr(archive, ' ');
+ *ptr = '\0';
+ } /* else */
+
+ subdir = ptr + 1;
+ if (*subdir == '\"')
+ {
+ subdir++;
+ ptr = strchr(subdir, '\"');
+ if (ptr == NULL)
+ {
+ printf("missing string terminator in argument.\n");
+ return 1;
+ } /* if */
+ *(ptr) = '\0';
+ } /* if */
+
+ if (PHYSFS_setRoot(archive, subdir))
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_setroot */
+
+
+static int cmd_removearchive(char *args)
+{
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ if (PHYSFS_unmount(args))
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_removearchive */
+
+
+static int cmd_enumerate(char *args)
+{
+ char **rc;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ rc = PHYSFS_enumerateFiles(args);
+
+ if (rc == NULL)
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+ else
+ {
+ int file_count;
+ char **i;
+ for (i = rc, file_count = 0; *i != NULL; i++, file_count++)
+ printf("%s\n", *i);
+
+ printf("\n total (%d) files.\n", file_count);
+ PHYSFS_freeList(rc);
+ } /* else */
+
+ return 1;
+} /* cmd_enumerate */
+
+
+static int cmd_getdirsep(char *args)
+{
+ printf("Directory separator is [%s].\n", PHYSFS_getDirSeparator());
+ return 1;
+} /* cmd_getdirsep */
+
+
+static int cmd_getlasterror(char *args)
+{
+ printf("last error is [%s].\n", PHYSFS_getLastError());
+ return 1;
+} /* cmd_getlasterror */
+
+
+static int cmd_getcdromdirs(char *args)
+{
+ char **rc = PHYSFS_getCdRomDirs();
+
+ if (rc == NULL)
+ printf("Failure. Reason: [%s].\n", PHYSFS_getLastError());
+ else
+ {
+ int dir_count;
+ char **i;
+ for (i = rc, dir_count = 0; *i != NULL; i++, dir_count++)
+ printf("%s\n", *i);
+
+ printf("\n total (%d) drives.\n", dir_count);
+ PHYSFS_freeList(rc);
+ } /* else */
+
+ return 1;
+} /* cmd_getcdromdirs */
+
+
+static int cmd_getsearchpath(char *args)
+{
+ char **rc = PHYSFS_getSearchPath();
+
+ if (rc == NULL)
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+ else
+ {
+ int dir_count;
+ char **i;
+ for (i = rc, dir_count = 0; *i != NULL; i++, dir_count++)
+ printf("%s\n", *i);
+
+ printf("\n total (%d) directories.\n", dir_count);
+ PHYSFS_freeList(rc);
+ } /* else */
+
+ return 1;
+} /* cmd_getcdromdirs */
+
+
+static int cmd_getbasedir(char *args)
+{
+ printf("Base dir is [%s].\n", PHYSFS_getBaseDir());
+ return 1;
+} /* cmd_getbasedir */
+
+
+static int cmd_getuserdir(char *args)
+{
+ printf("User dir is [%s].\n", PHYSFS_getUserDir());
+ return 1;
+} /* cmd_getuserdir */
+
+
+static int cmd_getprefdir(char *args)
+{
+ char *org;
+ char *appName;
+ char *ptr = args;
+
+ org = ptr;
+ ptr = strchr(ptr, ' '); *ptr = '\0'; ptr++; appName = ptr;
+ printf("Pref dir is [%s].\n", PHYSFS_getPrefDir(org, appName));
+ return 1;
+} /* cmd_getprefdir */
+
+
+static int cmd_getwritedir(char *args)
+{
+ printf("Write dir is [%s].\n", PHYSFS_getWriteDir());
+ return 1;
+} /* cmd_getwritedir */
+
+
+static int cmd_setwritedir(char *args)
+{
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ if (PHYSFS_setWriteDir(args))
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_setwritedir */
+
+
+static int cmd_permitsyms(char *args)
+{
+ int num;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ num = atoi(args);
+ PHYSFS_permitSymbolicLinks(num);
+ printf("Symlinks are now %s.\n", num ? "permitted" : "forbidden");
+ return 1;
+} /* cmd_permitsyms */
+
+
+static int cmd_setbuffer(char *args)
+{
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ do_buffer_size = (unsigned int) atoi(args);
+ if (do_buffer_size)
+ {
+ printf("Further tests will set a (%lu) size buffer.\n",
+ (unsigned long) do_buffer_size);
+ } /* if */
+
+ else
+ {
+ printf("Further tests will NOT use a buffer.\n");
+ } /* else */
+
+ return 1;
+} /* cmd_setbuffer */
+
+
+static int cmd_stressbuffer(char *args)
+{
+ int num;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ num = atoi(args);
+ if (num < 0)
+ printf("buffer must be greater than or equal to zero.\n");
+ else
+ {
+ PHYSFS_File *f;
+ int rndnum;
+
+ printf("Stress testing with (%d) byte buffer...\n", num);
+ f = PHYSFS_openWrite("test.txt");
+ if (f == NULL)
+ printf("Couldn't open test.txt for writing: %s.\n", PHYSFS_getLastError());
+ else
+ {
+ int i, j;
+ char buf[37];
+ char buf2[37];
+
+ if (!PHYSFS_setBuffer(f, num))
+ {
+ printf("PHYSFS_setBuffer() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ PHYSFS_delete("test.txt");
+ return 1;
+ } /* if */
+
+ strcpy(buf, "abcdefghijklmnopqrstuvwxyz0123456789");
+ srand((unsigned int) time(NULL));
+
+ for (i = 0; i < 10; i++)
+ {
+ for (j = 0; j < 10000; j++)
+ {
+ PHYSFS_uint32 right = 1 + (PHYSFS_uint32) (35.0 * rand() / (RAND_MAX + 1.0));
+ PHYSFS_uint32 left = 36 - right;
+ if (PHYSFS_writeBytes(f, buf, left) != left)
+ {
+ printf("PHYSFS_writeBytes() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+
+ rndnum = 1 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+ if (rndnum == 42)
+ {
+ if (!PHYSFS_flush(f))
+ {
+ printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* if */
+
+ if (PHYSFS_writeBytes(f, buf + left, right) != right)
+ {
+ printf("PHYSFS_writeBytes() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+
+ rndnum = 1 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+ if (rndnum == 42)
+ {
+ if (!PHYSFS_flush(f))
+ {
+ printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* if */
+ } /* for */
+
+ if (!PHYSFS_flush(f))
+ {
+ printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+
+ } /* for */
+
+ if (!PHYSFS_close(f))
+ {
+ printf("PHYSFS_close() failed: %s.\n", PHYSFS_getLastError());
+ return 1; /* oh well. */
+ } /* if */
+
+ printf(" ... test file written ...\n");
+ f = PHYSFS_openRead("test.txt");
+ if (f == NULL)
+ {
+ printf("Failed to reopen stress file for reading: %s.\n", PHYSFS_getLastError());
+ return 1;
+ } /* if */
+
+ if (!PHYSFS_setBuffer(f, num))
+ {
+ printf("PHYSFS_setBuffer() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+
+ for (i = 0; i < 10; i++)
+ {
+ for (j = 0; j < 10000; j++)
+ {
+ PHYSFS_uint32 right = 1 + (PHYSFS_uint32) (35.0 * rand() / (RAND_MAX + 1.0));
+ PHYSFS_uint32 left = 36 - right;
+ if (PHYSFS_readBytes(f, buf2, left) != left)
+ {
+ printf("PHYSFS_readBytes() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+
+ rndnum = 1 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+ if (rndnum == 42)
+ {
+ if (!PHYSFS_flush(f))
+ {
+ printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* if */
+
+ if (PHYSFS_readBytes(f, buf2 + left, right) != right)
+ {
+ printf("PHYSFS_readBytes() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+
+ rndnum = 1 + (int) (1000.0 * rand() / (RAND_MAX + 1.0));
+ if (rndnum == 42)
+ {
+ if (!PHYSFS_flush(f))
+ {
+ printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* if */
+
+ if (memcmp(buf, buf2, 36) != 0)
+ {
+ printf("readback is mismatched on iterations (%d, %d).\n", i, j);
+ printf("wanted: [");
+ for (i = 0; i < 36; i++)
+ printf("%c", buf[i]);
+ printf("]\n");
+
+ printf(" got: [");
+ for (i = 0; i < 36; i++)
+ printf("%c", buf2[i]);
+ printf("]\n");
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* for */
+
+ if (!PHYSFS_flush(f))
+ {
+ printf("PHYSFS_flush() failed: %s.\n", PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+
+ } /* for */
+
+ printf(" ... test file read ...\n");
+
+ if (!PHYSFS_eof(f))
+ printf("PHYSFS_eof() returned true! That's wrong.\n");
+
+ if (!PHYSFS_close(f))
+ {
+ printf("PHYSFS_close() failed: %s.\n", PHYSFS_getLastError());
+ return 1; /* oh well. */
+ } /* if */
+
+ PHYSFS_delete("test.txt");
+ printf("stress test completed successfully.\n");
+ } /* else */
+ } /* else */
+
+ return 1;
+} /* cmd_stressbuffer */
+
+
+static int cmd_setsaneconfig(char *args)
+{
+ char *org;
+ char *appName;
+ char *arcExt;
+ int inclCD;
+ int arcsFirst;
+ char *ptr = args;
+
+ /* ugly. */
+ org = ptr;
+ ptr = strchr(ptr, ' '); *ptr = '\0'; ptr++; appName = ptr;
+ ptr = strchr(ptr, ' '); *ptr = '\0'; ptr++; arcExt = ptr;
+ ptr = strchr(ptr, ' '); *ptr = '\0'; ptr++; inclCD = atoi(arcExt);
+ arcsFirst = atoi(ptr);
+
+ if (strcmp(arcExt, "!") == 0)
+ arcExt = NULL;
+
+ if (PHYSFS_setSaneConfig(org, appName, arcExt, inclCD, arcsFirst))
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_setsaneconfig */
+
+
+static int cmd_mkdir(char *args)
+{
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ if (PHYSFS_mkdir(args))
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_mkdir */
+
+
+static int cmd_delete(char *args)
+{
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ if (PHYSFS_delete(args))
+ printf("Successful.\n");
+ else
+ printf("Failure. reason: %s.\n", PHYSFS_getLastError());
+
+ return 1;
+} /* cmd_delete */
+
+
+static int cmd_getrealdir(char *args)
+{
+ const char *rc;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ rc = PHYSFS_getRealDir(args);
+ if (rc)
+ printf("Found at [%s].\n", rc);
+ else
+ printf("Not found.\n");
+
+ return 1;
+} /* cmd_getrealdir */
+
+
+static int cmd_exists(char *args)
+{
+ int rc;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ rc = PHYSFS_exists(args);
+ printf("File %sexists.\n", rc ? "" : "does not ");
+ return 1;
+} /* cmd_exists */
+
+
+static int cmd_isdir(char *args)
+{
+ PHYSFS_Stat statbuf;
+ int rc;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ rc = PHYSFS_stat(args, &statbuf);
+ if (rc)
+ rc = (statbuf.filetype == PHYSFS_FILETYPE_DIRECTORY);
+ printf("File %s a directory.\n", rc ? "is" : "is NOT");
+ return 1;
+} /* cmd_isdir */
+
+
+static int cmd_issymlink(char *args)
+{
+ PHYSFS_Stat statbuf;
+ int rc;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ rc = PHYSFS_stat(args, &statbuf);
+ if (rc)
+ rc = (statbuf.filetype == PHYSFS_FILETYPE_SYMLINK);
+ printf("File %s a symlink.\n", rc ? "is" : "is NOT");
+ return 1;
+} /* cmd_issymlink */
+
+
+static int cmd_cat(char *args)
+{
+ PHYSFS_File *f;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ f = PHYSFS_openRead(args);
+ if (f == NULL)
+ printf("failed to open. Reason: [%s].\n", PHYSFS_getLastError());
+ else
+ {
+ if (do_buffer_size)
+ {
+ if (!PHYSFS_setBuffer(f, do_buffer_size))
+ {
+ printf("failed to set file buffer. Reason: [%s].\n",
+ PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* if */
+
+ while (1)
+ {
+ char buffer[128];
+ PHYSFS_sint64 rc;
+ PHYSFS_sint64 i;
+ rc = PHYSFS_readBytes(f, buffer, sizeof (buffer));
+
+ for (i = 0; i < rc; i++)
+ fputc((int) buffer[i], stdout);
+
+ if (rc < sizeof (buffer))
+ {
+ printf("\n\n");
+ if (!PHYSFS_eof(f))
+ {
+ printf("\n (Error condition in reading. Reason: [%s])\n\n",
+ PHYSFS_getLastError());
+ } /* if */
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* while */
+ } /* else */
+
+ return 1;
+} /* cmd_cat */
+
+static int cmd_cat2(char *args)
+{
+ PHYSFS_File *f1 = NULL;
+ PHYSFS_File *f2 = NULL;
+ char *fname1;
+ char *fname2;
+ char *ptr;
+
+ fname1 = args;
+ if (*fname1 == '\"')
+ {
+ fname1++;
+ ptr = strchr(fname1, '\"');
+ if (ptr == NULL)
+ {
+ printf("missing string terminator in argument.\n");
+ return 1;
+ } /* if */
+ *(ptr) = '\0';
+ } /* if */
+ else
+ {
+ ptr = strchr(fname1, ' ');
+ *ptr = '\0';
+ } /* else */
+
+ fname2 = ptr + 1;
+ if (*fname2 == '\"')
+ {
+ fname2++;
+ ptr = strchr(fname2, '\"');
+ if (ptr == NULL)
+ {
+ printf("missing string terminator in argument.\n");
+ return 1;
+ } /* if */
+ *(ptr) = '\0';
+ } /* if */
+
+ if ((f1 = PHYSFS_openRead(fname1)) == NULL)
+ printf("failed to open '%s'. Reason: [%s].\n", fname1, PHYSFS_getLastError());
+ else if ((f2 = PHYSFS_openRead(fname2)) == NULL)
+ printf("failed to open '%s'. Reason: [%s].\n", fname2, PHYSFS_getLastError());
+ else
+ {
+ char *buffer1 = NULL;
+ size_t buffer1len = 0;
+ char *buffer2 = NULL;
+ size_t buffer2len = 0;
+ char *ptr = NULL;
+ size_t i;
+
+ if (do_buffer_size)
+ {
+ if (!PHYSFS_setBuffer(f1, do_buffer_size))
+ {
+ printf("failed to set file buffer for '%s'. Reason: [%s].\n",
+ fname1, PHYSFS_getLastError());
+ PHYSFS_close(f1);
+ PHYSFS_close(f2);
+ return 1;
+ } /* if */
+ else if (!PHYSFS_setBuffer(f2, do_buffer_size))
+ {
+ printf("failed to set file buffer for '%s'. Reason: [%s].\n",
+ fname2, PHYSFS_getLastError());
+ PHYSFS_close(f1);
+ PHYSFS_close(f2);
+ return 1;
+ } /* if */
+ } /* if */
+
+
+ do
+ {
+ int readlen = 128;
+ PHYSFS_sint64 rc;
+
+ ptr = realloc(buffer1, buffer1len + readlen);
+ if (!ptr)
+ {
+ printf("(Out of memory.)\n\n");
+ free(buffer1);
+ free(buffer2);
+ PHYSFS_close(f1);
+ PHYSFS_close(f2);
+ return 1;
+ } /* if */
+
+ buffer1 = ptr;
+ rc = PHYSFS_readBytes(f1, buffer1 + buffer1len, readlen);
+ if (rc < 0)
+ {
+ printf("(Error condition in reading '%s'. Reason: [%s])\n\n",
+ fname1, PHYSFS_getLastError());
+ free(buffer1);
+ free(buffer2);
+ PHYSFS_close(f1);
+ PHYSFS_close(f2);
+ return 1;
+ } /* if */
+ buffer1len += (size_t) rc;
+
+ ptr = realloc(buffer2, buffer2len + readlen);
+ if (!ptr)
+ {
+ printf("(Out of memory.)\n\n");
+ free(buffer1);
+ free(buffer2);
+ PHYSFS_close(f1);
+ PHYSFS_close(f2);
+ return 1;
+ } /* if */
+
+ buffer2 = ptr;
+ rc = PHYSFS_readBytes(f2, buffer2 + buffer2len, readlen);
+ if (rc < 0)
+ {
+ printf("(Error condition in reading '%s'. Reason: [%s])\n\n",
+ fname2, PHYSFS_getLastError());
+ free(buffer1);
+ free(buffer2);
+ PHYSFS_close(f1);
+ PHYSFS_close(f2);
+ return 1;
+ } /* if */
+ buffer2len += (size_t) rc;
+ } while (!PHYSFS_eof(f1) || !PHYSFS_eof(f2));
+
+ printf("file '%s' ...\n\n", fname1);
+ for (i = 0; i < buffer1len; i++)
+ fputc((int) buffer1[i], stdout);
+ free(buffer1);
+
+ printf("\n\nfile '%s' ...\n\n", fname2);
+ for (i = 0; i < buffer2len; i++)
+ fputc((int) buffer2[i], stdout);
+ free(buffer2);
+
+ printf("\n\n");
+ } /* else */
+
+ if (f1)
+ PHYSFS_close(f1);
+
+ if (f2)
+ PHYSFS_close(f2);
+
+ return 1;
+} /* cmd_cat2 */
+
+
+#define CRC32_BUFFERSIZE 512
+static int cmd_crc32(char *args)
+{
+ PHYSFS_File *f;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ f = PHYSFS_openRead(args);
+ if (f == NULL)
+ printf("failed to open. Reason: [%s].\n", PHYSFS_getLastError());
+ else
+ {
+ PHYSFS_uint8 buffer[CRC32_BUFFERSIZE];
+ PHYSFS_uint32 crc = -1;
+ PHYSFS_sint64 bytesread;
+
+ while ((bytesread = PHYSFS_readBytes(f, buffer, CRC32_BUFFERSIZE)) > 0)
+ {
+ PHYSFS_uint32 i, bit;
+ for (i = 0; i < bytesread; i++)
+ {
+ for (bit = 0; bit < 8; bit++, buffer[i] >>= 1)
+ crc = (crc >> 1) ^ (((crc ^ buffer[i]) & 1) ? 0xEDB88320 : 0);
+ } /* for */
+ } /* while */
+
+ if (bytesread < 0)
+ {
+ printf("error while reading. Reason: [%s].\n",
+ PHYSFS_getLastError());
+ return 1;
+ } /* if */
+
+ PHYSFS_close(f);
+
+ crc ^= -1;
+ printf("CRC32 for %s: 0x%08X\n", args, crc);
+ } /* else */
+
+ return 1;
+} /* cmd_crc32 */
+
+
+static int cmd_filelength(char *args)
+{
+ PHYSFS_File *f;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ f = PHYSFS_openRead(args);
+ if (f == NULL)
+ printf("failed to open. Reason: [%s].\n", PHYSFS_getLastError());
+ else
+ {
+ PHYSFS_sint64 len = PHYSFS_fileLength(f);
+ if (len == -1)
+ printf("failed to determine length. Reason: [%s].\n", PHYSFS_getLastError());
+ else
+ printf(" (cast to int) %d bytes.\n", (int) len);
+
+ PHYSFS_close(f);
+ } /* else */
+
+ return 1;
+} /* cmd_filelength */
+
+#define WRITESTR "The cat sat on the mat.\n\n"
+
+static int cmd_append(char *args)
+{
+ PHYSFS_File *f;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ f = PHYSFS_openAppend(args);
+ if (f == NULL)
+ printf("failed to open. Reason: [%s].\n", PHYSFS_getLastError());
+ else
+ {
+ size_t bw;
+ PHYSFS_sint64 rc;
+
+ if (do_buffer_size)
+ {
+ if (!PHYSFS_setBuffer(f, do_buffer_size))
+ {
+ printf("failed to set file buffer. Reason: [%s].\n",
+ PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* if */
+
+ bw = strlen(WRITESTR);
+ rc = PHYSFS_writeBytes(f, WRITESTR, bw);
+ if (rc != bw)
+ {
+ printf("Wrote (%d) of (%d) bytes. Reason: [%s].\n",
+ (int) rc, (int) bw, PHYSFS_getLastError());
+ } /* if */
+ else
+ {
+ printf("Successful.\n");
+ } /* else */
+
+ PHYSFS_close(f);
+ } /* else */
+
+ return 1;
+} /* cmd_append */
+
+
+static int cmd_write(char *args)
+{
+ PHYSFS_File *f;
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ f = PHYSFS_openWrite(args);
+ if (f == NULL)
+ printf("failed to open. Reason: [%s].\n", PHYSFS_getLastError());
+ else
+ {
+ size_t bw;
+ PHYSFS_sint64 rc;
+
+ if (do_buffer_size)
+ {
+ if (!PHYSFS_setBuffer(f, do_buffer_size))
+ {
+ printf("failed to set file buffer. Reason: [%s].\n",
+ PHYSFS_getLastError());
+ PHYSFS_close(f);
+ return 1;
+ } /* if */
+ } /* if */
+
+ bw = strlen(WRITESTR);
+ rc = PHYSFS_writeBytes(f, WRITESTR, bw);
+ if (rc != bw)
+ {
+ printf("Wrote (%d) of (%d) bytes. Reason: [%s].\n",
+ (int) rc, (int) bw, PHYSFS_getLastError());
+ } /* if */
+ else
+ {
+ printf("Successful.\n");
+ } /* else */
+
+ PHYSFS_close(f);
+ } /* else */
+
+ return 1;
+} /* cmd_write */
+
+
+static char* modTimeToStr(PHYSFS_sint64 modtime, char *modstr, size_t strsize)
+{
+ if (modtime < 0)
+ strncpy(modstr, "Unknown\n", strsize);
+ else
+ {
+ time_t t = (time_t) modtime;
+ char *str = ctime(&t);
+ strncpy(modstr, str, strsize);
+ } /* else */
+
+ modstr[strsize-1] = '\0';
+ return modstr;
+} /* modTimeToStr */
+
+
+static int cmd_getlastmodtime(char *args)
+{
+ PHYSFS_Stat statbuf;
+ if (!PHYSFS_stat(args, &statbuf))
+ printf("Failed to determine. Reason: [%s].\n", PHYSFS_getLastError());
+ else
+ {
+ char modstr[64];
+ modTimeToStr(statbuf.modtime, modstr, sizeof (modstr));
+ printf("Last modified: %s (%ld).\n", modstr, (long) statbuf.modtime);
+ } /* else */
+
+ return 1;
+} /* cmd_getLastModTime */
+
+static int cmd_stat(char *args)
+{
+ PHYSFS_Stat stat;
+ char timestring[65];
+
+ if (*args == '\"')
+ {
+ args++;
+ args[strlen(args) - 1] = '\0';
+ } /* if */
+
+ if(!PHYSFS_stat(args, &stat))
+ {
+ printf("failed to stat. Reason [%s].\n", PHYSFS_getLastError());
+ return 1;
+ } /* if */
+
+ printf("Filename: %s\n", args);
+ printf("Size %d\n",(int) stat.filesize);
+
+ if(stat.filetype == PHYSFS_FILETYPE_REGULAR)
+ printf("Type: File\n");
+ else if(stat.filetype == PHYSFS_FILETYPE_DIRECTORY)
+ printf("Type: Directory\n");
+ else if(stat.filetype == PHYSFS_FILETYPE_SYMLINK)
+ printf("Type: Symlink\n");
+ else
+ printf("Type: Unknown\n");
+
+ printf("Created at: %s", modTimeToStr(stat.createtime, timestring, 64));
+ printf("Last modified at: %s", modTimeToStr(stat.modtime, timestring, 64));
+ printf("Last accessed at: %s", modTimeToStr(stat.accesstime, timestring, 64));
+ printf("Readonly: %s\n", stat.readonly ? "true" : "false");
+
+ return 1;
+} /* cmd_filelength */
+
+
+
+/* must have spaces trimmed prior to this call. */
+static int count_args(const char *str)
+{
+ int retval = 0;
+ int in_quotes = 0;
+
+ if (str != NULL)
+ {
+ for (; *str != '\0'; str++)
+ {
+ if (*str == '\"')
+ in_quotes = !in_quotes;
+ else if ((*str == ' ') && (!in_quotes))
+ retval++;
+ } /* for */
+ retval++;
+ } /* if */
+
+ return retval;
+} /* count_args */
+
+
+static int cmd_help(char *args);
+
+typedef struct
+{
+ const char *cmd;
+ int (*func)(char *args);
+ int argcount;
+ const char *usage;
+} command_info;
+
+static const command_info commands[] =
+{
+ { "quit", cmd_quit, 0, NULL },
+ { "q", cmd_quit, 0, NULL },
+ { "help", cmd_help, 0, NULL },
+ { "init", cmd_init, 1, "" },
+ { "deinit", cmd_deinit, 0, NULL },
+ { "addarchive", cmd_addarchive, 2, " " },
+ { "mount", cmd_mount, 3, " " },
+ { "mountmem", cmd_mount_mem, 3, " " },
+ { "mounthandle", cmd_mount_handle, 3, " " },
+ { "removearchive", cmd_removearchive, 1, "" },
+ { "unmount", cmd_removearchive, 1, "" },
+ { "enumerate", cmd_enumerate, 1, "" },
+ { "ls", cmd_enumerate, 1, "" },
+ { "getlasterror", cmd_getlasterror, 0, NULL },
+ { "getdirsep", cmd_getdirsep, 0, NULL },
+ { "getcdromdirs", cmd_getcdromdirs, 0, NULL },
+ { "getsearchpath", cmd_getsearchpath, 0, NULL },
+ { "getbasedir", cmd_getbasedir, 0, NULL },
+ { "getuserdir", cmd_getuserdir, 0, NULL },
+ { "getprefdir", cmd_getprefdir, 2, " " },
+ { "getwritedir", cmd_getwritedir, 0, NULL },
+ { "setwritedir", cmd_setwritedir, 1, "" },
+ { "permitsymlinks", cmd_permitsyms, 1, "<1or0>" },
+ { "setsaneconfig", cmd_setsaneconfig, 5, " " },
+ { "mkdir", cmd_mkdir, 1, "" },
+ { "delete", cmd_delete, 1, "" },
+ { "getrealdir", cmd_getrealdir, 1, "" },
+ { "exists", cmd_exists, 1, "" },
+ { "isdir", cmd_isdir, 1, "" },
+ { "issymlink", cmd_issymlink, 1, "" },
+ { "cat", cmd_cat, 1, "" },
+ { "cat2", cmd_cat2, 2, " " },
+ { "filelength", cmd_filelength, 1, "" },
+ { "stat", cmd_stat, 1, "" },
+ { "append", cmd_append, 1, "" },
+ { "write", cmd_write, 1, "" },
+ { "getlastmodtime", cmd_getlastmodtime, 1, "" },
+ { "setbuffer", cmd_setbuffer, 1, "" },
+ { "stressbuffer", cmd_stressbuffer, 1, "" },
+ { "crc32", cmd_crc32, 1, "" },
+ { "getmountpoint", cmd_getmountpoint, 1, "" },
+ { "setroot", cmd_setroot, 2, " " },
+ { NULL, NULL, -1, NULL }
+};
+
+
+static void output_usage(const char *intro, const command_info *cmdinfo)
+{
+ if (cmdinfo->argcount == 0)
+ printf("%s \"%s\" (no arguments)\n", intro, cmdinfo->cmd);
+ else
+ printf("%s \"%s %s\"\n", intro, cmdinfo->cmd, cmdinfo->usage);
+} /* output_usage */
+
+
+static int cmd_help(char *args)
+{
+ const command_info *i;
+
+ printf("Commands:\n");
+ for (i = commands; i->cmd != NULL; i++)
+ output_usage(" -", i);
+
+ return 1;
+} /* output_cmd_help */
+
+
+static void trim_command(const char *orig, char *copy)
+{
+ const char *i;
+ char *writeptr = copy;
+ int spacecount = 0;
+ int have_first = 0;
+
+ for (i = orig; *i != '\0'; i++)
+ {
+ if (*i == ' ')
+ {
+ if ((*(i + 1) != ' ') && (*(i + 1) != '\0'))
+ {
+ if ((have_first) && (!spacecount))
+ {
+ spacecount++;
+ *writeptr = ' ';
+ writeptr++;
+ } /* if */
+ } /* if */
+ } /* if */
+ else
+ {
+ have_first = 1;
+ spacecount = 0;
+ *writeptr = *i;
+ writeptr++;
+ } /* else */
+ } /* for */
+
+ *writeptr = '\0';
+
+ /*
+ printf("\n command is [%s].\n", copy);
+ */
+} /* trim_command */
+
+
+static int process_command(char *complete_cmd)
+{
+ const command_info *i;
+ char *cmd_copy;
+ char *args;
+ int rc = 1;
+
+ if (complete_cmd == NULL) /* can happen if user hits CTRL-D, etc. */
+ {
+ printf("\n");
+ return 0;
+ } /* if */
+
+ cmd_copy = (char *) malloc(strlen(complete_cmd) + 1);
+ if (cmd_copy == NULL)
+ {
+ printf("\n\n\nOUT OF MEMORY!\n\n\n");
+ return 0;
+ } /* if */
+
+ trim_command(complete_cmd, cmd_copy);
+ args = strchr(cmd_copy, ' ');
+ if (args != NULL)
+ {
+ *args = '\0';
+ args++;
+ } /* else */
+
+ if (cmd_copy[0] != '\0')
+ {
+ for (i = commands; i->cmd != NULL; i++)
+ {
+ if (strcmp(i->cmd, cmd_copy) == 0)
+ {
+ if ((i->argcount >= 0) && (count_args(args) != i->argcount))
+ output_usage("usage:", i);
+ else
+ rc = i->func(args);
+ break;
+ } /* if */
+ } /* for */
+
+ if (i->cmd == NULL)
+ printf("Unknown command. Enter \"help\" for instructions.\n");
+
+#if (defined PHYSFS_HAVE_READLINE)
+ add_history(complete_cmd);
+ if (history_file)
+ {
+ fprintf(history_file, "%s\n", complete_cmd);
+ fflush(history_file);
+ } /* if */
+#endif
+
+ } /* if */
+
+ free(cmd_copy);
+ return rc;
+} /* process_command */
+
+
+static void open_history_file(void)
+{
+#if (defined PHYSFS_HAVE_READLINE)
+#if 0
+ const char *envr = getenv("TESTPHYSFS_HISTORY");
+ if (!envr)
+ return;
+#else
+ char envr[256];
+ strcpy(envr, PHYSFS_getUserDir());
+ strcat(envr, ".testphys_history");
+#endif
+
+ if (access(envr, F_OK) == 0)
+ {
+ char buf[512];
+ FILE *f = fopen(envr, "r");
+ if (!f)
+ {
+ printf("\n\n"
+ "Could not open history file [%s] for reading!\n"
+ " Will not have past history available.\n\n",
+ envr);
+ return;
+ } /* if */
+
+ do
+ {
+ if (fgets(buf, sizeof (buf), f) == NULL)
+ break;
+
+ if (buf[strlen(buf) - 1] == '\n')
+ buf[strlen(buf) - 1] = '\0';
+ add_history(buf);
+ } while (!feof(f));
+
+ fclose(f);
+ } /* if */
+
+ history_file = fopen(envr, "ab");
+ if (!history_file)
+ {
+ printf("\n\n"
+ "Could not open history file [%s] for appending!\n"
+ " Will not be able to record this session's history.\n\n",
+ envr);
+ } /* if */
+#endif
+} /* open_history_file */
+
+
+int main(int argc, char **argv)
+{
+ char *buf = NULL;
+ int rc = 0;
+
+#if (defined __MWERKS__)
+ extern tSIOUXSettings SIOUXSettings;
+ SIOUXSettings.asktosaveonclose = 0;
+ SIOUXSettings.autocloseonquit = 1;
+ SIOUXSettings.rows = 40;
+ SIOUXSettings.columns = 120;
+#endif
+
+ printf("\n");
+
+ if (!PHYSFS_init(argv[0]))
+ {
+ printf("PHYSFS_init() failed!\n reason: %s.\n", PHYSFS_getLastError());
+ return 1;
+ } /* if */
+
+ output_versions();
+ output_archivers();
+
+ open_history_file();
+
+ printf("Enter commands. Enter \"help\" for instructions.\n");
+ fflush(stdout);
+
+ do
+ {
+#if (defined PHYSFS_HAVE_READLINE)
+ buf = readline("> ");
+#else
+ int i;
+ buf = (char *) malloc(512);
+ memset(buf, '\0', 512);
+ printf("> ");
+ fflush(stdout);
+ for (i = 0; i < 511; i++)
+ {
+ int ch = fgetc(stdin);
+ if (ch == EOF)
+ {
+ strcpy(buf, "quit");
+ break;
+ } /* if */
+ else if ((ch == '\n') || (ch == '\r'))
+ {
+ buf[i] = '\0';
+ break;
+ } /* else if */
+ else if (ch == '\b')
+ {
+ if (i > 0)
+ i--;
+ } /* else if */
+ else
+ {
+ buf[i] = (char) ch;
+ } /* else */
+ } /* for */
+#endif
+
+ rc = process_command(buf);
+ fflush(stdout);
+ if (buf != NULL)
+ free(buf);
+ } while (rc);
+
+ if (!PHYSFS_deinit())
+ printf("PHYSFS_deinit() failed!\n reason: %s.\n", PHYSFS_getLastError());
+
+ if (history_file)
+ fclose(history_file);
+
+/*
+ printf("\n\ntest_physfs written by ryan c. gordon.\n");
+ printf(" it makes you shoot teh railgun bettar.\n");
+*/
+
+ return 0;
+} /* main */
+
+/* end of test_physfs.c ... */
+
diff --git a/third-party/stb/.github/CONTRIBUTING.md b/third-party/stb/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..54e3543
--- /dev/null
+++ b/third-party/stb/.github/CONTRIBUTING.md
@@ -0,0 +1,32 @@
+Pull Requests and Issues are both welcome.
+
+# Responsiveness
+
+General priority order is:
+
+* Crashes
+* Security issues in stb_image
+* Bugs
+* Security concerns in other libs
+* Warnings
+* Enhancements (new features, performance improvement, etc)
+
+Pull requests get priority over Issues. Some pull requests I take
+as written; some I modify myself; some I will request changes before
+accepting them. Because I've ended up supporting a lot of libraries
+(20 as I write this, with more on the way), I am somewhat slow to
+address things. Many issues have been around for a long time.
+
+# Pull requests
+
+* Make sure you're using a special branch just for this pull request. (Sometimes people unknowingly use a default branch, then later update that branch, which updates the pull request with the other changes if it hasn't been merged yet.)
+* Do NOT update the version number in the file. (This just causes conflicts.)
+* Do add your name to the list of contributors. (Don't worry about the formatting.) I'll try to remember to add it if you don't, but I sometimes forget as it's an extra step.
+* Your change needs to compile as both C and C++. Pre-C99 compilers should be supported (e.g. declare at start of block)
+
+# Specific libraries
+
+I generally do not want new file formats for stb_image because
+we are trying to improve its security, so increasing its attack
+surface is counter-productive.
+
diff --git a/third-party/stb/.github/ISSUE_TEMPLATE/bug_report.md b/third-party/stb/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..a5cb26f
--- /dev/null
+++ b/third-party/stb/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,24 @@
+---
+name: Bug report
+about: if you're having trouble using a library, try the support forum instead
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
diff --git a/third-party/stb/.github/ISSUE_TEMPLATE/config.yml b/third-party/stb/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..85cbf6e
--- /dev/null
+++ b/third-party/stb/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,4 @@
+contact_links:
+ - name: support forum
+ url: https://github.com/nothings/stb/discussions/categories/q-a
+ about: having trouble using an stb library? don't create an issue, post in the forum
diff --git a/third-party/stb/.github/ISSUE_TEMPLATE/feature_request.md b/third-party/stb/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..71c8763
--- /dev/null
+++ b/third-party/stb/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: 4 enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/third-party/stb/.github/PULL_REQUEST_TEMPLATE.md b/third-party/stb/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..2b10daa
--- /dev/null
+++ b/third-party/stb/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,6 @@
+* Delete this list before clicking CREATE PULL REQUEST
+* Make sure you're using a special branch just for this pull request. (Sometimes people unknowingly use a default branch, then later update that branch, which updates the pull request with the other changes if it hasn't been merged yet.)
+* Do NOT update the version number in the file. (This just causes conflicts.)
+* Do add your name to the list of contributors. (Don't worry about the formatting.) I'll try to remember to add it if you don't, but I sometimes forget as it's an extra step.
+
+If you get something above wrong, don't fret it, it's not the end of the world.
diff --git a/third-party/stb/.github/workflows/ci-fuzz.yml b/third-party/stb/.github/workflows/ci-fuzz.yml
new file mode 100644
index 0000000..332fca9
--- /dev/null
+++ b/third-party/stb/.github/workflows/ci-fuzz.yml
@@ -0,0 +1,23 @@
+name: CIFuzz
+on: [pull_request]
+jobs:
+ Fuzzing:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Build Fuzzers
+ uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'stb'
+ dry-run: false
+ - name: Run Fuzzers
+ uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'stb'
+ fuzz-seconds: 900
+ dry-run: false
+ - name: Upload Crash
+ uses: actions/upload-artifact@v1
+ if: failure()
+ with:
+ name: artifacts
+ path: ./out/artifacts
diff --git a/third-party/stb/.travis.yml b/third-party/stb/.travis.yml
new file mode 100644
index 0000000..c2ad947
--- /dev/null
+++ b/third-party/stb/.travis.yml
@@ -0,0 +1,8 @@
+language: C
+arch:
+ - AMD64
+ - ppc64le
+install: true
+script:
+ - cd tests
+ - make all
diff --git a/third-party/stb/LICENSE b/third-party/stb/LICENSE
new file mode 100644
index 0000000..a77ae91
--- /dev/null
+++ b/third-party/stb/LICENSE
@@ -0,0 +1,37 @@
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+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 OR COPYRIGHT HOLDERS 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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+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.
diff --git a/third-party/stb/README.md b/third-party/stb/README.md
new file mode 100644
index 0000000..48fb917
--- /dev/null
+++ b/third-party/stb/README.md
@@ -0,0 +1,184 @@
+
+
+stb
+===
+
+single-file public domain (or MIT licensed) libraries for C/C++
+
+# This project discusses security-relevant bugs in public in Github Issues and Pull Requests, and it may take significant time for security fixes to be implemented or merged. If this poses an unreasonable risk to your project, do not use stb libraries.
+
+Noteworthy:
+
+* image loader: [stb_image.h](stb_image.h)
+* image writer: [stb_image_write.h](stb_image_write.h)
+* image resizer: [stb_image_resize2.h](stb_image_resize2.h)
+* font text rasterizer: [stb_truetype.h](stb_truetype.h)
+* typesafe containers: [stb_ds.h](stb_ds.h)
+
+Most libraries by stb, except: stb_dxt by Fabian "ryg" Giesen, stb_image_resize
+by Jorge L. "VinoBS" Rodriguez, and stb_sprintf by Jeff Roberts.
+
+
+
+library | lastest version | category | LoC | description
+--------------------- | ---- | -------- | --- | --------------------------------
+**[stb_vorbis.c](stb_vorbis.c)** | 1.22 | audio | 5584 | decode ogg vorbis files from file/memory to float/16-bit signed output
+**[stb_hexwave.h](stb_hexwave.h)** | 0.5 | audio | 680 | audio waveform synthesizer
+**[stb_image.h](stb_image.h)** | 2.28 | graphics | 7987 | image loading/decoding from file/memory: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC
+**[stb_truetype.h](stb_truetype.h)** | 1.26 | graphics | 5077 | parse, decode, and rasterize characters from truetype fonts
+**[stb_image_write.h](stb_image_write.h)** | 1.16 | graphics | 1724 | image writing to disk: PNG, TGA, BMP
+**[stb_image_resize2.h](stb_image_resize2.h)** | 2.01 | graphics | 10303 | resize images larger/smaller with good quality
+**[stb_rect_pack.h](stb_rect_pack.h)** | 1.01 | graphics | 623 | simple 2D rectangle packer with decent quality
+**[stb_perlin.h](stb_perlin.h)** | 0.5 | graphics | 428 | perlin's revised simplex noise w/ different seeds
+**[stb_ds.h](stb_ds.h)** | 0.67 | utility | 1895 | typesafe dynamic array and hash tables for C, will compile in C++
+**[stb_sprintf.h](stb_sprintf.h)** | 1.10 | utility | 1906 | fast sprintf, snprintf for C/C++
+**[stb_textedit.h](stb_textedit.h)** | 1.14 | user interface | 1429 | guts of a text editor for games etc implementing them from scratch
+**[stb_voxel_render.h](stb_voxel_render.h)** | 0.89 | 3D graphics | 3807 | Minecraft-esque voxel rendering "engine" with many more features
+**[stb_dxt.h](stb_dxt.h)** | 1.12 | 3D graphics | 719 | Fabian "ryg" Giesen's real-time DXT compressor
+**[stb_easy_font.h](stb_easy_font.h)** | 1.1 | 3D graphics | 305 | quick-and-dirty easy-to-deploy bitmap font for printing frame rate, etc
+**[stb_tilemap_editor.h](stb_tilemap_editor.h)** | 0.42 | game dev | 4187 | embeddable tilemap editor
+**[stb_herringbone_wa...](stb_herringbone_wang_tile.h)** | 0.7 | game dev | 1221 | herringbone Wang tile map generator
+**[stb_c_lexer.h](stb_c_lexer.h)** | 0.12 | parsing | 940 | simplify writing parsers for C-like languages
+**[stb_divide.h](stb_divide.h)** | 0.94 | math | 433 | more useful 32-bit modulus e.g. "euclidean divide"
+**[stb_connected_comp...](stb_connected_components.h)** | 0.96 | misc | 1049 | incrementally compute reachability on grids
+**[stb_leakcheck.h](stb_leakcheck.h)** | 0.6 | misc | 194 | quick-and-dirty malloc/free leak-checking
+**[stb_include.h](stb_include.h)** | 0.02 | misc | 295 | implement recursive #include support, particularly for GLSL
+
+Total libraries: 21
+Total lines of C code: 50786
+
+
+FAQ
+---
+
+#### What's the license?
+
+These libraries are in the public domain. You can do anything you
+want with them. You have no legal obligation
+to do anything else, although I appreciate attribution.
+
+They are also licensed under the MIT open source license, if you have lawyers
+who are unhappy with public domain. Every source file includes an explicit
+dual-license for you to choose from.
+
+#### How do I use these libraries?
+
+The idea behind single-header file libraries is that they're easy to distribute and deploy
+because all the code is contained in a single file. By default, the .h files in here act as
+their own header files, i.e. they declare the functions contained in the file but don't
+actually result in any code getting compiled.
+
+So in addition, you should select _exactly one_ C/C++ source file that actually instantiates
+the code, preferably a file you're not editing frequently. This file should define a
+specific macro (this is documented per-library) to actually enable the function definitions.
+For example, to use stb_image, you should have exactly one C/C++ file that doesn't
+include stb_image.h regularly, but instead does
+
+ #define STB_IMAGE_IMPLEMENTATION
+ #include "stb_image.h"
+
+The right macro to define is pointed out right at the top of each of these libraries.
+
+#### Are there other single-file public-domain/open source libraries with minimal dependencies out there?
+
+[Yes.](https://github.com/nothings/single_file_libs)
+
+#### If I wrap an stb library in a new library, does the new library have to be public domain/MIT?
+
+No, because it's public domain you can freely relicense it to whatever license your new
+library wants to be.
+
+#### What's the deal with SSE support in GCC-based compilers?
+
+stb_image will either use SSE2 (if you compile with -msse2) or
+will not use any SIMD at all, rather than trying to detect the
+processor at runtime and handle it correctly. As I understand it,
+the approved path in GCC for runtime-detection require
+you to use multiple source files, one for each CPU configuration.
+Because stb_image is a header-file library that compiles in only
+one source file, there's no approved way to build both an
+SSE-enabled and a non-SSE-enabled variation.
+
+While we've tried to work around it, we've had multiple issues over
+the years due to specific versions of gcc breaking what we're doing,
+so we've given up on it. See https://github.com/nothings/stb/issues/280
+and https://github.com/nothings/stb/issues/410 for examples.
+
+#### Some of these libraries seem redundant to existing open source libraries. Are they better somehow?
+
+Generally they're only better in that they're easier to integrate,
+easier to use, and easier to release (single file; good API; no
+attribution requirement). They may be less featureful, slower,
+and/or use more memory. If you're already using an equivalent
+library, there's probably no good reason to switch.
+
+#### Can I link directly to the table of stb libraries?
+
+You can use [this URL](https://github.com/nothings/stb#stb_libs) to link directly to that list.
+
+#### Why do you list "lines of code"? It's a terrible metric.
+
+Just to give you some idea of the internal complexity of the library,
+to help you manage your expectations, or to let you know what you're
+getting into. While not all the libraries are written in the same
+style, they're certainly similar styles, and so comparisons between
+the libraries are probably still meaningful.
+
+Note though that the lines do include both the implementation, the
+part that corresponds to a header file, and the documentation.
+
+#### Why single-file headers?
+
+Windows doesn't have standard directories where libraries
+live. That makes deploying libraries in Windows a lot more
+painful than open source developers on Unix-derivates generally
+realize. (It also makes library dependencies a lot worse in Windows.)
+
+There's also a common problem in Windows where a library was built
+against a different version of the runtime library, which causes
+link conflicts and confusion. Shipping the libs as headers means
+you normally just compile them straight into your project without
+making libraries, thus sidestepping that problem.
+
+Making them a single file makes it very easy to just
+drop them into a project that needs them. (Of course you can
+still put them in a proper shared library tree if you want.)
+
+Why not two files, one a header and one an implementation?
+The difference between 10 files and 9 files is not a big deal,
+but the difference between 2 files and 1 file is a big deal.
+You don't need to zip or tar the files up, you don't have to
+remember to attach *two* files, etc.
+
+#### Why "stb"? Is this something to do with Set-Top Boxes?
+
+No, they are just the initials for my name, Sean T. Barrett.
+This was not chosen out of egomania, but as a moderately sane
+way of namespacing the filenames and source function names.
+
+#### Will you add more image types to stb_image.h?
+
+No. As stb_image use has grown, it has become more important
+for us to focus on security of the codebase. Adding new image
+formats increases the amount of code we need to secure, so it
+is no longer worth adding new formats.
+
+#### Do you have any advice on how to create my own single-file library?
+
+Yes. https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
+
+#### Why public domain?
+
+I prefer it over GPL, LGPL, BSD, zlib, etc. for many reasons.
+Some of them are listed here:
+https://github.com/nothings/stb/blob/master/docs/why_public_domain.md
+
+#### Why C?
+
+Primarily, because I use C, not C++. But it does also make it easier
+for other people to use them from other languages.
+
+#### Why not C99? stdint.h, declare-anywhere, etc.
+
+I still use MSVC 6 (1998) as my IDE because it has better human factors
+for me than later versions of MSVC.
diff --git a/third-party/stb/data/atari_8bit_font_revised.png b/third-party/stb/data/atari_8bit_font_revised.png
new file mode 100644
index 0000000..d9e0215
--- /dev/null
+++ b/third-party/stb/data/atari_8bit_font_revised.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:48544ebe5dba177996abf56cfab1133e6d7cbee54ca73c2bb16bd830f029ae3c
+size 1769
diff --git a/third-party/stb/data/easy_font_raw.png b/third-party/stb/data/easy_font_raw.png
new file mode 100644
index 0000000..86d98e9
--- /dev/null
+++ b/third-party/stb/data/easy_font_raw.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bc9e9370e94ff7dafd124c5030dcd29ed4281ba663f98f19e34b609ad4498ca4
+size 645
diff --git a/third-party/stb/data/herringbone/license.txt b/third-party/stb/data/herringbone/license.txt
new file mode 100644
index 0000000..11ffc42
--- /dev/null
+++ b/third-party/stb/data/herringbone/license.txt
@@ -0,0 +1,4 @@
+All files in this directory are in the public domain. Where
+a public domain declaration is not recognized, you are granted
+a license to freely use, modify, and redistribute them in
+any way you choose.
\ No newline at end of file
diff --git a/third-party/stb/data/herringbone/template_caves_limit_connectivity.png b/third-party/stb/data/herringbone/template_caves_limit_connectivity.png
new file mode 100644
index 0000000..c2776a1
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_caves_limit_connectivity.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b20a6718aa2ad77858ce8400ab539a3c3af9a86049ee825c2c0b32aacbeb6b35
+size 10818
diff --git a/third-party/stb/data/herringbone/template_caves_tiny_corridors.png b/third-party/stb/data/herringbone/template_caves_tiny_corridors.png
new file mode 100644
index 0000000..8c7d7dd
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_caves_tiny_corridors.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5fdc5ab10125dfc183362491bc78349a23792d5155a56edb49b2a7ac01b69371
+size 4904
diff --git a/third-party/stb/data/herringbone/template_corner_caves.png b/third-party/stb/data/herringbone/template_corner_caves.png
new file mode 100644
index 0000000..284f01c
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_corner_caves.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d80b7470abb938124c2068e49cf4fd431278ef3661c9598b2ff1d8b609f05f28
+size 9337
diff --git a/third-party/stb/data/herringbone/template_horizontal_corridors_v1.png b/third-party/stb/data/herringbone/template_horizontal_corridors_v1.png
new file mode 100644
index 0000000..f18c614
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_horizontal_corridors_v1.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d9d76aaa0920a1e832cf443a247d30a15cc1cfc7db0625fdcb58609312a35473
+size 5491
diff --git a/third-party/stb/data/herringbone/template_horizontal_corridors_v2.png b/third-party/stb/data/herringbone/template_horizontal_corridors_v2.png
new file mode 100644
index 0000000..8c8de33
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_horizontal_corridors_v2.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:219969a389f8c267423725ba8af0e74294cab2a9bcc25a084213515e2820d3f1
+size 5759
diff --git a/third-party/stb/data/herringbone/template_horizontal_corridors_v3.png b/third-party/stb/data/herringbone/template_horizontal_corridors_v3.png
new file mode 100644
index 0000000..26f97a5
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_horizontal_corridors_v3.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:61ceebf8c3634947c836f9a85527737158c61a6dee013f5f788dfcb9051dd46c
+size 6766
diff --git a/third-party/stb/data/herringbone/template_limit_connectivity_fat.png b/third-party/stb/data/herringbone/template_limit_connectivity_fat.png
new file mode 100644
index 0000000..9d28bd6
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_limit_connectivity_fat.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0c6a2cef7255e9bda6fad0eeed4a12a1849d37b2e7de762f217b5ec0d690aa78
+size 6655
diff --git a/third-party/stb/data/herringbone/template_limited_connectivity.png b/third-party/stb/data/herringbone/template_limited_connectivity.png
new file mode 100644
index 0000000..c7aedc1
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_limited_connectivity.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:978793cbd677f3ad220a084d34475c618f7f4ffb87c8f7507490e72c28404421
+size 6728
diff --git a/third-party/stb/data/herringbone/template_maze_2_wide.png b/third-party/stb/data/herringbone/template_maze_2_wide.png
new file mode 100644
index 0000000..cd683ac
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_maze_2_wide.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9d5cca003e224b090f95d758c5bac831bd1d3ce47a5a91424c3d6dc48400fd68
+size 12776
diff --git a/third-party/stb/data/herringbone/template_maze_plus_2_wide.png b/third-party/stb/data/herringbone/template_maze_plus_2_wide.png
new file mode 100644
index 0000000..216aeda
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_maze_plus_2_wide.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e80abad457dec34d93671d1c1ad6c3548a320874466bd40c74cdeac6e5f962b4
+size 13141
diff --git a/third-party/stb/data/herringbone/template_open_areas.png b/third-party/stb/data/herringbone/template_open_areas.png
new file mode 100644
index 0000000..770db94
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_open_areas.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:44210039251cfe380097c9a74f0051aea1ec22cace11cc2b3daa13f8cb6f1f28
+size 7764
diff --git a/third-party/stb/data/herringbone/template_ref2_corner_caves.png b/third-party/stb/data/herringbone/template_ref2_corner_caves.png
new file mode 100644
index 0000000..0891db2
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_ref2_corner_caves.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:da0894b61b4419702a32007ba9b21ca1c7caa7f3c54a3df26f0b1c9a9abfa921
+size 9729
diff --git a/third-party/stb/data/herringbone/template_rooms_and_corridors.png b/third-party/stb/data/herringbone/template_rooms_and_corridors.png
new file mode 100644
index 0000000..a8fe86b
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_rooms_and_corridors.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d87abbd96ab7e814a67efc995ea47409415618e8c13023bfd80af383b4a7b976
+size 4736
diff --git a/third-party/stb/data/herringbone/template_rooms_and_corridors_2_wide_diagonal_bias.png b/third-party/stb/data/herringbone/template_rooms_and_corridors_2_wide_diagonal_bias.png
new file mode 100644
index 0000000..2a89442
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_rooms_and_corridors_2_wide_diagonal_bias.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:661417911b1447e5a32395d83af765a17c183bc85cdd0ebe1a6b42446bc97bda
+size 4193
diff --git a/third-party/stb/data/herringbone/template_rooms_limit_connectivity.png b/third-party/stb/data/herringbone/template_rooms_limit_connectivity.png
new file mode 100644
index 0000000..5c810f1
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_rooms_limit_connectivity.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8d30ba9f829d1ccda87f895a4f122a1d93b17a21d722252f7c08c4482be48cf9
+size 7664
diff --git a/third-party/stb/data/herringbone/template_round_rooms_diagonal_corridors.png b/third-party/stb/data/herringbone/template_round_rooms_diagonal_corridors.png
new file mode 100644
index 0000000..f8e5e8c
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_round_rooms_diagonal_corridors.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2ca960be4f4d7660652f9c35318869a1b20cc4667bc29302bd73a14677de9e04
+size 8172
diff --git a/third-party/stb/data/herringbone/template_sean_dungeon.png b/third-party/stb/data/herringbone/template_sean_dungeon.png
new file mode 100644
index 0000000..97ec43a
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_sean_dungeon.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a7b569bd653d3e77806703bce55020f9b610549dbbf36e069f24b90d80029aae
+size 10261
diff --git a/third-party/stb/data/herringbone/template_simple_caves_2_wide.png b/third-party/stb/data/herringbone/template_simple_caves_2_wide.png
new file mode 100644
index 0000000..54f3afb
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_simple_caves_2_wide.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bd3c9839c6eb98e03c7953db1d11dafeefa5c9244be2dd04476356795158ecab
+size 15712
diff --git a/third-party/stb/data/herringbone/template_square_rooms_with_random_rects.png b/third-party/stb/data/herringbone/template_square_rooms_with_random_rects.png
new file mode 100644
index 0000000..3751fd6
--- /dev/null
+++ b/third-party/stb/data/herringbone/template_square_rooms_with_random_rects.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c1e6fce24fb1e6d5db58c88fc33fce6380b428352d709f973412a759b7b73708
+size 4772
diff --git a/third-party/stb/data/map_01.png b/third-party/stb/data/map_01.png
new file mode 100644
index 0000000..bd19479
--- /dev/null
+++ b/third-party/stb/data/map_01.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:871a50d64fb238f18ebaeb06d5fae8b62f257d23475527d74db1e86c781860d8
+size 30625
diff --git a/third-party/stb/data/map_02.png b/third-party/stb/data/map_02.png
new file mode 100644
index 0000000..e7dd20e
--- /dev/null
+++ b/third-party/stb/data/map_02.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4ba835177f3f9d89ab3edcb035f25fb03bc6d77b914e79cf9dcd16ac26acfc71
+size 3243
diff --git a/third-party/stb/data/map_03.png b/third-party/stb/data/map_03.png
new file mode 100644
index 0000000..cb7fc3e
--- /dev/null
+++ b/third-party/stb/data/map_03.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d40e16682205cf1e83472798777a0535176401bf16630a5d30d825f9cb8f5ae9
+size 9620
diff --git a/third-party/stb/deprecated/rrsprintf.h b/third-party/stb/deprecated/rrsprintf.h
new file mode 100644
index 0000000..62962e3
--- /dev/null
+++ b/third-party/stb/deprecated/rrsprintf.h
@@ -0,0 +1,1055 @@
+#ifndef RR_SPRINTF_H_INCLUDE
+#define RR_SPRINTF_H_INCLUDE
+
+/*
+Single file sprintf replacement.
+
+Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20.
+Hereby placed in public domain.
+
+This is a full sprintf replacement that supports everything that
+the C runtime sprintfs support, including float/double, 64-bit integers,
+hex floats, field parameters (%*.*d stuff), length reads backs, etc.
+
+Why would you need this if sprintf already exists? Well, first off,
+it's *much* faster (see below). It's also much smaller than the CRT
+versions code-space-wise. We've also added some simple improvements
+that are super handy (commas in thousands, callbacks at buffer full,
+for example). Finally, the format strings for MSVC and GCC differ
+for 64-bit integers (among other small things), so this lets you use
+the same format strings in cross platform code.
+
+It uses the standard single file trick of being both the header file
+and the source itself. If you just include it normally, you just get
+the header file function definitions. To get the code, you include
+it from a C or C++ file and define RR_SPRINTF_IMPLEMENTATION first.
+
+It only uses va_args macros from the C runtime to do it's work. It
+does cast doubles to S64s and shifts and divides U64s, which does
+drag in CRT code on most platforms.
+
+It compiles to roughly 8K with float support, and 4K without.
+As a comparison, when using MSVC static libs, calling sprintf drags
+in 16K.
+
+API:
+====
+int rrsprintf( char * buf, char const * fmt, ... )
+int rrsnprintf( char * buf, int count, char const * fmt, ... )
+ Convert an arg list into a buffer. rrsnprintf always returns
+ a zero-terminated string (unlike regular snprintf).
+
+int rrvsprintf( char * buf, char const * fmt, va_list va )
+int rrvsnprintf( char * buf, int count, char const * fmt, va_list va )
+ Convert a va_list arg list into a buffer. rrvsnprintf always returns
+ a zero-terminated string (unlike regular snprintf).
+
+int rrvsprintfcb( RRSPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va )
+ typedef char * RRSPRINTFCB( char const * buf, void * user, int len );
+ Convert into a buffer, calling back every RR_SPRINTF_MIN chars.
+ Your callback can then copy the chars out, print them or whatever.
+ This function is actually the workhorse for everything else.
+ The buffer you pass in must hold at least RR_SPRINTF_MIN characters.
+ // you return the next buffer to use or 0 to stop converting
+
+void rrsetseparators( char comma, char period )
+ Set the comma and period characters to use.
+
+FLOATS/DOUBLES:
+===============
+This code uses a internal float->ascii conversion method that uses
+doubles with error correction (double-doubles, for ~105 bits of
+precision). This conversion is round-trip perfect - that is, an atof
+of the values output here will give you the bit-exact double back.
+
+One difference is that our insignificant digits will be different than
+with MSVC or GCC (but they don't match each other either). We also
+don't attempt to find the minimum length matching float (pre-MSVC15
+doesn't either).
+
+If you don't need float or doubles at all, define RR_SPRINTF_NOFLOAT
+and you'll save 4K of code space.
+
+64-BIT INTS:
+============
+This library also supports 64-bit integers and you can use MSVC style or
+GCC style indicators (%I64d or %lld). It supports the C99 specifiers
+for size_t and ptr_diff_t (%jd %zd) as well.
+
+EXTRAS:
+=======
+Like some GCCs, for integers and floats, you can use a ' (single quote)
+specifier and commas will be inserted on the thousands: "%'d" on 12345
+would print 12,345.
+
+For integers and floats, you can use a "$" specifier and the number
+will be converted to float and then divided to get kilo, mega, giga or
+tera and then printed, so "%$d" 1024 is "1.0 k", "%$.2d" 2536000 is
+"2.42 m", etc.
+
+In addition to octal and hexadecimal conversions, you can print
+integers in binary: "%b" for 256 would print 100.
+
+PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC):
+===================================================================
+"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC)
+"%24d" across all 32-bit ints (4.5x/4.2x faster)
+"%x" across all 32-bit ints (4.5x/3.8x faster)
+"%08x" across all 32-bit ints (4.3x/3.8x faster)
+"%f" across e-10 to e+10 floats (7.3x/6.0x faster)
+"%e" across e-10 to e+10 floats (8.1x/6.0x faster)
+"%g" across e-10 to e+10 floats (10.0x/7.1x faster)
+"%f" for values near e-300 (7.9x/6.5x faster)
+"%f" for values near e+300 (10.0x/9.1x faster)
+"%e" for values near e-300 (10.1x/7.0x faster)
+"%e" for values near e+300 (9.2x/6.0x faster)
+"%.320f" for values near e-300 (12.6x/11.2x faster)
+"%a" for random values (8.6x/4.3x faster)
+"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster)
+"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster)
+"%s%s%s" for 64 char strings (7.1x/7.3x faster)
+"...512 char string..." ( 35.0x/32.5x faster!)
+*/
+
+#ifdef RR_SPRINTF_STATIC
+#define RRPUBLIC_DEC static
+#define RRPUBLIC_DEF static
+#else
+#ifdef __cplusplus
+#define RRPUBLIC_DEC extern "C"
+#define RRPUBLIC_DEF extern "C"
+#else
+#define RRPUBLIC_DEC extern
+#define RRPUBLIC_DEF
+#endif
+#endif
+
+#include // for va_list()
+
+#ifndef RR_SPRINTF_MIN
+#define RR_SPRINTF_MIN 512 // how many characters per callback
+#endif
+typedef char * RRSPRINTFCB( char * buf, void * user, int len );
+
+#ifndef RR_SPRINTF_DECORATE
+#define RR_SPRINTF_DECORATE(name) rr##name // define this before including if you want to change the names
+#endif
+
+#ifndef RR_SPRINTF_IMPLEMENTATION
+
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsprintf )( char * buf, char const * fmt, va_list va );
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va );
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( sprintf ) ( char * buf, char const * fmt, ... );
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( snprintf )( char * buf, int count, char const * fmt, ... );
+
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsprintfcb )( RRSPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va );
+RRPUBLIC_DEF void RR_SPRINTF_DECORATE( setseparators )( char comma, char period );
+
+#else
+
+#include // for va_arg()
+
+#define rU32 unsigned int
+#define rS32 signed int
+
+#ifdef _MSC_VER
+#define rU64 unsigned __int64
+#define rS64 signed __int64
+#else
+#define rU64 unsigned long long
+#define rS64 signed long long
+#endif
+#define rU16 unsigned short
+
+#ifndef rUINTa
+#if defined(__ppc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64)
+#define rUINTa rU64
+#else
+#define rUINTa rU32
+#endif
+#endif
+
+#ifndef RR_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC)
+#if defined(_MSC_VER) && (_MSC_VER<1900)
+#define RR_SPRINTF_MSVC_MODE
+#endif
+#endif
+
+#ifdef RR_SPRINTF_NOUNALIGNED // define this before inclusion to force rrsprint to always use aligned accesses
+#define RR_UNALIGNED(code)
+#else
+#define RR_UNALIGNED(code) code
+#endif
+
+#ifndef RR_SPRINTF_NOFLOAT
+// internal float utility functions
+static rS32 rrreal_to_str( char const * * start, rU32 * len, char *out, rS32 * decimal_pos, double value, rU32 frac_digits );
+static rS32 rrreal_to_parts( rS64 * bits, rS32 * expo, double value );
+#define RRSPECIAL 0x7000
+#endif
+
+static char RRperiod='.';
+static char RRcomma=',';
+static char rrdiglookup[201]="00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899";
+
+RRPUBLIC_DEF void RR_SPRINTF_DECORATE( setseparators )( char pcomma, char pperiod )
+{
+ RRperiod=pperiod;
+ RRcomma=pcomma;
+}
+
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsprintfcb )( RRSPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va )
+{
+ static char hex[]="0123456789abcdefxp";
+ static char hexu[]="0123456789ABCDEFXP";
+ char * bf;
+ char const * f;
+ int tlen = 0;
+
+ bf = buf;
+ f = fmt;
+ for(;;)
+ {
+ rS32 fw,pr,tz; rU32 fl;
+
+ #define LJ 1
+ #define LP 2
+ #define LS 4
+ #define LX 8
+ #define LZ 16
+ #define BI 32
+ #define CS 64
+ #define NG 128
+ #define KI 256
+ #define HW 512
+
+ // macros for the callback buffer stuff
+ #define chk_cb_bufL(bytes) { int len = (int)(bf-buf); if ((len+(bytes))>=RR_SPRINTF_MIN) { tlen+=len; if (0==(bf=buf=callback(buf,user,len))) goto done; } }
+ #define chk_cb_buf(bytes) { if ( callback ) { chk_cb_bufL(bytes); } }
+ #define flush_cb() { chk_cb_bufL(RR_SPRINTF_MIN-1); } //flush if there is even one byte in the buffer
+ #define cb_buf_clamp(cl,v) cl = v; if ( callback ) { int lg = RR_SPRINTF_MIN-(int)(bf-buf); if (cl>lg) cl=lg; }
+
+ // fast copy everything up to the next % (or end of string)
+ for(;;)
+ {
+ while (((rUINTa)f)&3)
+ {
+ schk1: if (f[0]=='%') goto scandd;
+ schk2: if (f[0]==0) goto endfmt;
+ chk_cb_buf(1); *bf++=f[0]; ++f;
+ }
+ for(;;)
+ {
+ rU32 v,c;
+ v=*(rU32*)f; c=(~v)&0x80808080;
+ if ((v-0x26262626)&c) goto schk1;
+ if ((v-0x01010101)&c) goto schk2;
+ if (callback) if ((RR_SPRINTF_MIN-(int)(bf-buf))<4) goto schk1;
+ *(rU32*)bf=v; bf+=4; f+=4;
+ }
+ } scandd:
+
+ ++f;
+
+ // ok, we have a percent, read the modifiers first
+ fw = 0; pr = -1; fl = 0; tz = 0;
+
+ // flags
+ for(;;)
+ {
+ switch(f[0])
+ {
+ // if we have left just
+ case '-': fl|=LJ; ++f; continue;
+ // if we have leading plus
+ case '+': fl|=LP; ++f; continue;
+ // if we have leading space
+ case ' ': fl|=LS; ++f; continue;
+ // if we have leading 0x
+ case '#': fl|=LX; ++f; continue;
+ // if we have thousand commas
+ case '\'': fl|=CS; ++f; continue;
+ // if we have kilo marker
+ case '$': fl|=KI; ++f; continue;
+ // if we have leading zero
+ case '0': fl|=LZ; ++f; goto flags_done;
+ default: goto flags_done;
+ }
+ }
+ flags_done:
+
+ // get the field width
+ if ( f[0] == '*' ) {fw = va_arg(va,rU32); ++f;} else { while (( f[0] >= '0' ) && ( f[0] <= '9' )) { fw = fw * 10 + f[0] - '0'; f++; } }
+ // get the precision
+ if ( f[0]=='.' ) { ++f; if ( f[0] == '*' ) {pr = va_arg(va,rU32); ++f;} else { pr = 0; while (( f[0] >= '0' ) && ( f[0] <= '9' )) { pr = pr * 10 + f[0] - '0'; f++; } } }
+
+ // handle integer size overrides
+ switch(f[0])
+ {
+ // are we halfwidth?
+ case 'h': fl|=HW; ++f; break;
+ // are we 64-bit (unix style)
+ case 'l': ++f; if ( f[0]=='l') { fl|=BI; ++f; } break;
+ // are we 64-bit on intmax? (c99)
+ case 'j': fl|=BI; ++f; break;
+ // are we 64-bit on size_t or ptrdiff_t? (c99)
+ case 'z': case 't': fl|=((sizeof(char*)==8)?BI:0); ++f; break;
+ // are we 64-bit (msft style)
+ case 'I': if ( ( f[1]=='6') && ( f[2]=='4') ) { fl|=BI; f+=3; } else if ( ( f[1]=='3') && ( f[2]=='2') ) { f+=3; } else { fl|=((sizeof(void*)==8)?BI:0); ++f; } break;
+ default: break;
+ }
+
+ // handle each replacement
+ switch( f[0] )
+ {
+ #define NUMSZ 512 // big enough for e308 (with commas) or e-307
+ char num[NUMSZ];
+ char lead[8];
+ char tail[8];
+ char *s;
+ char const *h;
+ rU32 l,n,cs;
+ rU64 n64;
+ #ifndef RR_SPRINTF_NOFLOAT
+ double fv;
+ #endif
+ rS32 dp; char const * sn;
+
+ case 's':
+ // get the string
+ s = va_arg(va,char*); if (s==0) s = (char*)"null";
+ // get the length
+ sn = s;
+ for(;;)
+ {
+ if ((((rUINTa)sn)&3)==0) break;
+ lchk:
+ if (sn[0]==0) goto ld;
+ ++sn;
+ }
+ n = 0xffffffff;
+ if (pr>=0) { n=(rU32)(sn-s); if (n>=(rU32)pr) goto ld; n=((rU32)(pr-n))>>2; }
+ while(n)
+ {
+ rU32 v=*(rU32*)sn;
+ if ((v-0x01010101)&(~v)&0x80808080UL) goto lchk;
+ sn+=4;
+ --n;
+ }
+ goto lchk;
+ ld:
+
+ l = (rU32) ( sn - s );
+ // clamp to precision
+ if ( l > (rU32)pr ) l = pr;
+ lead[0]=0; tail[0]=0; pr = 0; dp = 0; cs = 0;
+ // copy the string in
+ goto scopy;
+
+ case 'c': // char
+ // get the character
+ s = num + NUMSZ -1; *s = (char)va_arg(va,int);
+ l = 1;
+ lead[0]=0; tail[0]=0; pr = 0; dp = 0; cs = 0;
+ goto scopy;
+
+ case 'n': // weird write-bytes specifier
+ { int * d = va_arg(va,int*);
+ *d = tlen + (int)( bf - buf ); }
+ break;
+
+#ifdef RR_SPRINTF_NOFLOAT
+ case 'A': // float
+ case 'a': // hex float
+ case 'G': // float
+ case 'g': // float
+ case 'E': // float
+ case 'e': // float
+ case 'f': // float
+ va_arg(va,double); // eat it
+ s = (char*)"No float";
+ l = 8;
+ lead[0]=0; tail[0]=0; pr = 0; dp = 0; cs = 0;
+ goto scopy;
+#else
+ case 'A': // float
+ h=hexu;
+ goto hexfloat;
+
+ case 'a': // hex float
+ h=hex;
+ hexfloat:
+ fv = va_arg(va,double);
+ if (pr==-1) pr=6; // default is 6
+ // read the double into a string
+ if ( rrreal_to_parts( (rS64*)&n64, &dp, fv ) )
+ fl |= NG;
+
+ s = num+64;
+
+ // sign
+ lead[0]=0; if (fl&NG) { lead[0]=1; lead[1]='-'; } else if (fl&LS) { lead[0]=1; lead[1]=' '; } else if (fl&LP) { lead[0]=1; lead[1]='+'; };
+
+ if (dp==-1023) dp=(n64)?-1022:0; else n64|=(((rU64)1)<<52);
+ n64<<=(64-56);
+ if (pr<15) n64+=((((rU64)8)<<56)>>(pr*4));
+ // add leading chars
+
+ #ifdef RR_SPRINTF_MSVC_MODE
+ *s++='0';*s++='x';
+ #else
+ lead[1+lead[0]]='0'; lead[2+lead[0]]='x'; lead[0]+=2;
+ #endif
+ *s++=h[(n64>>60)&15]; n64<<=4;
+ if ( pr ) *s++=RRperiod;
+ sn = s;
+
+ // print the bits
+ n = pr; if (n>13) n = 13; if (pr>(rS32)n) tz=pr-n; pr = 0;
+ while(n--) { *s++=h[(n64>>60)&15]; n64<<=4; }
+
+ // print the expo
+ tail[1]=h[17];
+ if (dp<0) { tail[2]='-'; dp=-dp;} else tail[2]='+';
+ n = (dp>=1000)?6:((dp>=100)?5:((dp>=10)?4:3));
+ tail[0]=(char)n;
+ for(;;) { tail[n]='0'+dp%10; if (n<=3) break; --n; dp/=10; }
+
+ dp = (int)(s-sn);
+ l = (int)(s-(num+64));
+ s = num+64;
+ cs = 1 + (3<<24);
+ goto scopy;
+
+ case 'G': // float
+ h=hexu;
+ goto dosmallfloat;
+
+ case 'g': // float
+ h=hex;
+ dosmallfloat:
+ fv = va_arg(va,double);
+ if (pr==-1) pr=6; else if (pr==0) pr = 1; // default is 6
+ // read the double into a string
+ if ( rrreal_to_str( &sn, &l, num, &dp, fv, (pr-1)|0x80000000 ) )
+ fl |= NG;
+
+ // clamp the precision and delete extra zeros after clamp
+ n = pr;
+ if ( l > (rU32)pr ) l = pr; while ((l>1)&&(pr)&&(sn[l-1]=='0')) { --pr; --l; }
+
+ // should we use %e
+ if ((dp<=-4)||(dp>(rS32)n))
+ {
+ if ( pr > (rS32)l ) pr = l-1; else if ( pr ) --pr; // when using %e, there is one digit before the decimal
+ goto doexpfromg;
+ }
+ // this is the insane action to get the pr to match %g sematics for %f
+ if(dp>0) { pr=(dp<(rS32)l)?l-dp:0; } else { pr = -dp+((pr>(rS32)l)?l:pr); }
+ goto dofloatfromg;
+
+ case 'E': // float
+ h=hexu;
+ goto doexp;
+
+ case 'e': // float
+ h=hex;
+ doexp:
+ fv = va_arg(va,double);
+ if (pr==-1) pr=6; // default is 6
+ // read the double into a string
+ if ( rrreal_to_str( &sn, &l, num, &dp, fv, pr|0x80000000 ) )
+ fl |= NG;
+ doexpfromg:
+ tail[0]=0;
+ lead[0]=0; if (fl&NG) { lead[0]=1; lead[1]='-'; } else if (fl&LS) { lead[0]=1; lead[1]=' '; } else if (fl&LP) { lead[0]=1; lead[1]='+'; };
+ if ( dp == RRSPECIAL ) { s=(char*)sn; cs=0; pr=0; goto scopy; }
+ s=num+64;
+ // handle leading chars
+ *s++=sn[0];
+
+ if (pr) *s++=RRperiod;
+
+ // handle after decimal
+ if ((l-1)>(rU32)pr) l=pr+1;
+ for(n=1;n=100)?5:4;
+ #endif
+ tail[0]=(char)n;
+ for(;;) { tail[n]='0'+dp%10; if (n<=3) break; --n; dp/=10; }
+ cs = 1 + (3<<24); // how many tens
+ goto flt_lead;
+
+ case 'f': // float
+ fv = va_arg(va,double);
+ doafloat:
+ // do kilos
+ if (fl&KI) {while(fl<0x4000000) { if ((fv<1024.0) && (fv>-1024.0)) break; fv/=1024.0; fl+=0x1000000; }}
+ if (pr==-1) pr=6; // default is 6
+ // read the double into a string
+ if ( rrreal_to_str( &sn, &l, num, &dp, fv, pr ) )
+ fl |= NG;
+ dofloatfromg:
+ tail[0]=0;
+ // sign
+ lead[0]=0; if (fl&NG) { lead[0]=1; lead[1]='-'; } else if (fl&LS) { lead[0]=1; lead[1]=' '; } else if (fl&LP) { lead[0]=1; lead[1]='+'; };
+ if ( dp == RRSPECIAL ) { s=(char*)sn; cs=0; pr=0; goto scopy; }
+ s=num+64;
+
+ // handle the three decimal varieties
+ if (dp<=0)
+ {
+ rS32 i;
+ // handle 0.000*000xxxx
+ *s++='0'; if (pr) *s++=RRperiod;
+ n=-dp; if((rS32)n>pr) n=pr; i=n; while(i) { if ((((rUINTa)s)&3)==0) break; *s++='0'; --i; } while(i>=4) { *(rU32*)s=0x30303030; s+=4; i-=4; } while(i) { *s++='0'; --i; }
+ if ((rS32)(l+n)>pr) l=pr-n; i=l; while(i) { *s++=*sn++; --i; }
+ tz = pr-(n+l);
+ cs = 1 + (3<<24); // how many tens did we write (for commas below)
+ }
+ else
+ {
+ cs = (fl&CS)?((600-(rU32)dp)%3):0;
+ if ((rU32)dp>=l)
+ {
+ // handle xxxx000*000.0
+ n=0; for(;;) { if ((fl&CS) && (++cs==4)) { cs = 0; *s++=RRcomma; } else { *s++=sn[n]; ++n; if (n>=l) break; } }
+ if (n<(rU32)dp)
+ {
+ n = dp - n;
+ if ((fl&CS)==0) { while(n) { if ((((rUINTa)s)&3)==0) break; *s++='0'; --n; } while(n>=4) { *(rU32*)s=0x30303030; s+=4; n-=4; } }
+ while(n) { if ((fl&CS) && (++cs==4)) { cs = 0; *s++=RRcomma; } else { *s++='0'; --n; } }
+ }
+ cs = (int)(s-(num+64)) + (3<<24); // cs is how many tens
+ if (pr) { *s++=RRperiod; tz=pr;}
+ }
+ else
+ {
+ // handle xxxxx.xxxx000*000
+ n=0; for(;;) { if ((fl&CS) && (++cs==4)) { cs = 0; *s++=RRcomma; } else { *s++=sn[n]; ++n; if (n>=(rU32)dp) break; } }
+ cs = (int)(s-(num+64)) + (3<<24); // cs is how many tens
+ if (pr) *s++=RRperiod;
+ if ((l-dp)>(rU32)pr) l=pr+dp;
+ while(n>24) { tail[2]="_kmgt"[fl>>24]; tail[0]=2; } } };
+
+ flt_lead:
+ // get the length that we copied
+ l = (rU32) ( s-(num+64) );
+ s=num+64;
+ goto scopy;
+#endif
+
+ case 'B': // upper binary
+ h = hexu;
+ goto binary;
+
+ case 'b': // lower binary
+ h = hex;
+ binary:
+ lead[0]=0;
+ if (fl&LX) { lead[0]=2;lead[1]='0';lead[2]=h[0xb]; }
+ l=(8<<4)|(1<<8);
+ goto radixnum;
+
+ case 'o': // octal
+ h = hexu;
+ lead[0]=0;
+ if (fl&LX) { lead[0]=1;lead[1]='0'; }
+ l=(3<<4)|(3<<8);
+ goto radixnum;
+
+ case 'p': // pointer
+ fl |= (sizeof(void*)==8)?BI:0;
+ pr = sizeof(void*)*2;
+ fl &= ~LZ; // 'p' only prints the pointer with zeros
+ // drop through to X
+
+ case 'X': // upper binary
+ h = hexu;
+ goto dohexb;
+
+ case 'x': // lower binary
+ h = hex; dohexb:
+ l=(4<<4)|(4<<8);
+ lead[0]=0;
+ if (fl&LX) { lead[0]=2;lead[1]='0';lead[2]=h[16]; }
+ radixnum:
+ // get the number
+ if ( fl&BI )
+ n64 = va_arg(va,rU64);
+ else
+ n64 = va_arg(va,rU32);
+
+ s = num + NUMSZ; dp = 0;
+ // clear tail, and clear leading if value is zero
+ tail[0]=0; if (n64==0) { lead[0]=0; if (pr==0) { l=0; cs = ( ((l>>4)&15)) << 24; goto scopy; } }
+ // convert to string
+ for(;;) { *--s = h[n64&((1<<(l>>8))-1)]; n64>>=(l>>8); if ( ! ( (n64) || ((rS32) ( (num+NUMSZ) - s ) < pr ) ) ) break; if ( fl&CS) { ++l; if ((l&15)==((l>>4)&15)) { l&=~15; *--s=RRcomma; } } };
+ // get the tens and the comma pos
+ cs = (rU32) ( (num+NUMSZ) - s ) + ( ( ((l>>4)&15)) << 24 );
+ // get the length that we copied
+ l = (rU32) ( (num+NUMSZ) - s );
+ // copy it
+ goto scopy;
+
+ case 'u': // unsigned
+ case 'i':
+ case 'd': // integer
+ // get the integer and abs it
+ if ( fl&BI )
+ {
+ rS64 i64 = va_arg(va,rS64); n64 = (rU64)i64; if ((f[0]!='u') && (i64<0)) { n64=(rU64)-i64; fl|=NG; }
+ }
+ else
+ {
+ rS32 i = va_arg(va,rS32); n64 = (rU32)i; if ((f[0]!='u') && (i<0)) { n64=(rU32)-i; fl|=NG; }
+ }
+
+ #ifndef RR_SPRINTF_NOFLOAT
+ if (fl&KI) { if (n64<1024) pr=0; else if (pr==-1) pr=1; fv=(double)(rS64)n64; goto doafloat; }
+ #endif
+
+ // convert to string
+ s = num+NUMSZ; l=0;
+
+ for(;;)
+ {
+ // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators)
+ char * o=s-8;
+ if (n64>=100000000) { n = (rU32)( n64 % 100000000); n64 /= 100000000; } else {n = (rU32)n64; n64 = 0; }
+ if((fl&CS)==0) { while(n) { s-=2; *(rU16*)s=*(rU16*)&rrdiglookup[(n%100)*2]; n/=100; } }
+ while (n) { if ( ( fl&CS) && (l++==3) ) { l=0; *--s=RRcomma; --o; } else { *--s=(char)(n%10)+'0'; n/=10; } }
+ if (n64==0) { if ((s[0]=='0') && (s!=(num+NUMSZ))) ++s; break; }
+ while (s!=o) if ( ( fl&CS) && (l++==3) ) { l=0; *--s=RRcomma; --o; } else { *--s='0'; }
+ }
+
+ tail[0]=0;
+ // sign
+ lead[0]=0; if (fl&NG) { lead[0]=1; lead[1]='-'; } else if (fl&LS) { lead[0]=1; lead[1]=' '; } else if (fl&LP) { lead[0]=1; lead[1]='+'; };
+
+ // get the length that we copied
+ l = (rU32) ( (num+NUMSZ) - s ); if ( l == 0 ) { *--s='0'; l = 1; }
+ cs = l + (3<<24);
+ if (pr<0) pr = 0;
+
+ scopy:
+ // get fw=leading/trailing space, pr=leading zeros
+ if (pr<(rS32)l) pr = l;
+ n = pr + lead[0] + tail[0] + tz;
+ if (fw<(rS32)n) fw = n;
+ fw -= n;
+ pr -= l;
+
+ // handle right justify and leading zeros
+ if ( (fl&LJ)==0 )
+ {
+ if (fl&LZ) // if leading zeros, everything is in pr
+ {
+ pr = (fw>pr)?fw:pr;
+ fw = 0;
+ }
+ else
+ {
+ fl &= ~CS; // if no leading zeros, then no commas
+ }
+ }
+
+ // copy the spaces and/or zeros
+ if (fw+pr)
+ {
+ rS32 i; rU32 c;
+
+ // copy leading spaces (or when doing %8.4d stuff)
+ if ( (fl&LJ)==0 ) while(fw>0) { cb_buf_clamp(i,fw); fw -= i; while(i) { if ((((rUINTa)bf)&3)==0) break; *bf++=' '; --i; } while(i>=4) { *(rU32*)bf=0x20202020; bf+=4; i-=4; } while (i) {*bf++=' '; --i;} chk_cb_buf(1); }
+
+ // copy leader
+ sn=lead+1; while(lead[0]) { cb_buf_clamp(i,lead[0]); lead[0] -= (char)i; while (i) {*bf++=*sn++; --i;} chk_cb_buf(1); }
+
+ // copy leading zeros
+ c = cs >> 24; cs &= 0xffffff;
+ cs = (fl&CS)?((rU32)(c-((pr+cs)%(c+1)))):0;
+ while(pr>0) { cb_buf_clamp(i,pr); pr -= i; if((fl&CS)==0) { while(i) { if ((((rUINTa)bf)&3)==0) break; *bf++='0'; --i; } while(i>=4) { *(rU32*)bf=0x30303030; bf+=4; i-=4; } } while (i) { if((fl&CS) && (cs++==c)) { cs = 0; *bf++=RRcomma; } else *bf++='0'; --i; } chk_cb_buf(1); }
+ }
+
+ // copy leader if there is still one
+ sn=lead+1; while(lead[0]) { rS32 i; cb_buf_clamp(i,lead[0]); lead[0] -= (char)i; while (i) {*bf++=*sn++; --i;} chk_cb_buf(1); }
+
+ // copy the string
+ n = l; while (n) { rS32 i; cb_buf_clamp(i,n); n-=i; RR_UNALIGNED( while(i>=4) { *(rU32*)bf=*(rU32*)s; bf+=4; s+=4; i-=4; } ) while (i) {*bf++=*s++; --i;} chk_cb_buf(1); }
+
+ // copy trailing zeros
+ while(tz) { rS32 i; cb_buf_clamp(i,tz); tz -= i; while(i) { if ((((rUINTa)bf)&3)==0) break; *bf++='0'; --i; } while(i>=4) { *(rU32*)bf=0x30303030; bf+=4; i-=4; } while (i) {*bf++='0'; --i;} chk_cb_buf(1); }
+
+ // copy tail if there is one
+ sn=tail+1; while(tail[0]) { rS32 i; cb_buf_clamp(i,tail[0]); tail[0] -= (char)i; while (i) {*bf++=*sn++; --i;} chk_cb_buf(1); }
+
+ // handle the left justify
+ if (fl&LJ) if (fw>0) { while (fw) { rS32 i; cb_buf_clamp(i,fw); fw-=i; while(i) { if ((((rUINTa)bf)&3)==0) break; *bf++=' '; --i; } while(i>=4) { *(rU32*)bf=0x20202020; bf+=4; i-=4; } while (i--) *bf++=' '; chk_cb_buf(1); } }
+ break;
+
+ default: // unknown, just copy code
+ s = num + NUMSZ -1; *s = f[0];
+ l = 1;
+ fw=pr=fl=0;
+ lead[0]=0; tail[0]=0; pr = 0; dp = 0; cs = 0;
+ goto scopy;
+ }
+ ++f;
+ }
+ endfmt:
+
+ if (!callback)
+ *bf = 0;
+ else
+ flush_cb();
+
+ done:
+ return tlen + (int)(bf-buf);
+}
+
+// cleanup
+#undef LJ
+#undef LP
+#undef LS
+#undef LX
+#undef LZ
+#undef BI
+#undef CS
+#undef NG
+#undef KI
+#undef NUMSZ
+#undef chk_cb_bufL
+#undef chk_cb_buf
+#undef flush_cb
+#undef cb_buf_clamp
+
+// ============================================================================
+// wrapper functions
+
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( sprintf )( char * buf, char const * fmt, ... )
+{
+ va_list va;
+ va_start( va, fmt );
+ return RR_SPRINTF_DECORATE( vsprintfcb )( 0, 0, buf, fmt, va );
+}
+
+typedef struct RRCCS
+{
+ char * buf;
+ int count;
+ char tmp[ RR_SPRINTF_MIN ];
+} RRCCS;
+
+static char * rrclampcallback( char * buf, void * user, int len )
+{
+ RRCCS * c = (RRCCS*)user;
+
+ if ( len > c->count ) len = c->count;
+
+ if (len)
+ {
+ if ( buf != c->buf )
+ {
+ char * s, * d, * se;
+ d = c->buf; s = buf; se = buf+len;
+ do{ *d++ = *s++; } while (sbuf += len;
+ c->count -= len;
+ }
+
+ if ( c->count <= 0 ) return 0;
+ return ( c->count >= RR_SPRINTF_MIN ) ? c->buf : c->tmp; // go direct into buffer if you can
+}
+
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va )
+{
+ RRCCS c;
+ int l;
+
+ if ( count == 0 )
+ return 0;
+
+ c.buf = buf;
+ c.count = count;
+
+ RR_SPRINTF_DECORATE( vsprintfcb )( rrclampcallback, &c, rrclampcallback(0,&c,0), fmt, va );
+
+ // zero-terminate
+ l = (int)( c.buf - buf );
+ if ( l >= count ) // should never be greater, only equal (or less) than count
+ l = count - 1;
+ buf[l] = 0;
+
+ return l;
+}
+
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( snprintf )( char * buf, int count, char const * fmt, ... )
+{
+ va_list va;
+ va_start( va, fmt );
+
+ return RR_SPRINTF_DECORATE( vsnprintf )( buf, count, fmt, va );
+}
+
+RRPUBLIC_DEF int RR_SPRINTF_DECORATE( vsprintf )( char * buf, char const * fmt, va_list va )
+{
+ return RR_SPRINTF_DECORATE( vsprintfcb )( 0, 0, buf, fmt, va );
+}
+
+// =======================================================================
+// low level float utility functions
+
+#ifndef RR_SPRINTF_NOFLOAT
+
+ // copies d to bits w/ strict aliasing (this compiles to nothing on /Ox)
+ #define RRCOPYFP(dest,src) { int cn; for(cn=0;cn<8;cn++) ((char*)&dest)[cn]=((char*)&src)[cn]; }
+
+// get float info
+static rS32 rrreal_to_parts( rS64 * bits, rS32 * expo, double value )
+{
+ double d;
+ rS64 b = 0;
+
+ // load value and round at the frac_digits
+ d = value;
+
+ RRCOPYFP( b, d );
+
+ *bits = b & ((((rU64)1)<<52)-1);
+ *expo = ((b >> 52) & 2047)-1023;
+
+ return (rS32)(b >> 63);
+}
+
+static double const rrbot[23]={1e+000,1e+001,1e+002,1e+003,1e+004,1e+005,1e+006,1e+007,1e+008,1e+009,1e+010,1e+011,1e+012,1e+013,1e+014,1e+015,1e+016,1e+017,1e+018,1e+019,1e+020,1e+021,1e+022};
+static double const rrnegbot[22]={1e-001,1e-002,1e-003,1e-004,1e-005,1e-006,1e-007,1e-008,1e-009,1e-010,1e-011,1e-012,1e-013,1e-014,1e-015,1e-016,1e-017,1e-018,1e-019,1e-020,1e-021,1e-022};
+static double const rrnegboterr[22]={-5.551115123125783e-018,-2.0816681711721684e-019,-2.0816681711721686e-020,-4.7921736023859299e-021,-8.1803053914031305e-022,4.5251888174113741e-023,4.5251888174113739e-024,-2.0922560830128471e-025,-6.2281591457779853e-026,-3.6432197315497743e-027,6.0503030718060191e-028,2.0113352370744385e-029,-3.0373745563400371e-030,1.1806906454401013e-032,-7.7705399876661076e-032,2.0902213275965398e-033,-7.1542424054621921e-034,-7.1542424054621926e-035,2.4754073164739869e-036,5.4846728545790429e-037,9.2462547772103625e-038,-4.8596774326570872e-039};
+static double const rrtop[13]={1e+023,1e+046,1e+069,1e+092,1e+115,1e+138,1e+161,1e+184,1e+207,1e+230,1e+253,1e+276,1e+299};
+static double const rrnegtop[13]={1e-023,1e-046,1e-069,1e-092,1e-115,1e-138,1e-161,1e-184,1e-207,1e-230,1e-253,1e-276,1e-299};
+static double const rrtoperr[13]={8388608,6.8601809640529717e+028,-7.253143638152921e+052,-4.3377296974619174e+075,-1.5559416129466825e+098,-3.2841562489204913e+121,-3.7745893248228135e+144,-1.7356668416969134e+167,-3.8893577551088374e+190,-9.9566444326005119e+213,6.3641293062232429e+236,-5.2069140800249813e+259,-5.2504760255204387e+282};
+static double const rrnegtoperr[13]={3.9565301985100693e-040,-2.299904345391321e-063,3.6506201437945798e-086,1.1875228833981544e-109,-5.0644902316928607e-132,-6.7156837247865426e-155,-2.812077463003139e-178,-5.7778912386589953e-201,7.4997100559334532e-224,-4.6439668915134491e-247,-6.3691100762962136e-270,-9.436808465446358e-293,8.0970921678014997e-317};
+
+#if defined(_MSC_VER) && (_MSC_VER<=1200)
+static rU64 const rrpot[20]={1,10,100,1000, 10000,100000,1000000,10000000, 100000000,1000000000,10000000000,100000000000, 1000000000000,10000000000000,100000000000000,1000000000000000, 10000000000000000,100000000000000000,1000000000000000000,10000000000000000000U };
+#define rrtento19th ((rU64)1000000000000000000)
+#else
+static rU64 const rrpot[20]={1,10,100,1000, 10000,100000,1000000,10000000, 100000000,1000000000,10000000000ULL,100000000000ULL, 1000000000000ULL,10000000000000ULL,100000000000000ULL,1000000000000000ULL, 10000000000000000ULL,100000000000000000ULL,1000000000000000000ULL,10000000000000000000ULL };
+#define rrtento19th (1000000000000000000ULL)
+#endif
+
+#define rrddmulthi(oh,ol,xh,yh) \
+{ \
+ double ahi=0,alo,bhi=0,blo; \
+ rS64 bt; \
+ oh = xh * yh; \
+ RRCOPYFP(bt,xh); bt&=((~(rU64)0)<<27); RRCOPYFP(ahi,bt); alo = xh-ahi; \
+ RRCOPYFP(bt,yh); bt&=((~(rU64)0)<<27); RRCOPYFP(bhi,bt); blo = yh-bhi; \
+ ol = ((ahi*bhi-oh)+ahi*blo+alo*bhi)+alo*blo; \
+}
+
+#define rrddtoS64(ob,xh,xl) \
+{ \
+ double ahi=0,alo,vh,t;\
+ ob = (rS64)ph;\
+ vh=(double)ob;\
+ ahi = ( xh - vh );\
+ t = ( ahi - xh );\
+ alo = (xh-(ahi-t))-(vh+t);\
+ ob += (rS64)(ahi+alo+xl);\
+}
+
+
+#define rrddrenorm(oh,ol) { double s; s=oh+ol; ol=ol-(s-oh); oh=s; }
+
+#define rrddmultlo(oh,ol,xh,xl,yh,yl) \
+ ol = ol + ( xh*yl + xl*yh ); \
+
+#define rrddmultlos(oh,ol,xh,yl) \
+ ol = ol + ( xh*yl ); \
+
+static void rrraise_to_power10( double *ohi, double *olo, double d, rS32 power ) // power can be -323 to +350
+{
+ double ph, pl;
+ if ((power>=0) && (power<=22))
+ {
+ rrddmulthi(ph,pl,d,rrbot[power]);
+ }
+ else
+ {
+ rS32 e,et,eb;
+ double p2h,p2l;
+
+ e=power; if (power<0) e=-e;
+ et = (e*0x2c9)>>14;/* %23 */ if (et>13) et=13; eb = e-(et*23);
+
+ ph = d; pl = 0.0;
+ if (power<0)
+ {
+ if (eb) { --eb; rrddmulthi(ph,pl,d,rrnegbot[eb]); rrddmultlos(ph,pl,d,rrnegboterr[eb]); }
+ if (et)
+ {
+ rrddrenorm(ph,pl);
+ --et; rrddmulthi(p2h,p2l,ph,rrnegtop[et]); rrddmultlo(p2h,p2l,ph,pl,rrnegtop[et],rrnegtoperr[et]); ph=p2h;pl=p2l;
+ }
+ }
+ else
+ {
+ if (eb)
+ {
+ e = eb; if (eb>22) eb=22; e -= eb;
+ rrddmulthi(ph,pl,d,rrbot[eb]);
+ if ( e ) { rrddrenorm(ph,pl); rrddmulthi(p2h,p2l,ph,rrbot[e]); rrddmultlos(p2h,p2l,rrbot[e],pl); ph=p2h;pl=p2l; }
+ }
+ if (et)
+ {
+ rrddrenorm(ph,pl);
+ --et; rrddmulthi(p2h,p2l,ph,rrtop[et]); rrddmultlo(p2h,p2l,ph,pl,rrtop[et],rrtoperr[et]); ph=p2h;pl=p2l;
+ }
+ }
+ }
+ rrddrenorm(ph,pl);
+ *ohi = ph; *olo = pl;
+}
+
+// given a float value, returns the significant bits in bits, and the position of the
+// decimal point in decimal_pos. +/-INF and NAN are specified by special values
+// returned in the decimal_pos parameter.
+// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000
+static rS32 rrreal_to_str( char const * * start, rU32 * len, char *out, rS32 * decimal_pos, double value, rU32 frac_digits )
+{
+ double d;
+ rS64 bits = 0;
+ rS32 expo, e, ng, tens;
+
+ d = value;
+ RRCOPYFP(bits,d);
+ expo = (bits >> 52) & 2047;
+ ng = (rS32)(bits >> 63);
+ if (ng) d=-d;
+
+ if ( expo == 2047 ) // is nan or inf?
+ {
+ *start = (bits&((((rU64)1)<<52)-1)) ? "NaN" : "Inf";
+ *decimal_pos = RRSPECIAL;
+ *len = 3;
+ return ng;
+ }
+
+ if ( expo == 0 ) // is zero or denormal
+ {
+ if ((bits<<1)==0) // do zero
+ {
+ *decimal_pos = 1;
+ *start = out;
+ out[0] = '0'; *len = 1;
+ return ng;
+ }
+ // find the right expo for denormals
+ {
+ rS64 v = ((rU64)1)<<51;
+ while ((bits&v)==0) { --expo; v >>= 1; }
+ }
+ }
+
+ // find the decimal exponent as well as the decimal bits of the value
+ {
+ double ph,pl;
+
+ // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046
+ tens=expo-1023; tens = (tens<0)?((tens*617)/2048):(((tens*1233)/4096)+1);
+
+ // move the significant bits into position and stick them into an int
+ rrraise_to_power10( &ph, &pl, d, 18-tens );
+
+ // get full as much precision from double-double as possible
+ rrddtoS64( bits, ph,pl );
+
+ // check if we undershot
+ if ( ((rU64)bits) >= rrtento19th ) ++tens;
+ }
+
+ // now do the rounding in integer land
+ frac_digits = ( frac_digits & 0x80000000 ) ? ( (frac_digits&0x7ffffff) + 1 ) : ( tens + frac_digits );
+ if ( ( frac_digits < 24 ) )
+ {
+ rU32 dg = 1; if ((rU64)bits >= rrpot[9] ) dg=10; while( (rU64)bits >= rrpot[dg] ) { ++dg; if (dg==20) goto noround; }
+ if ( frac_digits < dg )
+ {
+ rU64 r;
+ // add 0.5 at the right position and round
+ e = dg - frac_digits;
+ if ( (rU32)e >= 24 ) goto noround;
+ r = rrpot[e];
+ bits = bits + (r/2);
+ if ( (rU64)bits >= rrpot[dg] ) ++tens;
+ bits /= r;
+ }
+ noround:;
+ }
+
+ // kill long trailing runs of zeros
+ if ( bits )
+ {
+ rU32 n; for(;;) { if ( bits<=0xffffffff ) break; if (bits%1000) goto donez; bits/=1000; } n = (rU32)bits; while ((n%1000)==0) n/=1000; bits=n; donez:;
+ }
+
+ // convert to string
+ out += 64;
+ e = 0;
+ for(;;)
+ {
+ rU32 n;
+ char * o = out-8;
+ // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned)
+ if (bits>=100000000) { n = (rU32)( bits % 100000000); bits /= 100000000; } else {n = (rU32)bits; bits = 0; }
+ while(n) { out-=2; *(rU16*)out=*(rU16*)&rrdiglookup[(n%100)*2]; n/=100; e+=2; }
+ if (bits==0) { if ((e) && (out[0]=='0')) { ++out; --e; } break; }
+ while( out!=o ) { *--out ='0'; ++e; }
+ }
+
+ *decimal_pos = tens;
+ *start = out;
+ *len = e;
+ return ng;
+}
+
+#undef rrddmulthi
+#undef rrddrenorm
+#undef rrddmultlo
+#undef rrddmultlos
+#undef RRSPECIAL
+#undef RRCOPYFP
+
+#endif
+
+// clean up
+#undef rU16
+#undef rU32
+#undef rS32
+#undef rU64
+#undef rS64
+#undef RRPUBLIC_DEC
+#undef RRPUBLIC_DEF
+#undef RR_SPRINTF_DECORATE
+#undef RR_UNALIGNED
+
+#endif
+
+#endif
diff --git a/third-party/stb/deprecated/stb.h b/third-party/stb/deprecated/stb.h
new file mode 100644
index 0000000..1633c3b
--- /dev/null
+++ b/third-party/stb/deprecated/stb.h
@@ -0,0 +1,13111 @@
+/* stb.h - v2.37 - Sean's Tool Box -- public domain -- http://nothings.org/stb.h
+ no warranty is offered or implied; use this code at your own risk
+
+ This is a single header file with a bunch of useful utilities
+ for getting stuff done in C/C++.
+
+ Documentation: http://nothings.org/stb/stb_h.html
+ Unit tests: http://nothings.org/stb/stb.c
+
+ ============================================================================
+ You MUST
+
+ #define STB_DEFINE
+
+ in EXACTLY _one_ C or C++ file that includes this header, BEFORE the
+ include, like this:
+
+ #define STB_DEFINE
+ #include "stb.h"
+
+ All other files should just #include "stb.h" without the #define.
+ ============================================================================
+
+Version History
+
+ 2.36 various fixes
+ 2.35 fix clang-cl issues with swprintf
+ 2.34 fix warnings
+ 2.33 more fixes to random numbers
+ 2.32 stb_intcmprev, stb_uidict, fix random numbers on Linux
+ 2.31 stb_ucharcmp
+ 2.30 MinGW fix
+ 2.29 attempt to fix use of swprintf()
+ 2.28 various new functionality
+ 2.27 test _WIN32 not WIN32 in STB_THREADS
+ 2.26 various warning & bugfixes
+ 2.25 various warning & bugfixes
+ 2.24 various warning & bugfixes
+ 2.23 fix 2.22
+ 2.22 64-bit fixes from '!='; fix stb_sdict_copy() to have preferred name
+ 2.21 utf-8 decoder rejects "overlong" encodings; attempted 64-bit improvements
+ 2.20 fix to hash "copy" function--reported by someone with handle "!="
+ 2.19 ???
+ 2.18 stb_readdir_subdirs_mask
+ 2.17 stb_cfg_dir
+ 2.16 fix stb_bgio_, add stb_bgio_stat(); begin a streaming wrapper
+ 2.15 upgraded hash table template to allow:
+ - aggregate keys (explicit comparison func for EMPTY and DEL keys)
+ - "static" implementations (so they can be culled if unused)
+ 2.14 stb_mprintf
+ 2.13 reduce identifiable strings in STB_NO_STB_STRINGS
+ 2.12 fix STB_ONLY -- lots of uint32s, TRUE/FALSE things had crept in
+ 2.11 fix bug in stb_dirtree_get() which caused "c://path" sorts of stuff
+ 2.10 STB_F(), STB_I() inline constants (also KI,KU,KF,KD)
+ 2.09 stb_box_face_vertex_axis_side
+ 2.08 bugfix stb_trimwhite()
+ 2.07 colored printing in windows (why are we in 1985?)
+ 2.06 comparison functions are now functions-that-return-functions and
+ accept a struct-offset as a parameter (not thread-safe)
+ 2.05 compile and pass tests under Linux (but no threads); thread cleanup
+ 2.04 stb_cubic_bezier_1d, smoothstep, avoid dependency on registry
+ 2.03 ?
+ 2.02 remove integrated documentation
+ 2.01 integrate various fixes; stb_force_uniprocessor
+ 2.00 revised stb_dupe to use multiple hashes
+ 1.99 stb_charcmp
+ 1.98 stb_arr_deleten, stb_arr_insertn
+ 1.97 fix stb_newell_normal()
+ 1.96 stb_hash_number()
+ 1.95 hack stb__rec_max; clean up recursion code to use new functions
+ 1.94 stb_dirtree; rename stb_extra to stb_ptrmap
+ 1.93 stb_sem_new() API cleanup (no blockflag-starts blocked; use 'extra')
+ 1.92 stb_threadqueue--multi reader/writer queue, fixed size or resizeable
+ 1.91 stb_bgio_* for reading disk asynchronously
+ 1.90 stb_mutex uses CRITICAL_REGION; new stb_sync primitive for thread
+ joining; workqueue supports stb_sync instead of stb_semaphore
+ 1.89 support ';' in constant-string wildcards; stb_mutex wrapper (can
+ implement with EnterCriticalRegion eventually)
+ 1.88 portable threading API (only for win32 so far); worker thread queue
+ 1.87 fix wildcard handling in stb_readdir_recursive
+ 1.86 support ';' in wildcards
+ 1.85 make stb_regex work with non-constant strings;
+ beginnings of stb_introspect()
+ 1.84 (forgot to make notes)
+ 1.83 whoops, stb_keep_if_different wasn't deleting the temp file
+ 1.82 bring back stb_compress from stb_file.h for cmirror
+ 1.81 various bugfixes, STB_FASTMALLOC_INIT inits FASTMALLOC in release
+ 1.80 stb_readdir returns utf8; write own utf8-utf16 because lib was wrong
+ 1.79 stb_write
+ 1.78 calloc() support for malloc wrapper, STB_FASTMALLOC
+ 1.77 STB_FASTMALLOC
+ 1.76 STB_STUA - Lua-like language; (stb_image, stb_csample, stb_bilinear)
+ 1.75 alloc/free array of blocks; stb_hheap bug; a few stb_ps_ funcs;
+ hash*getkey, hash*copy; stb_bitset; stb_strnicmp; bugfix stb_bst
+ 1.74 stb_replaceinplace; use stdlib C function to convert utf8 to UTF-16
+ 1.73 fix performance bug & leak in stb_ischar (C++ port lost a 'static')
+ 1.72 remove stb_block, stb_block_manager, stb_decompress (to stb_file.h)
+ 1.71 stb_trimwhite, stb_tokens_nested, etc.
+ 1.70 back out 1.69 because it might problemize mixed builds; stb_filec()
+ 1.69 (stb_file returns 'char *' in C++)
+ 1.68 add a special 'tree root' data type for stb_bst; stb_arr_end
+ 1.67 full C++ port. (stb_block_manager)
+ 1.66 stb_newell_normal
+ 1.65 stb_lex_item_wild -- allow wildcard items which MUST match entirely
+ 1.64 stb_data
+ 1.63 stb_log_name
+ 1.62 stb_define_sort; C++ cleanup
+ 1.61 stb_hash_fast -- Paul Hsieh's hash function (beats Bob Jenkins'?)
+ 1.60 stb_delete_directory_recursive
+ 1.59 stb_readdir_recursive
+ 1.58 stb_bst variant with parent pointer for O(1) iteration, not O(log N)
+ 1.57 replace LCG random with Mersenne Twister (found a public domain one)
+ 1.56 stb_perfect_hash, stb_ischar, stb_regex
+ 1.55 new stb_bst API allows multiple BSTs per node (e.g. secondary keys)
+ 1.54 bugfix: stb_define_hash, stb_wildmatch, regexp
+ 1.53 stb_define_hash; recoded stb_extra, stb_sdict use it
+ 1.52 stb_rand_define, stb_bst, stb_reverse
+ 1.51 fix 'stb_arr_setlen(NULL, 0)'
+ 1.50 stb_wordwrap
+ 1.49 minor improvements to enable the scripting language
+ 1.48 better approach for stb_arr using stb_malloc; more invasive, clearer
+ 1.47 stb_lex (lexes stb.h at 1.5ML/s on 3Ghz P4; 60/70% of optimal/flex)
+ 1.46 stb_wrapper_*, STB_MALLOC_WRAPPER
+ 1.45 lightly tested DFA acceleration of regexp searching
+ 1.44 wildcard matching & searching; regexp matching & searching
+ 1.43 stb_temp
+ 1.42 allow stb_arr to use stb_malloc/realloc; note this is global
+ 1.41 make it compile in C++; (disable stb_arr in C++)
+ 1.40 stb_dupe tweak; stb_swap; stb_substr
+ 1.39 stb_dupe; improve stb_file_max to be less stupid
+ 1.38 stb_sha1_file: generate sha1 for file, even > 4GB
+ 1.37 stb_file_max; partial support for utf8 filenames in Windows
+ 1.36 remove STB__NO_PREFIX - poor interaction with IDE, not worth it
+ streamline stb_arr to make it separately publishable
+ 1.35 bugfixes for stb_sdict, stb_malloc(0), stristr
+ 1.34 (streaming interfaces for stb_compress)
+ 1.33 stb_alloc; bug in stb_getopt; remove stb_overflow
+ 1.32 (stb_compress returns, smaller&faster; encode window & 64-bit len)
+ 1.31 stb_prefix_count
+ 1.30 (STB__NO_PREFIX - remove stb_ prefixes for personal projects)
+ 1.29 stb_fput_varlen64, etc.
+ 1.28 stb_sha1
+ 1.27 ?
+ 1.26 stb_extra
+ 1.25 ?
+ 1.24 stb_copyfile
+ 1.23 stb_readdir
+ 1.22 ?
+ 1.21 ?
+ 1.20 ?
+ 1.19 ?
+ 1.18 ?
+ 1.17 ?
+ 1.16 ?
+ 1.15 stb_fixpath, stb_splitpath, stb_strchr2
+ 1.14 stb_arr
+ 1.13 ?stb, stb_log, stb_fatal
+ 1.12 ?stb_hash2
+ 1.11 miniML
+ 1.10 stb_crc32, stb_adler32
+ 1.09 stb_sdict
+ 1.08 stb_bitreverse, stb_ispow2, stb_big32
+ stb_fopen, stb_fput_varlen, stb_fput_ranged
+ stb_fcmp, stb_feq
+ 1.07 (stb_encompress)
+ 1.06 stb_compress
+ 1.05 stb_tokens, (stb_hheap)
+ 1.04 stb_rand
+ 1.03 ?(s-strings)
+ 1.02 ?stb_filelen, stb_tokens
+ 1.01 stb_tolower
+ 1.00 stb_hash, stb_intcmp
+ stb_file, stb_stringfile, stb_fgets
+ stb_prefix, stb_strlower, stb_strtok
+ stb_image
+ (stb_array), (stb_arena)
+
+Parenthesized items have since been removed.
+
+LICENSE
+
+ See end of file for license information.
+
+CREDITS
+
+ Written by Sean Barrett.
+
+ Fixes:
+ Philipp Wiesemann
+ Robert Nix
+ r-lyeh
+ blackpawn
+ github:Mojofreem
+ Ryan Whitworth
+ Vincent Isambart
+ Mike Sartain
+ Eugene Opalev
+ Tim Sjostrand
+ github:infatum
+ Dave Butler (Croepha)
+ Ethan Lee (flibitijibibo)
+ Brian Collins
+ Kyle Langley
+*/
+
+#include
+
+#ifndef STB__INCLUDE_STB_H
+#define STB__INCLUDE_STB_H
+
+#define STB_VERSION 1
+
+#ifdef STB_INTROSPECT
+ #define STB_DEFINE
+#endif
+
+#ifdef STB_DEFINE_THREADS
+ #ifndef STB_DEFINE
+ #define STB_DEFINE
+ #endif
+ #ifndef STB_THREADS
+ #define STB_THREADS
+ #endif
+#endif
+
+#if defined(_WIN32) && !defined(__MINGW32__)
+ #ifndef _CRT_SECURE_NO_WARNINGS
+ #define _CRT_SECURE_NO_WARNINGS
+ #endif
+ #ifndef _CRT_NONSTDC_NO_DEPRECATE
+ #define _CRT_NONSTDC_NO_DEPRECATE
+ #endif
+ #ifndef _CRT_NON_CONFORMING_SWPRINTFS
+ #define _CRT_NON_CONFORMING_SWPRINTFS
+ #endif
+ #if !defined(_MSC_VER) || _MSC_VER > 1700
+ #include // _BitScanReverse
+ #endif
+#endif
+
+#include // stdlib could have min/max
+#include // need FILE
+#include // stb_define_hash needs memcpy/memset
+#include // stb_dirtree
+#ifdef __MINGW32__
+ #include // O_RDWR
+#endif
+
+#ifdef STB_PERSONAL
+ typedef int Bool;
+ #define False 0
+ #define True 1
+#endif
+
+#ifdef STB_MALLOC_WRAPPER_PAGED
+ #define STB_MALLOC_WRAPPER_DEBUG
+#endif
+#ifdef STB_MALLOC_WRAPPER_DEBUG
+ #define STB_MALLOC_WRAPPER
+#endif
+#ifdef STB_MALLOC_WRAPPER_FASTMALLOC
+ #define STB_FASTMALLOC
+ #define STB_MALLOC_WRAPPER
+#endif
+
+#ifdef STB_FASTMALLOC
+ #ifndef _WIN32
+ #undef STB_FASTMALLOC
+ #endif
+#endif
+
+#ifdef STB_DEFINE
+ #include
+ #include
+ #include
+ #include
+ #include
+ #ifndef _WIN32
+ #include
+ #else
+ #include // _mktemp
+ #include // _rmdir
+ #endif
+ #include // stat()/_stat()
+ #include // stat()/_stat()
+#endif
+
+#define stb_min(a,b) ((a) < (b) ? (a) : (b))
+#define stb_max(a,b) ((a) > (b) ? (a) : (b))
+
+#ifndef STB_ONLY
+ #if !defined(__cplusplus) && !defined(min) && !defined(max)
+ #define min(x,y) stb_min(x,y)
+ #define max(x,y) stb_max(x,y)
+ #endif
+
+ #ifndef M_PI
+ #define M_PI 3.14159265358979323846f
+ #endif
+
+ #ifndef TRUE
+ #define TRUE 1
+ #define FALSE 0
+ #endif
+
+ #ifndef deg2rad
+ #define deg2rad(a) ((a)*(M_PI/180))
+ #endif
+ #ifndef rad2deg
+ #define rad2deg(a) ((a)*(180/M_PI))
+ #endif
+
+ #ifndef swap
+ #ifndef __cplusplus
+ #define swap(TYPE,a,b) \
+ do { TYPE stb__t; stb__t = (a); (a) = (b); (b) = stb__t; } while (0)
+ #endif
+ #endif
+
+ typedef unsigned char uint8 ;
+ typedef signed char int8 ;
+ typedef unsigned short uint16;
+ typedef signed short int16;
+ #if defined(STB_USE_LONG_FOR_32_BIT_INT) || defined(STB_LONG32)
+ typedef unsigned long uint32;
+ typedef signed long int32;
+ #else
+ typedef unsigned int uint32;
+ typedef signed int int32;
+ #endif
+
+ typedef unsigned char uchar ;
+ typedef unsigned short ushort;
+ typedef unsigned int uint ;
+ typedef unsigned long ulong ;
+
+ // produce compile errors if the sizes aren't right
+ typedef char stb__testsize16[sizeof(int16)==2];
+ typedef char stb__testsize32[sizeof(int32)==4];
+#endif
+
+#ifndef STB_TRUE
+ #define STB_TRUE 1
+ #define STB_FALSE 0
+#endif
+
+// if we're STB_ONLY, can't rely on uint32 or even uint, so all the
+// variables we'll use herein need typenames prefixed with 'stb':
+typedef unsigned char stb_uchar;
+typedef unsigned char stb_uint8;
+typedef unsigned int stb_uint;
+typedef unsigned short stb_uint16;
+typedef short stb_int16;
+typedef signed char stb_int8;
+#if defined(STB_USE_LONG_FOR_32_BIT_INT) || defined(STB_LONG32)
+ typedef unsigned long stb_uint32;
+ typedef long stb_int32;
+#else
+ typedef unsigned int stb_uint32;
+ typedef int stb_int32;
+#endif
+typedef char stb__testsize2_16[sizeof(stb_uint16)==2 ? 1 : -1];
+typedef char stb__testsize2_32[sizeof(stb_uint32)==4 ? 1 : -1];
+
+#ifdef _MSC_VER
+ typedef unsigned __int64 stb_uint64;
+ typedef __int64 stb_int64;
+ #define STB_IMM_UINT64(literalui64) (literalui64##ui64)
+ #define STB_IMM_INT64(literali64) (literali64##i64)
+#else
+ // ??
+ typedef unsigned long long stb_uint64;
+ typedef long long stb_int64;
+ #define STB_IMM_UINT64(literalui64) (literalui64##ULL)
+ #define STB_IMM_INT64(literali64) (literali64##LL)
+#endif
+typedef char stb__testsize2_64[sizeof(stb_uint64)==8 ? 1 : -1];
+
+// add platform-specific ways of checking for sizeof(char*) == 8,
+// and make those define STB_PTR64
+#if defined(_WIN64) || defined(__x86_64__) || defined(__ia64__) || defined(__LP64__)
+ #define STB_PTR64
+#endif
+
+#ifdef STB_PTR64
+typedef char stb__testsize2_ptr[sizeof(char *) == 8];
+typedef stb_uint64 stb_uinta;
+typedef stb_int64 stb_inta;
+#else
+typedef char stb__testsize2_ptr[sizeof(char *) == 4];
+typedef stb_uint32 stb_uinta;
+typedef stb_int32 stb_inta;
+#endif
+typedef char stb__testsize2_uinta[sizeof(stb_uinta)==sizeof(char*) ? 1 : -1];
+
+// if so, we should define an int type that is the pointer size. until then,
+// we'll have to make do with this (which is not the same at all!)
+
+typedef union
+{
+ unsigned int i;
+ void * p;
+} stb_uintptr;
+
+
+#ifdef __cplusplus
+ #define STB_EXTERN extern "C"
+#else
+ #define STB_EXTERN extern
+#endif
+
+// check for well-known debug defines
+#if defined(DEBUG) || defined(_DEBUG) || defined(DBG)
+ #ifndef NDEBUG
+ #define STB_DEBUG
+ #endif
+#endif
+
+#ifdef STB_DEBUG
+ #include
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// C library function platform handling
+//
+
+#ifdef STB_DEFINE
+
+#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__)
+static FILE * stb_p_fopen(const char *filename, const char *mode)
+{
+ FILE *f;
+ if (0 == fopen_s(&f, filename, mode))
+ return f;
+ else
+ return NULL;
+}
+static FILE * stb_p_wfopen(const wchar_t *filename, const wchar_t *mode)
+{
+ FILE *f;
+ if (0 == _wfopen_s(&f, filename, mode))
+ return f;
+ else
+ return NULL;
+}
+static char *stb_p_strcpy_s(char *a, size_t size, const char *b)
+{
+ strcpy_s(a,size,b);
+ return a;
+}
+static char *stb_p_strncpy_s(char *a, size_t size, const char *b, size_t count)
+{
+ strncpy_s(a,size,b,count);
+ return a;
+}
+#define stb_p_mktemp(s) (_mktemp_s(s, strlen(s)+1) == 0)
+#define stb_p_sprintf sprintf_s
+#define stb_p_size(x) ,(x)
+#else
+#define stb_p_fopen fopen
+#define stb_p_wfopen _wfopen
+#define stb_p_strcpy_s(a,s,b) strcpy(a,b)
+#define stb_p_strncpy_s(a,s,b,c) strncpy(a,b,c)
+#define stb_p_mktemp(s) (mktemp(s) != NULL)
+
+#define stb_p_sprintf sprintf
+#define stb_p_size(x)
+#endif
+
+#if defined(_WIN32)
+#define stb_p_vsnprintf _vsnprintf
+#else
+#define stb_p_vsnprintf vsnprintf
+#endif
+#endif // STB_DEFINE
+
+#if defined(_WIN32) && (_MSC_VER >= 1300)
+#define stb_p_stricmp _stricmp
+#define stb_p_strnicmp _strnicmp
+#define stb_p_strdup _strdup
+#else
+#define stb_p_strdup strdup
+#define stb_p_stricmp stricmp
+#define stb_p_strnicmp strnicmp
+#endif
+
+STB_EXTERN void stb_wrapper_malloc(void *newp, size_t sz, char *file, int line);
+STB_EXTERN void stb_wrapper_free(void *oldp, char *file, int line);
+STB_EXTERN void stb_wrapper_realloc(void *oldp, void *newp, size_t sz, char *file, int line);
+STB_EXTERN void stb_wrapper_calloc(size_t num, size_t sz, char *file, int line);
+STB_EXTERN void stb_wrapper_listall(void (*func)(void *ptr, size_t sz, char *file, int line));
+STB_EXTERN void stb_wrapper_dump(char *filename);
+STB_EXTERN size_t stb_wrapper_allocsize(void *oldp);
+STB_EXTERN void stb_wrapper_check(void *oldp);
+
+#ifdef STB_DEFINE
+// this is a special function used inside malloc wrapper
+// to do allocations that aren't tracked (to avoid
+// reentrancy). Of course if someone _else_ wraps realloc,
+// this breaks, but if they're doing that AND the malloc
+// wrapper they need to explicitly check for reentrancy.
+//
+// only define realloc_raw() and we do realloc(NULL,sz)
+// for malloc() and realloc(p,0) for free().
+static void * stb__realloc_raw(void *p, int sz)
+{
+ if (p == NULL) return malloc(sz);
+ if (sz == 0) { free(p); return NULL; }
+ return realloc(p,sz);
+}
+#endif
+
+#ifdef _WIN32
+STB_EXTERN void * stb_smalloc(size_t sz);
+STB_EXTERN void stb_sfree(void *p);
+STB_EXTERN void * stb_srealloc(void *p, size_t sz);
+STB_EXTERN void * stb_scalloc(size_t n, size_t sz);
+STB_EXTERN char * stb_sstrdup(char *s);
+#endif
+
+#ifdef STB_FASTMALLOC
+#define malloc stb_smalloc
+#define free stb_sfree
+#define realloc stb_srealloc
+#define strdup stb_sstrdup
+#define calloc stb_scalloc
+#endif
+
+#ifndef STB_MALLOC_ALLCHECK
+ #define stb__check(p) 1
+#else
+ #ifndef STB_MALLOC_WRAPPER
+ #error STB_MALLOC_ALLCHECK requires STB_MALLOC_WRAPPER
+ #else
+ #define stb__check(p) stb_mcheck(p)
+ #endif
+#endif
+
+#ifdef STB_MALLOC_WRAPPER
+ STB_EXTERN void * stb__malloc(size_t, char *, int);
+ STB_EXTERN void * stb__realloc(void *, size_t, char *, int);
+ STB_EXTERN void * stb__calloc(size_t n, size_t s, char *, int);
+ STB_EXTERN void stb__free(void *, char *file, int);
+ STB_EXTERN char * stb__strdup(char *s, char *file, int);
+ STB_EXTERN void stb_malloc_checkall(void);
+ STB_EXTERN void stb_malloc_check_counter(int init_delay, int rep_delay);
+ #ifndef STB_MALLOC_WRAPPER_DEBUG
+ #define stb_mcheck(p) 1
+ #else
+ STB_EXTERN int stb_mcheck(void *);
+ #endif
+
+
+ #ifdef STB_DEFINE
+
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ #define STB__PAD 32
+ #define STB__BIAS 16
+ #define STB__SIG 0x51b01234
+ #define STB__FIXSIZE(sz) (((sz+3) & ~3) + STB__PAD)
+ #define STB__ptr(x,y) ((char *) (x) + (y))
+ #else
+ #define STB__ptr(x,y) (x)
+ #define STB__FIXSIZE(sz) (sz)
+ #endif
+
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ int stb_mcheck(void *p)
+ {
+ unsigned int sz;
+ if (p == NULL) return 1;
+ p = ((char *) p) - STB__BIAS;
+ sz = * (unsigned int *) p;
+ assert(* (unsigned int *) STB__ptr(p,4) == STB__SIG);
+ assert(* (unsigned int *) STB__ptr(p,8) == STB__SIG);
+ assert(* (unsigned int *) STB__ptr(p,12) == STB__SIG);
+ assert(* (unsigned int *) STB__ptr(p,sz-4) == STB__SIG+1);
+ assert(* (unsigned int *) STB__ptr(p,sz-8) == STB__SIG+1);
+ assert(* (unsigned int *) STB__ptr(p,sz-12) == STB__SIG+1);
+ assert(* (unsigned int *) STB__ptr(p,sz-16) == STB__SIG+1);
+ stb_wrapper_check(STB__ptr(p, STB__BIAS));
+ return 1;
+ }
+
+ static void stb__check2(void *p, size_t sz, char *file, int line)
+ {
+ stb_mcheck(p);
+ }
+
+ void stb_malloc_checkall(void)
+ {
+ stb_wrapper_listall(stb__check2);
+ }
+ #else
+ void stb_malloc_checkall(void) { }
+ #endif
+
+ static int stb__malloc_wait=(1 << 30), stb__malloc_next_wait = (1 << 30), stb__malloc_iter;
+ void stb_malloc_check_counter(int init_delay, int rep_delay)
+ {
+ stb__malloc_wait = init_delay;
+ stb__malloc_next_wait = rep_delay;
+ }
+
+ void stb_mcheck_all(void)
+ {
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ ++stb__malloc_iter;
+ if (--stb__malloc_wait <= 0) {
+ stb_malloc_checkall();
+ stb__malloc_wait = stb__malloc_next_wait;
+ }
+ #endif
+ }
+
+ #ifdef STB_MALLOC_WRAPPER_PAGED
+ #define STB__WINDOWS_PAGE (1 << 12)
+ #ifndef _WINDOWS_
+ STB_EXTERN __declspec(dllimport) void * __stdcall VirtualAlloc(void *p, unsigned long size, unsigned long type, unsigned long protect);
+ STB_EXTERN __declspec(dllimport) int __stdcall VirtualFree(void *p, unsigned long size, unsigned long freetype);
+ #endif
+ #endif
+
+ static void *stb__malloc_final(size_t sz)
+ {
+ #ifdef STB_MALLOC_WRAPPER_PAGED
+ size_t aligned = (sz + STB__WINDOWS_PAGE - 1) & ~(STB__WINDOWS_PAGE-1);
+ char *p = VirtualAlloc(NULL, aligned + STB__WINDOWS_PAGE, 0x2000, 0x04); // RESERVE, READWRITE
+ if (p == NULL) return p;
+ VirtualAlloc(p, aligned, 0x1000, 0x04); // COMMIT, READWRITE
+ return p;
+ #else
+ return malloc(sz);
+ #endif
+ }
+
+ static void stb__free_final(void *p)
+ {
+ #ifdef STB_MALLOC_WRAPPER_PAGED
+ VirtualFree(p, 0, 0x8000); // RELEASE
+ #else
+ free(p);
+ #endif
+ }
+
+ int stb__malloc_failure;
+ #ifdef STB_MALLOC_WRAPPER_PAGED
+ static void *stb__realloc_final(void *p, size_t sz, size_t old_sz)
+ {
+ void *q = stb__malloc_final(sz);
+ if (q == NULL)
+ return ++stb__malloc_failure, q;
+ // @TODO: deal with p being smaller!
+ memcpy(q, p, sz < old_sz ? sz : old_sz);
+ stb__free_final(p);
+ return q;
+ }
+ #endif
+
+ void stb__free(void *p, char *file, int line)
+ {
+ stb_mcheck_all();
+ if (!p) return;
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ stb_mcheck(p);
+ #endif
+ stb_wrapper_free(p,file,line);
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ p = STB__ptr(p,-STB__BIAS);
+ * (unsigned int *) STB__ptr(p,0) = 0xdeadbeef;
+ * (unsigned int *) STB__ptr(p,4) = 0xdeadbeef;
+ * (unsigned int *) STB__ptr(p,8) = 0xdeadbeef;
+ * (unsigned int *) STB__ptr(p,12) = 0xdeadbeef;
+ #endif
+ stb__free_final(p);
+ }
+
+ void * stb__malloc(size_t sz, char *file, int line)
+ {
+ void *p;
+ stb_mcheck_all();
+ if (sz == 0) return NULL;
+ p = stb__malloc_final(STB__FIXSIZE(sz));
+ if (p == NULL) p = stb__malloc_final(STB__FIXSIZE(sz));
+ if (p == NULL) p = stb__malloc_final(STB__FIXSIZE(sz));
+ if (p == NULL) {
+ ++stb__malloc_failure;
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ stb_malloc_checkall();
+ #endif
+ return p;
+ }
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ * (int *) STB__ptr(p,0) = STB__FIXSIZE(sz);
+ * (unsigned int *) STB__ptr(p,4) = STB__SIG;
+ * (unsigned int *) STB__ptr(p,8) = STB__SIG;
+ * (unsigned int *) STB__ptr(p,12) = STB__SIG;
+ * (unsigned int *) STB__ptr(p,STB__FIXSIZE(sz)-4) = STB__SIG+1;
+ * (unsigned int *) STB__ptr(p,STB__FIXSIZE(sz)-8) = STB__SIG+1;
+ * (unsigned int *) STB__ptr(p,STB__FIXSIZE(sz)-12) = STB__SIG+1;
+ * (unsigned int *) STB__ptr(p,STB__FIXSIZE(sz)-16) = STB__SIG+1;
+ p = STB__ptr(p, STB__BIAS);
+ #endif
+ stb_wrapper_malloc(p,sz,file,line);
+ return p;
+ }
+
+ void * stb__realloc(void *p, size_t sz, char *file, int line)
+ {
+ void *q;
+
+ stb_mcheck_all();
+ if (p == NULL) return stb__malloc(sz,file,line);
+ if (sz == 0 ) { stb__free(p,file,line); return NULL; }
+
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ stb_mcheck(p);
+ p = STB__ptr(p,-STB__BIAS);
+ #endif
+ #ifdef STB_MALLOC_WRAPPER_PAGED
+ {
+ size_t n = stb_wrapper_allocsize(STB__ptr(p,STB__BIAS));
+ if (!n)
+ stb_wrapper_check(STB__ptr(p,STB__BIAS));
+ q = stb__realloc_final(p, STB__FIXSIZE(sz), STB__FIXSIZE(n));
+ }
+ #else
+ q = realloc(p, STB__FIXSIZE(sz));
+ #endif
+ if (q == NULL)
+ return ++stb__malloc_failure, q;
+ #ifdef STB_MALLOC_WRAPPER_DEBUG
+ * (int *) STB__ptr(q,0) = STB__FIXSIZE(sz);
+ * (unsigned int *) STB__ptr(q,4) = STB__SIG;
+ * (unsigned int *) STB__ptr(q,8) = STB__SIG;
+ * (unsigned int *) STB__ptr(q,12) = STB__SIG;
+ * (unsigned int *) STB__ptr(q,STB__FIXSIZE(sz)-4) = STB__SIG+1;
+ * (unsigned int *) STB__ptr(q,STB__FIXSIZE(sz)-8) = STB__SIG+1;
+ * (unsigned int *) STB__ptr(q,STB__FIXSIZE(sz)-12) = STB__SIG+1;
+ * (unsigned int *) STB__ptr(q,STB__FIXSIZE(sz)-16) = STB__SIG+1;
+
+ q = STB__ptr(q, STB__BIAS);
+ p = STB__ptr(p, STB__BIAS);
+ #endif
+ stb_wrapper_realloc(p,q,sz,file,line);
+ return q;
+ }
+
+ STB_EXTERN int stb_log2_ceil(size_t);
+ static void *stb__calloc(size_t n, size_t sz, char *file, int line)
+ {
+ void *q;
+ stb_mcheck_all();
+ if (n == 0 || sz == 0) return NULL;
+ if (stb_log2_ceil(n) + stb_log2_ceil(sz) >= 32) return NULL;
+ q = stb__malloc(n*sz, file, line);
+ if (q) memset(q, 0, n*sz);
+ return q;
+ }
+
+ char * stb__strdup(char *s, char *file, int line)
+ {
+ char *p;
+ stb_mcheck_all();
+ p = stb__malloc(strlen(s)+1, file, line);
+ if (!p) return p;
+ stb_p_strcpy_s(p, strlen(s)+1, s);
+ return p;
+ }
+ #endif // STB_DEFINE
+
+ #ifdef STB_FASTMALLOC
+ #undef malloc
+ #undef realloc
+ #undef free
+ #undef strdup
+ #undef calloc
+ #endif
+
+ // include everything that might define these, BEFORE making macros
+ #include
+ #include
+ #include
+
+ #define malloc(s) stb__malloc ( s, __FILE__, __LINE__)
+ #define realloc(p,s) stb__realloc(p,s, __FILE__, __LINE__)
+ #define calloc(n,s) stb__calloc (n,s, __FILE__, __LINE__)
+ #define free(p) stb__free (p, __FILE__, __LINE__)
+ #define strdup(p) stb__strdup (p, __FILE__, __LINE__)
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Windows pretty display
+//
+
+STB_EXTERN void stbprint(const char *fmt, ...);
+STB_EXTERN char *stb_sprintf(const char *fmt, ...);
+STB_EXTERN char *stb_mprintf(const char *fmt, ...);
+STB_EXTERN int stb_snprintf(char *s, size_t n, const char *fmt, ...);
+STB_EXTERN int stb_vsnprintf(char *s, size_t n, const char *fmt, va_list v);
+
+#ifdef STB_DEFINE
+int stb_vsnprintf(char *s, size_t n, const char *fmt, va_list v)
+{
+ int res;
+ #ifdef _WIN32
+ #ifdef __STDC_WANT_SECURE_LIB__
+ res = _vsnprintf_s(s, n, _TRUNCATE, fmt, v);
+ #else
+ res = stb_p_vsnprintf(s,n,fmt,v);
+ #endif
+ #else
+ res = vsnprintf(s,n,fmt,v);
+ #endif
+ if (n) s[n-1] = 0;
+ // Unix returns length output would require, Windows returns negative when truncated.
+ return (res >= (int) n || res < 0) ? -1 : res;
+}
+
+int stb_snprintf(char *s, size_t n, const char *fmt, ...)
+{
+ int res;
+ va_list v;
+ va_start(v,fmt);
+ res = stb_vsnprintf(s, n, fmt, v);
+ va_end(v);
+ return res;
+}
+
+char *stb_sprintf(const char *fmt, ...)
+{
+ static char buffer[1024];
+ va_list v;
+ va_start(v,fmt);
+ stb_vsnprintf(buffer,1024,fmt,v);
+ va_end(v);
+ return buffer;
+}
+
+char *stb_mprintf(const char *fmt, ...)
+{
+ static char buffer[1024];
+ va_list v;
+ va_start(v,fmt);
+ stb_vsnprintf(buffer,1024,fmt,v);
+ va_end(v);
+ return stb_p_strdup(buffer);
+}
+
+#ifdef _WIN32
+
+#ifndef _WINDOWS_
+STB_EXTERN __declspec(dllimport) int __stdcall WriteConsoleA(void *, const void *, unsigned int, unsigned int *, void *);
+STB_EXTERN __declspec(dllimport) void * __stdcall GetStdHandle(unsigned int);
+STB_EXTERN __declspec(dllimport) int __stdcall SetConsoleTextAttribute(void *, unsigned short);
+#endif
+
+static void stb__print_one(void *handle, char *s, ptrdiff_t len)
+{
+ if (len)
+ if (0==WriteConsoleA(handle, s, (unsigned) len, NULL,NULL))
+ // if it fails, maybe redirected, so output normally...
+ // but it's supriously reporting failure now on Win7 and later
+ {}//fwrite(s, 1, (unsigned) len, stdout);
+}
+
+static void stb__print(char *s)
+{
+ void *handle = GetStdHandle((unsigned int) -11); // STD_OUTPUT_HANDLE
+ int pad=0; // number of padding characters to add
+
+ char *t = s;
+ while (*s) {
+ int lpad;
+ while (*s && *s != '{') {
+ if (pad) {
+ if (*s == '\r' || *s == '\n')
+ pad = 0;
+ else if (s[0] == ' ' && s[1] == ' ') {
+ stb__print_one(handle, t, s-t);
+ t = s;
+ while (pad) {
+ stb__print_one(handle, t, 1);
+ --pad;
+ }
+ }
+ }
+ ++s;
+ }
+ if (!*s) break;
+ stb__print_one(handle, t, s-t);
+ if (s[1] == '{') {
+ ++s;
+ continue;
+ }
+
+ if (s[1] == '#') {
+ t = s+3;
+ if (isxdigit(s[2]))
+ if (isdigit(s[2]))
+ SetConsoleTextAttribute(handle, s[2] - '0');
+ else
+ SetConsoleTextAttribute(handle, tolower(s[2]) - 'a' + 10);
+ else {
+ SetConsoleTextAttribute(handle, 0x0f);
+ t=s+2;
+ }
+ } else if (s[1] == '!') {
+ SetConsoleTextAttribute(handle, 0x0c);
+ t = s+2;
+ } else if (s[1] == '@') {
+ SetConsoleTextAttribute(handle, 0x09);
+ t = s+2;
+ } else if (s[1] == '$') {
+ SetConsoleTextAttribute(handle, 0x0a);
+ t = s+2;
+ } else {
+ SetConsoleTextAttribute(handle, 0x08); // 0,7,8,15 => shades of grey
+ t = s+1;
+ }
+
+ lpad = (int) (t-s);
+ s = t;
+ while (*s && *s != '}') ++s;
+ if (!*s) break;
+ stb__print_one(handle, t, s-t);
+ if (s[1] == '}') {
+ t = s+2;
+ } else {
+ pad += 1+lpad;
+ t = s+1;
+ }
+ s=t;
+ SetConsoleTextAttribute(handle, 0x07);
+ }
+ stb__print_one(handle, t, s-t);
+ SetConsoleTextAttribute(handle, 0x07);
+}
+
+void stbprint(const char *fmt, ...)
+{
+ int res;
+ char buffer[1024];
+ char *tbuf = buffer;
+ va_list v;
+
+ va_start(v,fmt);
+ res = stb_vsnprintf(buffer, sizeof(buffer), fmt, v);
+ va_end(v);
+
+ if (res < 0) {
+ tbuf = (char *) malloc(16384);
+ va_start(v,fmt);
+ res = stb_vsnprintf(tbuf,16384, fmt, v);
+ va_end(v);
+ tbuf[16383] = 0;
+ }
+
+ stb__print(tbuf);
+
+ if (tbuf != buffer)
+ free(tbuf);
+}
+
+#else // _WIN32
+void stbprint(const char *fmt, ...)
+{
+ va_list v;
+ va_start(v,fmt);
+ vprintf(fmt,v);
+ va_end(v);
+}
+#endif // _WIN32
+#endif // STB_DEFINE
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Windows UTF8 filename handling
+//
+// Windows stupidly treats 8-bit filenames as some dopey code page,
+// rather than utf-8. If we want to use utf8 filenames, we have to
+// convert them to WCHAR explicitly and call WCHAR versions of the
+// file functions. So, ok, we do.
+
+
+#ifdef _WIN32
+ #define stb__fopen(x,y) stb_p_wfopen((const wchar_t *)stb__from_utf8(x), (const wchar_t *)stb__from_utf8_alt(y))
+ #define stb__windows(x,y) x
+#else
+ #define stb__fopen(x,y) stb_p_fopen(x,y)
+ #define stb__windows(x,y) y
+#endif
+
+
+typedef unsigned short stb__wchar;
+
+STB_EXTERN stb__wchar * stb_from_utf8(stb__wchar *buffer, const char *str, int n);
+STB_EXTERN char * stb_to_utf8 (char *buffer, const stb__wchar *str, int n);
+
+STB_EXTERN stb__wchar *stb__from_utf8(const char *str);
+STB_EXTERN stb__wchar *stb__from_utf8_alt(const char *str);
+STB_EXTERN char *stb__to_utf8(const stb__wchar *str);
+
+
+#ifdef STB_DEFINE
+stb__wchar * stb_from_utf8(stb__wchar *buffer, const char *ostr, int n)
+{
+ unsigned char *str = (unsigned char *) ostr;
+ stb_uint32 c;
+ int i=0;
+ --n;
+ while (*str) {
+ if (i >= n)
+ return NULL;
+ if (!(*str & 0x80))
+ buffer[i++] = *str++;
+ else if ((*str & 0xe0) == 0xc0) {
+ if (*str < 0xc2) return NULL;
+ c = (*str++ & 0x1f) << 6;
+ if ((*str & 0xc0) != 0x80) return NULL;
+ buffer[i++] = c + (*str++ & 0x3f);
+ } else if ((*str & 0xf0) == 0xe0) {
+ if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return NULL;
+ if (*str == 0xed && str[1] > 0x9f) return NULL; // str[1] < 0x80 is checked below
+ c = (*str++ & 0x0f) << 12;
+ if ((*str & 0xc0) != 0x80) return NULL;
+ c += (*str++ & 0x3f) << 6;
+ if ((*str & 0xc0) != 0x80) return NULL;
+ buffer[i++] = c + (*str++ & 0x3f);
+ } else if ((*str & 0xf8) == 0xf0) {
+ if (*str > 0xf4) return NULL;
+ if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return NULL;
+ if (*str == 0xf4 && str[1] > 0x8f) return NULL; // str[1] < 0x80 is checked below
+ c = (*str++ & 0x07) << 18;
+ if ((*str & 0xc0) != 0x80) return NULL;
+ c += (*str++ & 0x3f) << 12;
+ if ((*str & 0xc0) != 0x80) return NULL;
+ c += (*str++ & 0x3f) << 6;
+ if ((*str & 0xc0) != 0x80) return NULL;
+ c += (*str++ & 0x3f);
+ // utf-8 encodings of values used in surrogate pairs are invalid
+ if ((c & 0xFFFFF800) == 0xD800) return NULL;
+ if (c >= 0x10000) {
+ c -= 0x10000;
+ if (i + 2 > n) return NULL;
+ buffer[i++] = 0xD800 | (0x3ff & (c >> 10));
+ buffer[i++] = 0xDC00 | (0x3ff & (c ));
+ }
+ } else
+ return NULL;
+ }
+ buffer[i] = 0;
+ return buffer;
+}
+
+char * stb_to_utf8(char *buffer, const stb__wchar *str, int n)
+{
+ int i=0;
+ --n;
+ while (*str) {
+ if (*str < 0x80) {
+ if (i+1 > n) return NULL;
+ buffer[i++] = (char) *str++;
+ } else if (*str < 0x800) {
+ if (i+2 > n) return NULL;
+ buffer[i++] = 0xc0 + (*str >> 6);
+ buffer[i++] = 0x80 + (*str & 0x3f);
+ str += 1;
+ } else if (*str >= 0xd800 && *str < 0xdc00) {
+ stb_uint32 c;
+ if (i+4 > n) return NULL;
+ c = ((str[0] - 0xd800) << 10) + ((str[1]) - 0xdc00) + 0x10000;
+ buffer[i++] = 0xf0 + (c >> 18);
+ buffer[i++] = 0x80 + ((c >> 12) & 0x3f);
+ buffer[i++] = 0x80 + ((c >> 6) & 0x3f);
+ buffer[i++] = 0x80 + ((c ) & 0x3f);
+ str += 2;
+ } else if (*str >= 0xdc00 && *str < 0xe000) {
+ return NULL;
+ } else {
+ if (i+3 > n) return NULL;
+ buffer[i++] = 0xe0 + (*str >> 12);
+ buffer[i++] = 0x80 + ((*str >> 6) & 0x3f);
+ buffer[i++] = 0x80 + ((*str ) & 0x3f);
+ str += 1;
+ }
+ }
+ buffer[i] = 0;
+ return buffer;
+}
+
+stb__wchar *stb__from_utf8(const char *str)
+{
+ static stb__wchar buffer[4096];
+ return stb_from_utf8(buffer, str, 4096);
+}
+
+stb__wchar *stb__from_utf8_alt(const char *str)
+{
+ static stb__wchar buffer[4096];
+ return stb_from_utf8(buffer, str, 4096);
+}
+
+char *stb__to_utf8(const stb__wchar *str)
+{
+ static char buffer[4096];
+ return stb_to_utf8(buffer, str, 4096);
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Miscellany
+//
+
+STB_EXTERN void stb_fatal(const char *fmt, ...);
+STB_EXTERN void stb_(char *fmt, ...);
+STB_EXTERN void stb_append_to_file(char *file, char *fmt, ...);
+STB_EXTERN void stb_log(int active);
+STB_EXTERN void stb_log_fileline(int active);
+STB_EXTERN void stb_log_name(char *filename);
+
+STB_EXTERN void stb_swap(void *p, void *q, size_t sz);
+STB_EXTERN void *stb_copy(void *p, size_t sz);
+STB_EXTERN void stb_pointer_array_free(void *p, int len);
+STB_EXTERN void **stb_array_block_alloc(int count, int blocksize);
+
+#define stb_arrcount(x) (sizeof(x)/sizeof((x)[0]))
+
+
+STB_EXTERN int stb__record_fileline(const char *f, int n);
+
+#ifdef STB_DEFINE
+
+static char *stb__file;
+static int stb__line;
+
+int stb__record_fileline(const char *f, int n)
+{
+ stb__file = (char*) f;
+ stb__line = n;
+ return 0;
+}
+
+void stb_fatal(const char *s, ...)
+{
+ va_list a;
+ if (stb__file)
+ fprintf(stderr, "[%s:%d] ", stb__file, stb__line);
+ va_start(a,s);
+ fputs("Fatal error: ", stderr);
+ vfprintf(stderr, s, a);
+ va_end(a);
+ fputs("\n", stderr);
+ #ifdef STB_DEBUG
+ #ifdef _MSC_VER
+ #ifndef STB_PTR64
+ __asm int 3; // trap to debugger!
+ #else
+ __debugbreak();
+ #endif
+ #else
+ __builtin_trap();
+ #endif
+ #endif
+ exit(1);
+}
+
+static int stb__log_active=1, stb__log_fileline=1;
+
+void stb_log(int active)
+{
+ stb__log_active = active;
+}
+
+void stb_log_fileline(int active)
+{
+ stb__log_fileline = active;
+}
+
+#ifdef STB_NO_STB_STRINGS
+const char *stb__log_filename = "temp.log";
+#else
+const char *stb__log_filename = "stb.log";
+#endif
+
+void stb_log_name(char *s)
+{
+ stb__log_filename = s;
+}
+
+void stb_(char *s, ...)
+{
+ if (stb__log_active) {
+ FILE *f = stb_p_fopen(stb__log_filename, "a");
+ if (f) {
+ va_list a;
+ if (stb__log_fileline && stb__file)
+ fprintf(f, "[%s:%4d] ", stb__file, stb__line);
+ va_start(a,s);
+ vfprintf(f, s, a);
+ va_end(a);
+ fputs("\n", f);
+ fclose(f);
+ }
+ }
+}
+
+void stb_append_to_file(char *filename, char *s, ...)
+{
+ FILE *f = stb_p_fopen(filename, "a");
+ if (f) {
+ va_list a;
+ va_start(a,s);
+ vfprintf(f, s, a);
+ va_end(a);
+ fputs("\n", f);
+ fclose(f);
+ }
+}
+
+
+typedef struct { char d[4]; } stb__4;
+typedef struct { char d[8]; } stb__8;
+
+// optimize the small cases, though you shouldn't be calling this for those!
+void stb_swap(void *p, void *q, size_t sz)
+{
+ char buffer[256];
+ if (p == q) return;
+ if (sz == 4) {
+ stb__4 temp = * ( stb__4 *) p;
+ * (stb__4 *) p = * ( stb__4 *) q;
+ * (stb__4 *) q = temp;
+ return;
+ } else if (sz == 8) {
+ stb__8 temp = * ( stb__8 *) p;
+ * (stb__8 *) p = * ( stb__8 *) q;
+ * (stb__8 *) q = temp;
+ return;
+ }
+
+ while (sz > sizeof(buffer)) {
+ stb_swap(p, q, sizeof(buffer));
+ p = (char *) p + sizeof(buffer);
+ q = (char *) q + sizeof(buffer);
+ sz -= sizeof(buffer);
+ }
+
+ memcpy(buffer, p , sz);
+ memcpy(p , q , sz);
+ memcpy(q , buffer, sz);
+}
+
+void *stb_copy(void *p, size_t sz)
+{
+ void *q = malloc(sz);
+ memcpy(q, p, sz);
+ return q;
+}
+
+void stb_pointer_array_free(void *q, int len)
+{
+ void **p = (void **) q;
+ int i;
+ for (i=0; i < len; ++i)
+ free(p[i]);
+}
+
+void **stb_array_block_alloc(int count, int blocksize)
+{
+ int i;
+ char *p = (char *) malloc(sizeof(void *) * count + count * blocksize);
+ void **q;
+ if (p == NULL) return NULL;
+ q = (void **) p;
+ p += sizeof(void *) * count;
+ for (i=0; i < count; ++i)
+ q[i] = p + i * blocksize;
+ return q;
+}
+#endif
+
+#ifdef STB_DEBUG
+ // tricky hack to allow recording FILE,LINE even in varargs functions
+ #define STB__RECORD_FILE(x) (stb__record_fileline(__FILE__, __LINE__),(x))
+ #define stb_log STB__RECORD_FILE(stb_log)
+ #define stb_ STB__RECORD_FILE(stb_)
+ #ifndef STB_FATAL_CLEAN
+ #define stb_fatal STB__RECORD_FILE(stb_fatal)
+ #endif
+ #define STB__DEBUG(x) x
+#else
+ #define STB__DEBUG(x)
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_temp
+//
+
+#define stb_temp(block, sz) stb__temp(block, sizeof(block), (sz))
+
+STB_EXTERN void * stb__temp(void *b, int b_sz, int want_sz);
+STB_EXTERN void stb_tempfree(void *block, void *ptr);
+
+#ifdef STB_DEFINE
+
+void * stb__temp(void *b, int b_sz, int want_sz)
+{
+ if (b_sz >= want_sz)
+ return b;
+ else
+ return malloc(want_sz);
+}
+
+void stb_tempfree(void *b, void *p)
+{
+ if (p != b)
+ free(p);
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// math/sampling operations
+//
+
+
+#define stb_lerp(t,a,b) ( (a) + (t) * (float) ((b)-(a)) )
+#define stb_unlerp(t,a,b) ( ((t) - (a)) / (float) ((b) - (a)) )
+
+#define stb_clamp(x,xmin,xmax) ((x) < (xmin) ? (xmin) : (x) > (xmax) ? (xmax) : (x))
+
+STB_EXTERN void stb_newell_normal(float *normal, int num_vert, float **vert, int normalize);
+STB_EXTERN int stb_box_face_vertex_axis_side(int face_number, int vertex_number, int axis);
+STB_EXTERN void stb_linear_controller(float *curpos, float target_pos, float acc, float deacc, float dt);
+
+STB_EXTERN int stb_float_eq(float x, float y, float delta, int max_ulps);
+STB_EXTERN int stb_is_prime(unsigned int m);
+STB_EXTERN unsigned int stb_power_of_two_nearest_prime(int n);
+
+STB_EXTERN float stb_smoothstep(float t);
+STB_EXTERN float stb_cubic_bezier_1d(float t, float p0, float p1, float p2, float p3);
+
+STB_EXTERN double stb_linear_remap(double x, double a, double b,
+ double c, double d);
+
+#ifdef STB_DEFINE
+float stb_smoothstep(float t)
+{
+ return (3 - 2*t)*(t*t);
+}
+
+float stb_cubic_bezier_1d(float t, float p0, float p1, float p2, float p3)
+{
+ float it = 1-t;
+ return it*it*it*p0 + 3*it*it*t*p1 + 3*it*t*t*p2 + t*t*t*p3;
+}
+
+void stb_newell_normal(float *normal, int num_vert, float **vert, int normalize)
+{
+ int i,j;
+ float p;
+ normal[0] = normal[1] = normal[2] = 0;
+ for (i=num_vert-1,j=0; j < num_vert; i=j++) {
+ float *u = vert[i];
+ float *v = vert[j];
+ normal[0] += (u[1] - v[1]) * (u[2] + v[2]);
+ normal[1] += (u[2] - v[2]) * (u[0] + v[0]);
+ normal[2] += (u[0] - v[0]) * (u[1] + v[1]);
+ }
+ if (normalize) {
+ p = normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2];
+ p = (float) (1.0 / sqrt(p));
+ normal[0] *= p;
+ normal[1] *= p;
+ normal[2] *= p;
+ }
+}
+
+int stb_box_face_vertex_axis_side(int face_number, int vertex_number, int axis)
+{
+ static int box_vertices[6][4][3] =
+ {
+ { { 1,1,1 }, { 1,0,1 }, { 1,0,0 }, { 1,1,0 } },
+ { { 0,0,0 }, { 0,0,1 }, { 0,1,1 }, { 0,1,0 } },
+ { { 0,0,0 }, { 0,1,0 }, { 1,1,0 }, { 1,0,0 } },
+ { { 0,0,0 }, { 1,0,0 }, { 1,0,1 }, { 0,0,1 } },
+ { { 1,1,1 }, { 0,1,1 }, { 0,0,1 }, { 1,0,1 } },
+ { { 1,1,1 }, { 1,1,0 }, { 0,1,0 }, { 0,1,1 } },
+ };
+ assert(face_number >= 0 && face_number < 6);
+ assert(vertex_number >= 0 && vertex_number < 4);
+ assert(axis >= 0 && axis < 3);
+ return box_vertices[face_number][vertex_number][axis];
+}
+
+void stb_linear_controller(float *curpos, float target_pos, float acc, float deacc, float dt)
+{
+ float sign = 1, p, cp = *curpos;
+ if (cp == target_pos) return;
+ if (target_pos < cp) {
+ target_pos = -target_pos;
+ cp = -cp;
+ sign = -1;
+ }
+ // first decelerate
+ if (cp < 0) {
+ p = cp + deacc * dt;
+ if (p > 0) {
+ p = 0;
+ dt = dt - cp / deacc;
+ if (dt < 0) dt = 0;
+ } else {
+ dt = 0;
+ }
+ cp = p;
+ }
+ // now accelerate
+ p = cp + acc*dt;
+ if (p > target_pos) p = target_pos;
+ *curpos = p * sign;
+ // @TODO: testing
+}
+
+float stb_quadratic_controller(float target_pos, float curpos, float maxvel, float maxacc, float dt, float *curvel)
+{
+ return 0; // @TODO
+}
+
+int stb_float_eq(float x, float y, float delta, int max_ulps)
+{
+ if (fabs(x-y) <= delta) return 1;
+ if (abs(*(int *)&x - *(int *)&y) <= max_ulps) return 1;
+ return 0;
+}
+
+int stb_is_prime(unsigned int m)
+{
+ unsigned int i,j;
+ if (m < 2) return 0;
+ if (m == 2) return 1;
+ if (!(m & 1)) return 0;
+ if (m % 3 == 0) return (m == 3);
+ for (i=5; (j=i*i), j <= m && j > i; i += 6) {
+ if (m % i == 0) return 0;
+ if (m % (i+2) == 0) return 0;
+ }
+ return 1;
+}
+
+unsigned int stb_power_of_two_nearest_prime(int n)
+{
+ static signed char tab[32] = { 0,0,0,0,1,0,-1,0,1,-1,-1,3,-1,0,-1,2,1,
+ 0,2,0,-1,-4,-1,5,-1,18,-2,15,2,-1,2,0 };
+ if (!tab[0]) {
+ int i;
+ for (i=0; i < 32; ++i)
+ tab[i] = (1 << i) + 2*tab[i] - 1;
+ tab[1] = 2;
+ tab[0] = 1;
+ }
+ if (n >= 32) return 0xfffffffb;
+ return tab[n];
+}
+
+double stb_linear_remap(double x, double x_min, double x_max,
+ double out_min, double out_max)
+{
+ return stb_lerp(stb_unlerp(x,x_min,x_max),out_min,out_max);
+}
+#endif
+
+// create a macro so it's faster, but you can get at the function pointer
+#define stb_linear_remap(t,a,b,c,d) stb_lerp(stb_unlerp(t,a,b),c,d)
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// bit operations
+//
+
+#define stb_big32(c) (((c)[0]<<24) + (c)[1]*65536 + (c)[2]*256 + (c)[3])
+#define stb_little32(c) (((c)[3]<<24) + (c)[2]*65536 + (c)[1]*256 + (c)[0])
+#define stb_big16(c) ((c)[0]*256 + (c)[1])
+#define stb_little16(c) ((c)[1]*256 + (c)[0])
+
+STB_EXTERN int stb_bitcount(unsigned int a);
+STB_EXTERN unsigned int stb_bitreverse8(unsigned char n);
+STB_EXTERN unsigned int stb_bitreverse(unsigned int n);
+
+STB_EXTERN int stb_is_pow2(size_t);
+STB_EXTERN int stb_log2_ceil(size_t);
+STB_EXTERN int stb_log2_floor(size_t);
+
+STB_EXTERN int stb_lowbit8(unsigned int n);
+STB_EXTERN int stb_highbit8(unsigned int n);
+
+#ifdef STB_DEFINE
+int stb_bitcount(unsigned int a)
+{
+ a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2
+ a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4
+ a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits
+ a = (a + (a >> 8)); // max 16 per 8 bits
+ a = (a + (a >> 16)); // max 32 per 8 bits
+ return a & 0xff;
+}
+
+unsigned int stb_bitreverse8(unsigned char n)
+{
+ n = ((n & 0xAA) >> 1) + ((n & 0x55) << 1);
+ n = ((n & 0xCC) >> 2) + ((n & 0x33) << 2);
+ return (unsigned char) ((n >> 4) + (n << 4));
+}
+
+unsigned int stb_bitreverse(unsigned int n)
+{
+ n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1);
+ n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2);
+ n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4);
+ n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8);
+ return (n >> 16) | (n << 16);
+}
+
+int stb_is_pow2(size_t n)
+{
+ return (n & (n-1)) == 0;
+}
+
+// tricky use of 4-bit table to identify 5 bit positions (note the '-1')
+// 3-bit table would require another tree level; 5-bit table wouldn't save one
+#if defined(_WIN32) && !defined(__MINGW32__)
+#pragma warning(push)
+#pragma warning(disable: 4035) // disable warning about no return value
+int stb_log2_floor(size_t n)
+{
+ #if _MSC_VER > 1700
+ unsigned long i;
+ #ifdef STB_PTR64
+ _BitScanReverse64(&i, n);
+ #else
+ _BitScanReverse(&i, n);
+ #endif
+ return i != 0 ? i : -1;
+ #else
+ __asm {
+ bsr eax,n
+ jnz done
+ mov eax,-1
+ }
+ done:;
+ #endif
+}
+#pragma warning(pop)
+#else
+int stb_log2_floor(size_t n)
+{
+ static signed char log2_4[16] = { -1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3 };
+
+#ifdef STB_PTR64
+ if (n >= ((size_t) 1u << 32))
+ return stb_log2_floor(n >> 32);
+#endif
+
+ // 2 compares if n < 16, 3 compares otherwise
+ if (n < (1U << 14))
+ if (n < (1U << 4)) return 0 + log2_4[n ];
+ else if (n < (1U << 9)) return 5 + log2_4[n >> 5];
+ else return 10 + log2_4[n >> 10];
+ else if (n < (1U << 24))
+ if (n < (1U << 19)) return 15 + log2_4[n >> 15];
+ else return 20 + log2_4[n >> 20];
+ else if (n < (1U << 29)) return 25 + log2_4[n >> 25];
+ else return 30 + log2_4[n >> 30];
+}
+#endif
+
+// define ceil from floor
+int stb_log2_ceil(size_t n)
+{
+ if (stb_is_pow2(n)) return stb_log2_floor(n);
+ else return 1 + stb_log2_floor(n);
+}
+
+int stb_highbit8(unsigned int n)
+{
+ return stb_log2_ceil(n&255);
+}
+
+int stb_lowbit8(unsigned int n)
+{
+ static signed char lowbit4[16] = { -1,0,1,0, 2,0,1,0, 3,0,1,0, 2,0,1,0 };
+ int k = lowbit4[n & 15];
+ if (k >= 0) return k;
+ k = lowbit4[(n >> 4) & 15];
+ if (k >= 0) return k+4;
+ return k;
+}
+#endif
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// qsort Compare Routines
+//
+
+#ifdef _WIN32
+ #define stb_stricmp(a,b) stb_p_stricmp(a,b)
+ #define stb_strnicmp(a,b,n) stb_p_strnicmp(a,b,n)
+#else
+ #define stb_stricmp(a,b) strcasecmp(a,b)
+ #define stb_strnicmp(a,b,n) strncasecmp(a,b,n)
+#endif
+
+
+STB_EXTERN int (*stb_intcmp(int offset))(const void *a, const void *b);
+STB_EXTERN int (*stb_intcmprev(int offset))(const void *a, const void *b);
+STB_EXTERN int (*stb_qsort_strcmp(int offset))(const void *a, const void *b);
+STB_EXTERN int (*stb_qsort_stricmp(int offset))(const void *a, const void *b);
+STB_EXTERN int (*stb_floatcmp(int offset))(const void *a, const void *b);
+STB_EXTERN int (*stb_doublecmp(int offset))(const void *a, const void *b);
+STB_EXTERN int (*stb_charcmp(int offset))(const void *a, const void *b);
+
+#ifdef STB_DEFINE
+static int stb__intcmpoffset, stb__ucharcmpoffset, stb__strcmpoffset;
+static int stb__floatcmpoffset, stb__doublecmpoffset;
+static int stb__memcmpoffset, stb__memcmpsize;
+
+int stb__intcmp(const void *a, const void *b)
+{
+ const int p = *(const int *) ((const char *) a + stb__intcmpoffset);
+ const int q = *(const int *) ((const char *) b + stb__intcmpoffset);
+ return p < q ? -1 : p > q;
+}
+
+int stb__intcmprev(const void *a, const void *b)
+{
+ const int p = *(const int *) ((const char *) a + stb__intcmpoffset);
+ const int q = *(const int *) ((const char *) b + stb__intcmpoffset);
+ return q < p ? -1 : q > p;
+}
+
+int stb__ucharcmp(const void *a, const void *b)
+{
+ const int p = *(const unsigned char *) ((const char *) a + stb__ucharcmpoffset);
+ const int q = *(const unsigned char *) ((const char *) b + stb__ucharcmpoffset);
+ return p < q ? -1 : p > q;
+}
+
+int stb__floatcmp(const void *a, const void *b)
+{
+ const float p = *(const float *) ((const char *) a + stb__floatcmpoffset);
+ const float q = *(const float *) ((const char *) b + stb__floatcmpoffset);
+ return p < q ? -1 : p > q;
+}
+
+int stb__doublecmp(const void *a, const void *b)
+{
+ const double p = *(const double *) ((const char *) a + stb__doublecmpoffset);
+ const double q = *(const double *) ((const char *) b + stb__doublecmpoffset);
+ return p < q ? -1 : p > q;
+}
+
+int stb__qsort_strcmp(const void *a, const void *b)
+{
+ const char *p = *(const char **) ((const char *) a + stb__strcmpoffset);
+ const char *q = *(const char **) ((const char *) b + stb__strcmpoffset);
+ return strcmp(p,q);
+}
+
+int stb__qsort_stricmp(const void *a, const void *b)
+{
+ const char *p = *(const char **) ((const char *) a + stb__strcmpoffset);
+ const char *q = *(const char **) ((const char *) b + stb__strcmpoffset);
+ return stb_stricmp(p,q);
+}
+
+int stb__memcmp(const void *a, const void *b)
+{
+ return memcmp((char *) a + stb__memcmpoffset, (char *) b + stb__memcmpoffset, stb__memcmpsize);
+}
+
+int (*stb_intcmp(int offset))(const void *, const void *)
+{
+ stb__intcmpoffset = offset;
+ return &stb__intcmp;
+}
+
+int (*stb_intcmprev(int offset))(const void *, const void *)
+{
+ stb__intcmpoffset = offset;
+ return &stb__intcmprev;
+}
+
+int (*stb_ucharcmp(int offset))(const void *, const void *)
+{
+ stb__ucharcmpoffset = offset;
+ return &stb__ucharcmp;
+}
+
+int (*stb_qsort_strcmp(int offset))(const void *, const void *)
+{
+ stb__strcmpoffset = offset;
+ return &stb__qsort_strcmp;
+}
+
+int (*stb_qsort_stricmp(int offset))(const void *, const void *)
+{
+ stb__strcmpoffset = offset;
+ return &stb__qsort_stricmp;
+}
+
+int (*stb_floatcmp(int offset))(const void *, const void *)
+{
+ stb__floatcmpoffset = offset;
+ return &stb__floatcmp;
+}
+
+int (*stb_doublecmp(int offset))(const void *, const void *)
+{
+ stb__doublecmpoffset = offset;
+ return &stb__doublecmp;
+}
+
+int (*stb_memcmp(int offset, int size))(const void *, const void *)
+{
+ stb__memcmpoffset = offset;
+ stb__memcmpsize = size;
+ return &stb__memcmp;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Binary Search Toolkit
+//
+
+typedef struct
+{
+ int minval, maxval, guess;
+ int mode, step;
+} stb_search;
+
+STB_EXTERN int stb_search_binary(stb_search *s, int minv, int maxv, int find_smallest);
+STB_EXTERN int stb_search_open(stb_search *s, int minv, int find_smallest);
+STB_EXTERN int stb_probe(stb_search *s, int compare, int *result); // return 0 when done
+
+#ifdef STB_DEFINE
+enum
+{
+ STB_probe_binary_smallest,
+ STB_probe_binary_largest,
+ STB_probe_open_smallest,
+ STB_probe_open_largest,
+};
+
+static int stb_probe_guess(stb_search *s, int *result)
+{
+ switch(s->mode) {
+ case STB_probe_binary_largest:
+ if (s->minval == s->maxval) {
+ *result = s->minval;
+ return 0;
+ }
+ assert(s->minval < s->maxval);
+ // if a < b, then a < p <= b
+ s->guess = s->minval + (((unsigned) s->maxval - s->minval + 1) >> 1);
+ break;
+
+ case STB_probe_binary_smallest:
+ if (s->minval == s->maxval) {
+ *result = s->minval;
+ return 0;
+ }
+ assert(s->minval < s->maxval);
+ // if a < b, then a <= p < b
+ s->guess = s->minval + (((unsigned) s->maxval - s->minval) >> 1);
+ break;
+ case STB_probe_open_smallest:
+ case STB_probe_open_largest:
+ s->guess = s->maxval; // guess the current maxval
+ break;
+ }
+ *result = s->guess;
+ return 1;
+}
+
+int stb_probe(stb_search *s, int compare, int *result)
+{
+ switch(s->mode) {
+ case STB_probe_open_smallest:
+ case STB_probe_open_largest: {
+ if (compare <= 0) {
+ // then it lies within minval & maxval
+ if (s->mode == STB_probe_open_smallest)
+ s->mode = STB_probe_binary_smallest;
+ else
+ s->mode = STB_probe_binary_largest;
+ } else {
+ // otherwise, we need to probe larger
+ s->minval = s->maxval + 1;
+ s->maxval = s->minval + s->step;
+ s->step += s->step;
+ }
+ break;
+ }
+ case STB_probe_binary_smallest: {
+ // if compare < 0, then s->minval <= a < p
+ // if compare = 0, then s->minval <= a <= p
+ // if compare > 0, then p < a <= s->maxval
+ if (compare <= 0)
+ s->maxval = s->guess;
+ else
+ s->minval = s->guess+1;
+ break;
+ }
+ case STB_probe_binary_largest: {
+ // if compare < 0, then s->minval <= a < p
+ // if compare = 0, then p <= a <= s->maxval
+ // if compare > 0, then p < a <= s->maxval
+ if (compare < 0)
+ s->maxval = s->guess-1;
+ else
+ s->minval = s->guess;
+ break;
+ }
+ }
+ return stb_probe_guess(s, result);
+}
+
+int stb_search_binary(stb_search *s, int minv, int maxv, int find_smallest)
+{
+ int r;
+ if (maxv < minv) return minv-1;
+ s->minval = minv;
+ s->maxval = maxv;
+ s->mode = find_smallest ? STB_probe_binary_smallest : STB_probe_binary_largest;
+ stb_probe_guess(s, &r);
+ return r;
+}
+
+int stb_search_open(stb_search *s, int minv, int find_smallest)
+{
+ int r;
+ s->step = 4;
+ s->minval = minv;
+ s->maxval = minv+s->step;
+ s->mode = find_smallest ? STB_probe_open_smallest : STB_probe_open_largest;
+ stb_probe_guess(s, &r);
+ return r;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// String Processing
+//
+
+#define stb_prefixi(s,t) (0==stb_strnicmp((s),(t),strlen(t)))
+
+enum stb_splitpath_flag
+{
+ STB_PATH = 1,
+ STB_FILE = 2,
+ STB_EXT = 4,
+ STB_PATH_FILE = STB_PATH + STB_FILE,
+ STB_FILE_EXT = STB_FILE + STB_EXT,
+ STB_EXT_NO_PERIOD = 8,
+};
+
+STB_EXTERN char * stb_skipwhite(char *s);
+STB_EXTERN char * stb_trimwhite(char *s);
+STB_EXTERN char * stb_skipnewline(char *s);
+STB_EXTERN char * stb_strncpy(char *s, char *t, int n);
+STB_EXTERN char * stb_substr(char *t, int n);
+STB_EXTERN char * stb_duplower(char *s);
+STB_EXTERN void stb_tolower (char *s);
+STB_EXTERN char * stb_strchr2 (char *s, char p1, char p2);
+STB_EXTERN char * stb_strrchr2(char *s, char p1, char p2);
+STB_EXTERN char * stb_strtok(char *output, char *src, char *delimit);
+STB_EXTERN char * stb_strtok_keep(char *output, char *src, char *delimit);
+STB_EXTERN char * stb_strtok_invert(char *output, char *src, char *allowed);
+STB_EXTERN char * stb_dupreplace(char *s, char *find, char *replace);
+STB_EXTERN void stb_replaceinplace(char *s, char *find, char *replace);
+STB_EXTERN char * stb_splitpath(char *output, char *src, int flag);
+STB_EXTERN char * stb_splitpathdup(char *src, int flag);
+STB_EXTERN char * stb_replacedir(char *output, char *src, char *dir);
+STB_EXTERN char * stb_replaceext(char *output, char *src, char *ext);
+STB_EXTERN void stb_fixpath(char *path);
+STB_EXTERN char * stb_shorten_path_readable(char *path, int max_len);
+STB_EXTERN int stb_suffix (char *s, char *t);
+STB_EXTERN int stb_suffixi(char *s, char *t);
+STB_EXTERN int stb_prefix (char *s, char *t);
+STB_EXTERN char * stb_strichr(char *s, char t);
+STB_EXTERN char * stb_stristr(char *s, char *t);
+STB_EXTERN int stb_prefix_count(char *s, char *t);
+STB_EXTERN const char * stb_plural(int n); // "s" or ""
+STB_EXTERN size_t stb_strscpy(char *d, const char *s, size_t n);
+
+STB_EXTERN char **stb_tokens(char *src, char *delimit, int *count);
+STB_EXTERN char **stb_tokens_nested(char *src, char *delimit, int *count, char *nest_in, char *nest_out);
+STB_EXTERN char **stb_tokens_nested_empty(char *src, char *delimit, int *count, char *nest_in, char *nest_out);
+STB_EXTERN char **stb_tokens_allowempty(char *src, char *delimit, int *count);
+STB_EXTERN char **stb_tokens_stripwhite(char *src, char *delimit, int *count);
+STB_EXTERN char **stb_tokens_withdelim(char *src, char *delimit, int *count);
+STB_EXTERN char **stb_tokens_quoted(char *src, char *delimit, int *count);
+// with 'quoted', allow delimiters to appear inside quotation marks, and don't
+// strip whitespace inside them (and we delete the quotation marks unless they
+// appear back to back, in which case they're considered escaped)
+
+#ifdef STB_DEFINE
+
+size_t stb_strscpy(char *d, const char *s, size_t n)
+{
+ size_t len = strlen(s);
+ if (len >= n) {
+ if (n) d[0] = 0;
+ return 0;
+ }
+ stb_p_strcpy_s(d,n,s);
+ return len;
+}
+
+const char *stb_plural(int n)
+{
+ return n == 1 ? "" : "s";
+}
+
+int stb_prefix(char *s, char *t)
+{
+ while (*t)
+ if (*s++ != *t++)
+ return STB_FALSE;
+ return STB_TRUE;
+}
+
+int stb_prefix_count(char *s, char *t)
+{
+ int c=0;
+ while (*t) {
+ if (*s++ != *t++)
+ break;
+ ++c;
+ }
+ return c;
+}
+
+int stb_suffix(char *s, char *t)
+{
+ size_t n = strlen(s);
+ size_t m = strlen(t);
+ if (m <= n)
+ return 0 == strcmp(s+n-m, t);
+ else
+ return 0;
+}
+
+int stb_suffixi(char *s, char *t)
+{
+ size_t n = strlen(s);
+ size_t m = strlen(t);
+ if (m <= n)
+ return 0 == stb_stricmp(s+n-m, t);
+ else
+ return 0;
+}
+
+// originally I was using this table so that I could create known sentinel
+// values--e.g. change whitetable[0] to be true if I was scanning for whitespace,
+// and false if I was scanning for nonwhite. I don't appear to be using that
+// functionality anymore (I do for tokentable, though), so just replace it
+// with isspace()
+char *stb_skipwhite(char *s)
+{
+ while (isspace((unsigned char) *s)) ++s;
+ return s;
+}
+
+char *stb_skipnewline(char *s)
+{
+ if (s[0] == '\r' || s[0] == '\n') {
+ if (s[0]+s[1] == '\r' + '\n') ++s;
+ ++s;
+ }
+ return s;
+}
+
+char *stb_trimwhite(char *s)
+{
+ int i,n;
+ s = stb_skipwhite(s);
+ n = (int) strlen(s);
+ for (i=n-1; i >= 0; --i)
+ if (!isspace(s[i]))
+ break;
+ s[i+1] = 0;
+ return s;
+}
+
+char *stb_strncpy(char *s, char *t, int n)
+{
+ stb_p_strncpy_s(s,n+1,t,n);
+ s[n] = 0;
+ return s;
+}
+
+char *stb_substr(char *t, int n)
+{
+ char *a;
+ int z = (int) strlen(t);
+ if (z < n) n = z;
+ a = (char *) malloc(n+1);
+ stb_p_strncpy_s(a,n+1,t,n);
+ a[n] = 0;
+ return a;
+}
+
+char *stb_duplower(char *s)
+{
+ char *p = stb_p_strdup(s), *q = p;
+ while (*q) {
+ *q = tolower(*q);
+ ++q;
+ }
+ return p;
+}
+
+void stb_tolower(char *s)
+{
+ while (*s) {
+ *s = tolower(*s);
+ ++s;
+ }
+}
+
+char *stb_strchr2(char *s, char x, char y)
+{
+ for(; *s; ++s)
+ if (*s == x || *s == y)
+ return s;
+ return NULL;
+}
+
+char *stb_strrchr2(char *s, char x, char y)
+{
+ char *r = NULL;
+ for(; *s; ++s)
+ if (*s == x || *s == y)
+ r = s;
+ return r;
+}
+
+char *stb_strichr(char *s, char t)
+{
+ if (tolower(t) == toupper(t))
+ return strchr(s,t);
+ return stb_strchr2(s, (char) tolower(t), (char) toupper(t));
+}
+
+char *stb_stristr(char *s, char *t)
+{
+ size_t n = strlen(t);
+ char *z;
+ if (n==0) return s;
+ while ((z = stb_strichr(s, *t)) != NULL) {
+ if (0==stb_strnicmp(z, t, n))
+ return z;
+ s = z+1;
+ }
+ return NULL;
+}
+
+static char *stb_strtok_raw(char *output, char *src, char *delimit, int keep, int invert)
+{
+ if (invert) {
+ while (*src && strchr(delimit, *src) != NULL) {
+ *output++ = *src++;
+ }
+ } else {
+ while (*src && strchr(delimit, *src) == NULL) {
+ *output++ = *src++;
+ }
+ }
+ *output = 0;
+ if (keep)
+ return src;
+ else
+ return *src ? src+1 : src;
+}
+
+char *stb_strtok(char *output, char *src, char *delimit)
+{
+ return stb_strtok_raw(output, src, delimit, 0, 0);
+}
+
+char *stb_strtok_keep(char *output, char *src, char *delimit)
+{
+ return stb_strtok_raw(output, src, delimit, 1, 0);
+}
+
+char *stb_strtok_invert(char *output, char *src, char *delimit)
+{
+ return stb_strtok_raw(output, src, delimit, 1,1);
+}
+
+static char **stb_tokens_raw(char *src_, char *delimit, int *count,
+ int stripwhite, int allow_empty, char *start, char *end)
+{
+ int nested = 0;
+ unsigned char *src = (unsigned char *) src_;
+ static char stb_tokentable[256]; // rely on static initializion to 0
+ static char stable[256],etable[256];
+ char *out;
+ char **result;
+ int num=0;
+ unsigned char *s;
+
+ s = (unsigned char *) delimit; while (*s) stb_tokentable[*s++] = 1;
+ if (start) {
+ s = (unsigned char *) start; while (*s) stable[*s++] = 1;
+ s = (unsigned char *) end; if (s) while (*s) stable[*s++] = 1;
+ s = (unsigned char *) end; if (s) while (*s) etable[*s++] = 1;
+ }
+ stable[0] = 1;
+
+ // two passes through: the first time, counting how many
+ s = (unsigned char *) src;
+ while (*s) {
+ // state: just found delimiter
+ // skip further delimiters
+ if (!allow_empty) {
+ stb_tokentable[0] = 0;
+ while (stb_tokentable[*s])
+ ++s;
+ if (!*s) break;
+ }
+ ++num;
+ // skip further non-delimiters
+ stb_tokentable[0] = 1;
+ if (stripwhite == 2) { // quoted strings
+ while (!stb_tokentable[*s]) {
+ if (*s != '"')
+ ++s;
+ else {
+ ++s;
+ if (*s == '"')
+ ++s; // "" -> ", not start a string
+ else {
+ // begin a string
+ while (*s) {
+ if (s[0] == '"') {
+ if (s[1] == '"') s += 2; // "" -> "
+ else { ++s; break; } // terminating "
+ } else
+ ++s;
+ }
+ }
+ }
+ }
+ } else
+ while (nested || !stb_tokentable[*s]) {
+ if (stable[*s]) {
+ if (!*s) break;
+ if (end ? etable[*s] : nested)
+ --nested;
+ else
+ ++nested;
+ }
+ ++s;
+ }
+ if (allow_empty) {
+ if (*s) ++s;
+ }
+ }
+ // now num has the actual count... malloc our output structure
+ // need space for all the strings: strings won't be any longer than
+ // original input, since for every '\0' there's at least one delimiter
+ result = (char **) malloc(sizeof(*result) * (num+1) + (s-src+1));
+ if (result == NULL) return result;
+ out = (char *) (result + (num+1));
+ // second pass: copy out the data
+ s = (unsigned char *) src;
+ num = 0;
+ nested = 0;
+ while (*s) {
+ char *last_nonwhite;
+ // state: just found delimiter
+ // skip further delimiters
+ if (!allow_empty) {
+ stb_tokentable[0] = 0;
+ if (stripwhite)
+ while (stb_tokentable[*s] || isspace(*s))
+ ++s;
+ else
+ while (stb_tokentable[*s])
+ ++s;
+ } else if (stripwhite) {
+ while (isspace(*s)) ++s;
+ }
+ if (!*s) break;
+ // we're past any leading delimiters and whitespace
+ result[num] = out;
+ ++num;
+ // copy non-delimiters
+ stb_tokentable[0] = 1;
+ last_nonwhite = out-1;
+ if (stripwhite == 2) {
+ while (!stb_tokentable[*s]) {
+ if (*s != '"') {
+ if (!isspace(*s)) last_nonwhite = out;
+ *out++ = *s++;
+ } else {
+ ++s;
+ if (*s == '"') {
+ if (!isspace(*s)) last_nonwhite = out;
+ *out++ = *s++; // "" -> ", not start string
+ } else {
+ // begin a quoted string
+ while (*s) {
+ if (s[0] == '"') {
+ if (s[1] == '"') { *out++ = *s; s += 2; }
+ else { ++s; break; } // terminating "
+ } else
+ *out++ = *s++;
+ }
+ last_nonwhite = out-1; // all in quotes counts as non-white
+ }
+ }
+ }
+ } else {
+ while (nested || !stb_tokentable[*s]) {
+ if (!isspace(*s)) last_nonwhite = out;
+ if (stable[*s]) {
+ if (!*s) break;
+ if (end ? etable[*s] : nested)
+ --nested;
+ else
+ ++nested;
+ }
+ *out++ = *s++;
+ }
+ }
+
+ if (stripwhite) // rewind to last non-whitespace char
+ out = last_nonwhite+1;
+ *out++ = '\0';
+
+ if (*s) ++s; // skip delimiter
+ }
+ s = (unsigned char *) delimit; while (*s) stb_tokentable[*s++] = 0;
+ if (start) {
+ s = (unsigned char *) start; while (*s) stable[*s++] = 1;
+ s = (unsigned char *) end; if (s) while (*s) stable[*s++] = 1;
+ s = (unsigned char *) end; if (s) while (*s) etable[*s++] = 1;
+ }
+ if (count != NULL) *count = num;
+ result[num] = 0;
+ return result;
+}
+
+char **stb_tokens(char *src, char *delimit, int *count)
+{
+ return stb_tokens_raw(src,delimit,count,0,0,0,0);
+}
+
+char **stb_tokens_nested(char *src, char *delimit, int *count, char *nest_in, char *nest_out)
+{
+ return stb_tokens_raw(src,delimit,count,0,0,nest_in,nest_out);
+}
+
+char **stb_tokens_nested_empty(char *src, char *delimit, int *count, char *nest_in, char *nest_out)
+{
+ return stb_tokens_raw(src,delimit,count,0,1,nest_in,nest_out);
+}
+
+char **stb_tokens_allowempty(char *src, char *delimit, int *count)
+{
+ return stb_tokens_raw(src,delimit,count,0,1,0,0);
+}
+
+char **stb_tokens_stripwhite(char *src, char *delimit, int *count)
+{
+ return stb_tokens_raw(src,delimit,count,1,1,0,0);
+}
+
+char **stb_tokens_quoted(char *src, char *delimit, int *count)
+{
+ return stb_tokens_raw(src,delimit,count,2,1,0,0);
+}
+
+char *stb_dupreplace(char *src, char *find, char *replace)
+{
+ size_t len_find = strlen(find);
+ size_t len_replace = strlen(replace);
+ int count = 0;
+
+ char *s,*p,*q;
+
+ s = strstr(src, find);
+ if (s == NULL) return stb_p_strdup(src);
+ do {
+ ++count;
+ s = strstr(s + len_find, find);
+ } while (s != NULL);
+
+ p = (char *) malloc(strlen(src) + count * (len_replace - len_find) + 1);
+ if (p == NULL) return p;
+ q = p;
+ s = src;
+ for (;;) {
+ char *t = strstr(s, find);
+ if (t == NULL) {
+ stb_p_strcpy_s(q,strlen(src)+count*(len_replace-len_find)+1,s);
+ assert(strlen(p) == strlen(src) + count*(len_replace-len_find));
+ return p;
+ }
+ memcpy(q, s, t-s);
+ q += t-s;
+ memcpy(q, replace, len_replace);
+ q += len_replace;
+ s = t + len_find;
+ }
+}
+
+void stb_replaceinplace(char *src, char *find, char *replace)
+{
+ size_t len_find = strlen(find);
+ size_t len_replace = strlen(replace);
+ int delta;
+
+ char *s,*p,*q;
+
+ delta = (int) (len_replace - len_find);
+ assert(delta <= 0);
+ if (delta > 0) return;
+
+ p = strstr(src, find);
+ if (p == NULL) return;
+
+ s = q = p;
+ while (*s) {
+ memcpy(q, replace, len_replace);
+ p += len_find;
+ q += len_replace;
+ s = strstr(p, find);
+ if (s == NULL) s = p + strlen(p);
+ memmove(q, p, s-p);
+ q += s-p;
+ p = s;
+ }
+ *q = 0;
+}
+
+void stb_fixpath(char *path)
+{
+ for(; *path; ++path)
+ if (*path == '\\')
+ *path = '/';
+}
+
+void stb__add_section(char *buffer, char *data, ptrdiff_t curlen, ptrdiff_t newlen)
+{
+ if (newlen < curlen) {
+ ptrdiff_t z1 = newlen >> 1, z2 = newlen-z1;
+ memcpy(buffer, data, z1-1);
+ buffer[z1-1] = '.';
+ buffer[z1-0] = '.';
+ memcpy(buffer+z1+1, data+curlen-z2+1, z2-1);
+ } else
+ memcpy(buffer, data, curlen);
+}
+
+char * stb_shorten_path_readable(char *path, int len)
+{
+ static char buffer[1024];
+ ptrdiff_t n = strlen(path),n1,n2,r1,r2;
+ char *s;
+ if (n <= len) return path;
+ if (len > 1024) return path;
+ s = stb_strrchr2(path, '/', '\\');
+ if (s) {
+ n1 = s - path + 1;
+ n2 = n - n1;
+ ++s;
+ } else {
+ n1 = 0;
+ n2 = n;
+ s = path;
+ }
+ // now we need to reduce r1 and r2 so that they fit in len
+ if (n1 < len>>1) {
+ r1 = n1;
+ r2 = len - r1;
+ } else if (n2 < len >> 1) {
+ r2 = n2;
+ r1 = len - r2;
+ } else {
+ r1 = n1 * len / n;
+ r2 = n2 * len / n;
+ if (r1 < len>>2) r1 = len>>2, r2 = len-r1;
+ if (r2 < len>>2) r2 = len>>2, r1 = len-r2;
+ }
+ assert(r1 <= n1 && r2 <= n2);
+ if (n1)
+ stb__add_section(buffer, path, n1, r1);
+ stb__add_section(buffer+r1, s, n2, r2);
+ buffer[len] = 0;
+ return buffer;
+}
+
+static char *stb__splitpath_raw(char *buffer, char *path, int flag)
+{
+ ptrdiff_t len=0,x,y, n = (int) strlen(path), f1,f2;
+ char *s = stb_strrchr2(path, '/', '\\');
+ char *t = strrchr(path, '.');
+ if (s && t && t < s) t = NULL;
+
+ if (!s) {
+ // check for drive
+ if (isalpha(path[0]) && path[1] == ':')
+ s = &path[1];
+ }
+ if (s) ++s;
+
+ if (flag == STB_EXT_NO_PERIOD)
+ flag |= STB_EXT;
+
+ if (!(flag & (STB_PATH | STB_FILE | STB_EXT))) return NULL;
+
+ f1 = s == NULL ? 0 : s-path; // start of filename
+ f2 = t == NULL ? n : t-path; // just past end of filename
+
+ if (flag & STB_PATH) {
+ x = 0; if (f1 == 0 && flag == STB_PATH) len=2;
+ } else if (flag & STB_FILE) {
+ x = f1;
+ } else {
+ x = f2;
+ if (flag & STB_EXT_NO_PERIOD)
+ if (path[x] == '.')
+ ++x;
+ }
+
+ if (flag & STB_EXT)
+ y = n;
+ else if (flag & STB_FILE)
+ y = f2;
+ else
+ y = f1;
+
+ if (buffer == NULL) {
+ buffer = (char *) malloc(y-x + len + 1);
+ if (!buffer) return NULL;
+ }
+
+ if (len) { stb_p_strcpy_s(buffer, 3, "./"); return buffer; }
+ stb_strncpy(buffer, path+(int)x, (int)(y-x));
+ return buffer;
+}
+
+char *stb_splitpath(char *output, char *src, int flag)
+{
+ return stb__splitpath_raw(output, src, flag);
+}
+
+char *stb_splitpathdup(char *src, int flag)
+{
+ return stb__splitpath_raw(NULL, src, flag);
+}
+
+char *stb_replacedir(char *output, char *src, char *dir)
+{
+ char buffer[4096];
+ stb_splitpath(buffer, src, STB_FILE | STB_EXT);
+ if (dir)
+ stb_p_sprintf(output stb_p_size(9999), "%s/%s", dir, buffer);
+ else
+ stb_p_strcpy_s(output, sizeof(buffer), buffer); // @UNSAFE
+ return output;
+}
+
+char *stb_replaceext(char *output, char *src, char *ext)
+{
+ char buffer[4096];
+ stb_splitpath(buffer, src, STB_PATH | STB_FILE);
+ if (ext)
+ stb_p_sprintf(output stb_p_size(9999), "%s.%s", buffer, ext[0] == '.' ? ext+1 : ext);
+ else
+ stb_p_strcpy_s(output, sizeof(buffer), buffer); // @UNSAFE
+ return output;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_alloc - hierarchical allocator
+//
+// inspired by http://swapped.cc/halloc
+//
+//
+// When you alloc a given block through stb_alloc, you have these choices:
+//
+// 1. does it have a parent?
+// 2. can it have children?
+// 3. can it be freed directly?
+// 4. is it transferrable?
+// 5. what is its alignment?
+//
+// Here are interesting combinations of those:
+//
+// children free transfer alignment
+// arena Y Y N n/a
+// no-overhead, chunked N N N normal
+// string pool alloc N N N 1
+// parent-ptr, chunked Y N N normal
+// low-overhead, unchunked N Y Y normal
+// general purpose alloc Y Y Y normal
+//
+// Unchunked allocations will probably return 16-aligned pointers. If
+// we 16-align the results, we have room for 4 pointers. For smaller
+// allocations that allow finer alignment, we can reduce the pointers.
+//
+// The strategy is that given a pointer, assuming it has a header (only
+// the no-overhead allocations have no header), we can determine the
+// type of the header fields, and the number of them, by stepping backwards
+// through memory and looking at the tags in the bottom bits.
+//
+// Implementation strategy:
+// chunked allocations come from the middle of chunks, and can't
+// be freed. thefore they do not need to be on a sibling chain.
+// they may need child pointers if they have children.
+//
+// chunked, with-children
+// void *parent;
+//
+// unchunked, no-children -- reduced storage
+// void *next_sibling;
+// void *prev_sibling_nextp;
+//
+// unchunked, general
+// void *first_child;
+// void *next_sibling;
+// void *prev_sibling_nextp;
+// void *chunks;
+//
+// so, if we code each of these fields with different bit patterns
+// (actually same one for next/prev/child), then we can identify which
+// each one is from the last field.
+
+STB_EXTERN void stb_free(void *p);
+STB_EXTERN void *stb_malloc_global(size_t size);
+STB_EXTERN void *stb_malloc(void *context, size_t size);
+STB_EXTERN void *stb_malloc_nofree(void *context, size_t size);
+STB_EXTERN void *stb_malloc_leaf(void *context, size_t size);
+STB_EXTERN void *stb_malloc_raw(void *context, size_t size);
+STB_EXTERN void *stb_realloc(void *ptr, size_t newsize);
+
+STB_EXTERN void stb_reassign(void *new_context, void *ptr);
+STB_EXTERN void stb_malloc_validate(void *p, void *parent);
+
+extern int stb_alloc_chunk_size ;
+extern int stb_alloc_count_free ;
+extern int stb_alloc_count_alloc;
+extern int stb_alloc_alignment ;
+
+#ifdef STB_DEFINE
+
+int stb_alloc_chunk_size = 65536;
+int stb_alloc_count_free = 0;
+int stb_alloc_count_alloc = 0;
+int stb_alloc_alignment = -16;
+
+typedef struct stb__chunk
+{
+ struct stb__chunk *next;
+ int data_left;
+ int alloc;
+} stb__chunk;
+
+typedef struct
+{
+ void * next;
+ void ** prevn;
+} stb__nochildren;
+
+typedef struct
+{
+ void ** prevn;
+ void * child;
+ void * next;
+ stb__chunk *chunks;
+} stb__alloc;
+
+typedef struct
+{
+ stb__alloc *parent;
+} stb__chunked;
+
+#define STB__PARENT 1
+#define STB__CHUNKS 2
+
+typedef enum
+{
+ STB__nochildren = 0,
+ STB__chunked = STB__PARENT,
+ STB__alloc = STB__CHUNKS,
+
+ STB__chunk_raw = 4,
+} stb__alloc_type;
+
+// these functions set the bottom bits of a pointer efficiently
+#define STB__DECODE(x,v) ((void *) ((char *) (x) - (v)))
+#define STB__ENCODE(x,v) ((void *) ((char *) (x) + (v)))
+
+#define stb__parent(z) (stb__alloc *) STB__DECODE((z)->parent, STB__PARENT)
+#define stb__chunks(z) (stb__chunk *) STB__DECODE((z)->chunks, STB__CHUNKS)
+
+#define stb__setparent(z,p) (z)->parent = (stb__alloc *) STB__ENCODE((p), STB__PARENT)
+#define stb__setchunks(z,c) (z)->chunks = (stb__chunk *) STB__ENCODE((c), STB__CHUNKS)
+
+static stb__alloc stb__alloc_global =
+{
+ NULL,
+ NULL,
+ NULL,
+ (stb__chunk *) STB__ENCODE(NULL, STB__CHUNKS)
+};
+
+static stb__alloc_type stb__identify(void *p)
+{
+ void **q = (void **) p;
+ return (stb__alloc_type) ((stb_uinta) q[-1] & 3);
+}
+
+static void *** stb__prevn(void *p)
+{
+ if (stb__identify(p) == STB__alloc) {
+ stb__alloc *s = (stb__alloc *) p - 1;
+ return &s->prevn;
+ } else {
+ stb__nochildren *s = (stb__nochildren *) p - 1;
+ return &s->prevn;
+ }
+}
+
+void stb_free(void *p)
+{
+ if (p == NULL) return;
+
+ // count frees so that unit tests can see what's happening
+ ++stb_alloc_count_free;
+
+ switch(stb__identify(p)) {
+ case STB__chunked:
+ // freeing a chunked-block with children does nothing;
+ // they only get freed when the parent does
+ // surely this is wrong, and it should free them immediately?
+ // otherwise how are they getting put on the right chain?
+ return;
+ case STB__nochildren: {
+ stb__nochildren *s = (stb__nochildren *) p - 1;
+ // unlink from sibling chain
+ *(s->prevn) = s->next;
+ if (s->next)
+ *stb__prevn(s->next) = s->prevn;
+ free(s);
+ return;
+ }
+ case STB__alloc: {
+ stb__alloc *s = (stb__alloc *) p - 1;
+ stb__chunk *c, *n;
+ void *q;
+
+ // unlink from sibling chain, if any
+ *(s->prevn) = s->next;
+ if (s->next)
+ *stb__prevn(s->next) = s->prevn;
+
+ // first free chunks
+ c = (stb__chunk *) stb__chunks(s);
+ while (c != NULL) {
+ n = c->next;
+ stb_alloc_count_free += c->alloc;
+ free(c);
+ c = n;
+ }
+
+ // validating
+ stb__setchunks(s,NULL);
+ s->prevn = NULL;
+ s->next = NULL;
+
+ // now free children
+ while ((q = s->child) != NULL) {
+ stb_free(q);
+ }
+
+ // now free self
+ free(s);
+ return;
+ }
+ default:
+ assert(0); /* NOTREACHED */
+ }
+}
+
+void stb_malloc_validate(void *p, void *parent)
+{
+ if (p == NULL) return;
+
+ switch(stb__identify(p)) {
+ case STB__chunked:
+ return;
+ case STB__nochildren: {
+ stb__nochildren *n = (stb__nochildren *) p - 1;
+ if (n->prevn)
+ assert(*n->prevn == p);
+ if (n->next) {
+ assert(*stb__prevn(n->next) == &n->next);
+ stb_malloc_validate(n, parent);
+ }
+ return;
+ }
+ case STB__alloc: {
+ stb__alloc *s = (stb__alloc *) p - 1;
+
+ if (s->prevn)
+ assert(*s->prevn == p);
+
+ if (s->child) {
+ assert(*stb__prevn(s->child) == &s->child);
+ stb_malloc_validate(s->child, p);
+ }
+
+ if (s->next) {
+ assert(*stb__prevn(s->next) == &s->next);
+ stb_malloc_validate(s->next, parent);
+ }
+ return;
+ }
+ default:
+ assert(0); /* NOTREACHED */
+ }
+}
+
+static void * stb__try_chunk(stb__chunk *c, int size, int align, int pre_align)
+{
+ char *memblock = (char *) (c+1), *q;
+ stb_inta iq;
+ int start_offset;
+
+ // we going to allocate at the end of the chunk, not the start. confusing,
+ // but it means we don't need both a 'limit' and a 'cur', just a 'cur'.
+ // the block ends at: p + c->data_left
+ // then we move back by size
+ start_offset = c->data_left - size;
+
+ // now we need to check the alignment of that
+ q = memblock + start_offset;
+ iq = (stb_inta) q;
+ assert(sizeof(q) == sizeof(iq));
+
+ // suppose align = 2
+ // then we need to retreat iq far enough that (iq & (2-1)) == 0
+ // to get (iq & (align-1)) = 0 requires subtracting (iq & (align-1))
+
+ start_offset -= iq & (align-1);
+ assert(((stb_uinta) (memblock+start_offset) & (align-1)) == 0);
+
+ // now, if that + pre_align works, go for it!
+ start_offset -= pre_align;
+
+ if (start_offset >= 0) {
+ c->data_left = start_offset;
+ return memblock + start_offset;
+ }
+
+ return NULL;
+}
+
+static void stb__sort_chunks(stb__alloc *src)
+{
+ // of the first two chunks, put the chunk with more data left in it first
+ stb__chunk *c = stb__chunks(src), *d;
+ if (c == NULL) return;
+ d = c->next;
+ if (d == NULL) return;
+ if (c->data_left > d->data_left) return;
+
+ c->next = d->next;
+ d->next = c;
+ stb__setchunks(src, d);
+}
+
+static void * stb__alloc_chunk(stb__alloc *src, int size, int align, int pre_align)
+{
+ void *p;
+ stb__chunk *c = stb__chunks(src);
+
+ if (c && size <= stb_alloc_chunk_size) {
+
+ p = stb__try_chunk(c, size, align, pre_align);
+ if (p) { ++c->alloc; return p; }
+
+ // try a second chunk to reduce wastage
+ if (c->next) {
+ p = stb__try_chunk(c->next, size, align, pre_align);
+ if (p) { ++c->alloc; return p; }
+
+ // put the bigger chunk first, since the second will get buried
+ // the upshot of this is that, until it gets allocated from, chunk #2
+ // is always the largest remaining chunk. (could formalize
+ // this with a heap!)
+ stb__sort_chunks(src);
+ c = stb__chunks(src);
+ }
+ }
+
+ // allocate a new chunk
+ {
+ stb__chunk *n;
+
+ int chunk_size = stb_alloc_chunk_size;
+ // we're going to allocate a new chunk to put this in
+ if (size > chunk_size)
+ chunk_size = size;
+
+ assert(sizeof(*n) + pre_align <= 16);
+
+ // loop trying to allocate a large enough chunk
+ // the loop is because the alignment may cause problems if it's big...
+ // and we don't know what our chunk alignment is going to be
+ while (1) {
+ n = (stb__chunk *) malloc(16 + chunk_size);
+ if (n == NULL) return NULL;
+
+ n->data_left = chunk_size - sizeof(*n);
+
+ p = stb__try_chunk(n, size, align, pre_align);
+ if (p != NULL) {
+ n->next = c;
+ stb__setchunks(src, n);
+
+ // if we just used up the whole block immediately,
+ // move the following chunk up
+ n->alloc = 1;
+ if (size == chunk_size)
+ stb__sort_chunks(src);
+
+ return p;
+ }
+
+ free(n);
+ chunk_size += 16+align;
+ }
+ }
+}
+
+static stb__alloc * stb__get_context(void *context)
+{
+ if (context == NULL) {
+ return &stb__alloc_global;
+ } else {
+ int u = stb__identify(context);
+ // if context is chunked, grab parent
+ if (u == STB__chunked) {
+ stb__chunked *s = (stb__chunked *) context - 1;
+ return stb__parent(s);
+ } else {
+ return (stb__alloc *) context - 1;
+ }
+ }
+}
+
+static void stb__insert_alloc(stb__alloc *src, stb__alloc *s)
+{
+ s->prevn = &src->child;
+ s->next = src->child;
+ src->child = s+1;
+ if (s->next)
+ *stb__prevn(s->next) = &s->next;
+}
+
+static void stb__insert_nochild(stb__alloc *src, stb__nochildren *s)
+{
+ s->prevn = &src->child;
+ s->next = src->child;
+ src->child = s+1;
+ if (s->next)
+ *stb__prevn(s->next) = &s->next;
+}
+
+static void * malloc_base(void *context, size_t size, stb__alloc_type t, int align)
+{
+ void *p;
+
+ stb__alloc *src = stb__get_context(context);
+
+ if (align <= 0) {
+ // compute worst-case C packed alignment
+ // e.g. a 24-byte struct is 8-aligned
+ int align_proposed = 1 << stb_lowbit8((unsigned int) size);
+
+ if (align_proposed < 0)
+ align_proposed = 4;
+
+ if (align_proposed == 0) {
+ if (size == 0)
+ align_proposed = 1;
+ else
+ align_proposed = 256;
+ }
+
+ // a negative alignment means 'don't align any larger
+ // than this'; so -16 means we align 1,2,4,8, or 16
+
+ if (align < 0) {
+ if (align_proposed > -align)
+ align_proposed = -align;
+ }
+
+ align = align_proposed;
+ }
+
+ assert(stb_is_pow2(align));
+
+ // don't cause misalignment when allocating nochildren
+ if (t == STB__nochildren && align > 8)
+ t = STB__alloc;
+
+ switch (t) {
+ case STB__alloc: {
+ stb__alloc *s = (stb__alloc *) malloc(size + sizeof(*s));
+ if (s == NULL) return NULL;
+ p = s+1;
+ s->child = NULL;
+ stb__insert_alloc(src, s);
+
+ stb__setchunks(s,NULL);
+ break;
+ }
+
+ case STB__nochildren: {
+ stb__nochildren *s = (stb__nochildren *) malloc(size + sizeof(*s));
+ if (s == NULL) return NULL;
+ p = s+1;
+ stb__insert_nochild(src, s);
+ break;
+ }
+
+ case STB__chunk_raw: {
+ p = stb__alloc_chunk(src, (int) size, align, 0);
+ if (p == NULL) return NULL;
+ break;
+ }
+
+ case STB__chunked: {
+ stb__chunked *s;
+ if (align < sizeof(stb_uintptr)) align = sizeof(stb_uintptr);
+ s = (stb__chunked *) stb__alloc_chunk(src, (int) size, align, sizeof(*s));
+ if (s == NULL) return NULL;
+ stb__setparent(s, src);
+ p = s+1;
+ break;
+ }
+
+ default: p = NULL; assert(0); /* NOTREACHED */
+ }
+
+ ++stb_alloc_count_alloc;
+ return p;
+}
+
+void *stb_malloc_global(size_t size)
+{
+ return malloc_base(NULL, size, STB__alloc, stb_alloc_alignment);
+}
+
+void *stb_malloc(void *context, size_t size)
+{
+ return malloc_base(context, size, STB__alloc, stb_alloc_alignment);
+}
+
+void *stb_malloc_nofree(void *context, size_t size)
+{
+ return malloc_base(context, size, STB__chunked, stb_alloc_alignment);
+}
+
+void *stb_malloc_leaf(void *context, size_t size)
+{
+ return malloc_base(context, size, STB__nochildren, stb_alloc_alignment);
+}
+
+void *stb_malloc_raw(void *context, size_t size)
+{
+ return malloc_base(context, size, STB__chunk_raw, stb_alloc_alignment);
+}
+
+char *stb_malloc_string(void *context, size_t size)
+{
+ return (char *) malloc_base(context, size, STB__chunk_raw, 1);
+}
+
+void *stb_realloc(void *ptr, size_t newsize)
+{
+ stb__alloc_type t;
+
+ if (ptr == NULL) return stb_malloc(NULL, newsize);
+ if (newsize == 0) { stb_free(ptr); return NULL; }
+
+ t = stb__identify(ptr);
+ assert(t == STB__alloc || t == STB__nochildren);
+
+ if (t == STB__alloc) {
+ stb__alloc *s = (stb__alloc *) ptr - 1;
+
+ s = (stb__alloc *) realloc(s, newsize + sizeof(*s));
+ if (s == NULL) return NULL;
+
+ ptr = s+1;
+
+ // update pointers
+ (*s->prevn) = ptr;
+ if (s->next)
+ *stb__prevn(s->next) = &s->next;
+
+ if (s->child)
+ *stb__prevn(s->child) = &s->child;
+
+ return ptr;
+ } else {
+ stb__nochildren *s = (stb__nochildren *) ptr - 1;
+
+ s = (stb__nochildren *) realloc(ptr, newsize + sizeof(s));
+ if (s == NULL) return NULL;
+
+ // update pointers
+ (*s->prevn) = s+1;
+ if (s->next)
+ *stb__prevn(s->next) = &s->next;
+
+ return s+1;
+ }
+}
+
+void *stb_realloc_c(void *context, void *ptr, size_t newsize)
+{
+ if (ptr == NULL) return stb_malloc(context, newsize);
+ if (newsize == 0) { stb_free(ptr); return NULL; }
+ // @TODO: verify you haven't changed contexts
+ return stb_realloc(ptr, newsize);
+}
+
+void stb_reassign(void *new_context, void *ptr)
+{
+ stb__alloc *src = stb__get_context(new_context);
+
+ stb__alloc_type t = stb__identify(ptr);
+ assert(t == STB__alloc || t == STB__nochildren);
+
+ if (t == STB__alloc) {
+ stb__alloc *s = (stb__alloc *) ptr - 1;
+
+ // unlink from old
+ *(s->prevn) = s->next;
+ if (s->next)
+ *stb__prevn(s->next) = s->prevn;
+
+ stb__insert_alloc(src, s);
+ } else {
+ stb__nochildren *s = (stb__nochildren *) ptr - 1;
+
+ // unlink from old
+ *(s->prevn) = s->next;
+ if (s->next)
+ *stb__prevn(s->next) = s->prevn;
+
+ stb__insert_nochild(src, s);
+ }
+}
+
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_arr
+//
+// An stb_arr is directly useable as a pointer (use the actual type in your
+// definition), but when it resizes, it returns a new pointer and you can't
+// use the old one, so you have to be careful to copy-in-out as necessary.
+//
+// Use a NULL pointer as a 0-length array.
+//
+// float *my_array = NULL, *temp;
+//
+// // add elements on the end one at a time
+// stb_arr_push(my_array, 0.0f);
+// stb_arr_push(my_array, 1.0f);
+// stb_arr_push(my_array, 2.0f);
+//
+// assert(my_array[1] == 2.0f);
+//
+// // add an uninitialized element at the end, then assign it
+// *stb_arr_add(my_array) = 3.0f;
+//
+// // add three uninitialized elements at the end
+// temp = stb_arr_addn(my_array,3);
+// temp[0] = 4.0f;
+// temp[1] = 5.0f;
+// temp[2] = 6.0f;
+//
+// assert(my_array[5] == 5.0f);
+//
+// // remove the last one
+// stb_arr_pop(my_array);
+//
+// assert(stb_arr_len(my_array) == 6);
+
+
+#ifdef STB_MALLOC_WRAPPER
+ #define STB__PARAMS , char *file, int line
+ #define STB__ARGS , file, line
+#else
+ #define STB__PARAMS
+ #define STB__ARGS
+#endif
+
+// calling this function allocates an empty stb_arr attached to p
+// (whereas NULL isn't attached to anything)
+STB_EXTERN void stb_arr_malloc(void **target, void *context);
+
+// call this function with a non-NULL value to have all successive
+// stbs that are created be attached to the associated parent. Note
+// that once a given stb_arr is non-empty, it stays attached to its
+// current parent, even if you call this function again.
+// it turns the previous value, so you can restore it
+STB_EXTERN void* stb_arr_malloc_parent(void *p);
+
+// simple functions written on top of other functions
+#define stb_arr_empty(a) ( stb_arr_len(a) == 0 )
+#define stb_arr_add(a) ( stb_arr_addn((a),1) )
+#define stb_arr_push(a,v) ( *stb_arr_add(a)=(v) )
+
+typedef struct
+{
+ int len, limit;
+ int stb_malloc;
+ unsigned int signature;
+} stb__arr;
+
+#define stb_arr_signature 0x51bada7b // ends with 0123 in decimal
+
+// access the header block stored before the data
+#define stb_arrhead(a) /*lint --e(826)*/ (((stb__arr *) (a)) - 1)
+#define stb_arrhead2(a) /*lint --e(826)*/ (((stb__arr *) (a)) - 1)
+
+#ifdef STB_DEBUG
+#define stb_arr_check(a) assert(!a || stb_arrhead(a)->signature == stb_arr_signature)
+#define stb_arr_check2(a) assert(!a || stb_arrhead2(a)->signature == stb_arr_signature)
+#else
+#define stb_arr_check(a) ((void) 0)
+#define stb_arr_check2(a) ((void) 0)
+#endif
+
+// ARRAY LENGTH
+
+// get the array length; special case if pointer is NULL
+#define stb_arr_len(a) (a ? stb_arrhead(a)->len : 0)
+#define stb_arr_len2(a) ((stb__arr *) (a) ? stb_arrhead2(a)->len : 0)
+#define stb_arr_lastn(a) (stb_arr_len(a)-1)
+
+// check whether a given index is valid -- tests 0 <= i < stb_arr_len(a)
+#define stb_arr_valid(a,i) (a ? (int) (i) < stb_arrhead(a)->len : 0)
+
+// change the array length so is is exactly N entries long, creating
+// uninitialized entries as needed
+#define stb_arr_setlen(a,n) \
+ (stb__arr_setlen((void **) &(a), sizeof(a[0]), (n)))
+
+// change the array length so that N is a valid index (that is, so
+// it is at least N entries long), creating uninitialized entries as needed
+#define stb_arr_makevalid(a,n) \
+ (stb_arr_len(a) < (n)+1 ? stb_arr_setlen(a,(n)+1),(a) : (a))
+
+// remove the last element of the array, returning it
+#define stb_arr_pop(a) ((stb_arr_check(a), (a))[--stb_arrhead(a)->len])
+
+// access the last element in the array
+#define stb_arr_last(a) ((stb_arr_check(a), (a))[stb_arr_len(a)-1])
+
+// is iterator at end of list?
+#define stb_arr_end(a,i) ((i) >= &(a)[stb_arr_len(a)])
+
+// (internal) change the allocated length of the array
+#define stb_arr__grow(a,n) (stb_arr_check(a), stb_arrhead(a)->len += (n))
+
+// add N new uninitialized elements to the end of the array
+#define stb_arr__addn(a,n) /*lint --e(826)*/ \
+ ((stb_arr_len(a)+(n) > stb_arrcurmax(a)) \
+ ? (stb__arr_addlen((void **) &(a),sizeof(*a),(n)),0) \
+ : ((stb_arr__grow(a,n), 0)))
+
+// add N new uninitialized elements to the end of the array, and return
+// a pointer to the first new one
+#define stb_arr_addn(a,n) (stb_arr__addn((a),n),(a)+stb_arr_len(a)-(n))
+
+// add N new uninitialized elements starting at index 'i'
+#define stb_arr_insertn(a,i,n) (stb__arr_insertn((void **) &(a), sizeof(*a), (i), (n)))
+
+// insert an element at i
+#define stb_arr_insert(a,i,v) (stb__arr_insertn((void **) &(a), sizeof(*a), (i), (1)), ((a)[i] = v))
+
+// delete N elements from the middle starting at index 'i'
+#define stb_arr_deleten(a,i,n) (stb__arr_deleten((void **) &(a), sizeof(*a), (i), (n)))
+
+// delete the i'th element
+#define stb_arr_delete(a,i) stb_arr_deleten(a,i,1)
+
+// delete the i'th element, swapping down from the end
+#define stb_arr_fastdelete(a,i) \
+ (stb_swap(&a[i], &a[stb_arrhead(a)->len-1], sizeof(*a)), stb_arr_pop(a))
+
+
+// ARRAY STORAGE
+
+// get the array maximum storage; special case if NULL
+#define stb_arrcurmax(a) (a ? stb_arrhead(a)->limit : 0)
+#define stb_arrcurmax2(a) (a ? stb_arrhead2(a)->limit : 0)
+
+// set the maxlength of the array to n in anticipation of further growth
+#define stb_arr_setsize(a,n) (stb_arr_check(a), stb__arr_setsize((void **) &(a),sizeof((a)[0]),n))
+
+// make sure maxlength is large enough for at least N new allocations
+#define stb_arr_atleast(a,n) (stb_arr_len(a)+(n) > stb_arrcurmax(a) \
+ ? stb_arr_setsize((a), (n)) : 0)
+
+// make a copy of a given array (copies contents via 'memcpy'!)
+#define stb_arr_copy(a) stb__arr_copy(a, sizeof((a)[0]))
+
+// compute the storage needed to store all the elements of the array
+#define stb_arr_storage(a) (stb_arr_len(a) * sizeof((a)[0]))
+
+#define stb_arr_for(v,arr) for((v)=(arr); (v) < (arr)+stb_arr_len(arr); ++(v))
+
+// IMPLEMENTATION
+
+STB_EXTERN void stb_arr_free_(void **p);
+STB_EXTERN void *stb__arr_copy_(void *p, int elem_size);
+STB_EXTERN void stb__arr_setsize_(void **p, int size, int limit STB__PARAMS);
+STB_EXTERN void stb__arr_setlen_(void **p, int size, int newlen STB__PARAMS);
+STB_EXTERN void stb__arr_addlen_(void **p, int size, int addlen STB__PARAMS);
+STB_EXTERN void stb__arr_deleten_(void **p, int size, int loc, int n STB__PARAMS);
+STB_EXTERN void stb__arr_insertn_(void **p, int size, int loc, int n STB__PARAMS);
+
+#define stb_arr_free(p) stb_arr_free_((void **) &(p))
+#define stb__arr_copy stb__arr_copy_
+
+#ifndef STB_MALLOC_WRAPPER
+ #define stb__arr_setsize stb__arr_setsize_
+ #define stb__arr_setlen stb__arr_setlen_
+ #define stb__arr_addlen stb__arr_addlen_
+ #define stb__arr_deleten stb__arr_deleten_
+ #define stb__arr_insertn stb__arr_insertn_
+#else
+ #define stb__arr_addlen(p,s,n) stb__arr_addlen_(p,s,n,__FILE__,__LINE__)
+ #define stb__arr_setlen(p,s,n) stb__arr_setlen_(p,s,n,__FILE__,__LINE__)
+ #define stb__arr_setsize(p,s,n) stb__arr_setsize_(p,s,n,__FILE__,__LINE__)
+ #define stb__arr_deleten(p,s,i,n) stb__arr_deleten_(p,s,i,n,__FILE__,__LINE__)
+ #define stb__arr_insertn(p,s,i,n) stb__arr_insertn_(p,s,i,n,__FILE__,__LINE__)
+#endif
+
+#ifdef STB_DEFINE
+static void *stb__arr_context;
+
+void *stb_arr_malloc_parent(void *p)
+{
+ void *q = stb__arr_context;
+ stb__arr_context = p;
+ return q;
+}
+
+void stb_arr_malloc(void **target, void *context)
+{
+ stb__arr *q = (stb__arr *) stb_malloc(context, sizeof(*q));
+ q->len = q->limit = 0;
+ q->stb_malloc = 1;
+ q->signature = stb_arr_signature;
+ *target = (void *) (q+1);
+}
+
+static void * stb__arr_malloc(int size)
+{
+ if (stb__arr_context)
+ return stb_malloc(stb__arr_context, size);
+ return malloc(size);
+}
+
+void * stb__arr_copy_(void *p, int elem_size)
+{
+ stb__arr *q;
+ if (p == NULL) return p;
+ q = (stb__arr *) stb__arr_malloc(sizeof(*q) + elem_size * stb_arrhead2(p)->limit);
+ stb_arr_check2(p);
+ memcpy(q, stb_arrhead2(p), sizeof(*q) + elem_size * stb_arrhead2(p)->len);
+ q->stb_malloc = !!stb__arr_context;
+ return q+1;
+}
+
+void stb_arr_free_(void **pp)
+{
+ void *p = *pp;
+ stb_arr_check2(p);
+ if (p) {
+ stb__arr *q = stb_arrhead2(p);
+ if (q->stb_malloc)
+ stb_free(q);
+ else
+ free(q);
+ }
+ *pp = NULL;
+}
+
+static void stb__arrsize_(void **pp, int size, int limit, int len STB__PARAMS)
+{
+ void *p = *pp;
+ stb__arr *a;
+ stb_arr_check2(p);
+ if (p == NULL) {
+ if (len == 0 && size == 0) return;
+ a = (stb__arr *) stb__arr_malloc(sizeof(*a) + size*limit);
+ a->limit = limit;
+ a->len = len;
+ a->stb_malloc = !!stb__arr_context;
+ a->signature = stb_arr_signature;
+ } else {
+ a = stb_arrhead2(p);
+ a->len = len;
+ if (a->limit < limit) {
+ void *p;
+ if (a->limit >= 4 && limit < a->limit * 2)
+ limit = a->limit * 2;
+ if (a->stb_malloc)
+ p = stb_realloc(a, sizeof(*a) + limit*size);
+ else
+ #ifdef STB_MALLOC_WRAPPER
+ p = stb__realloc(a, sizeof(*a) + limit*size, file, line);
+ #else
+ p = realloc(a, sizeof(*a) + limit*size);
+ #endif
+ if (p) {
+ a = (stb__arr *) p;
+ a->limit = limit;
+ } else {
+ // throw an error!
+ }
+ }
+ }
+ a->len = stb_min(a->len, a->limit);
+ *pp = a+1;
+}
+
+void stb__arr_setsize_(void **pp, int size, int limit STB__PARAMS)
+{
+ void *p = *pp;
+ stb_arr_check2(p);
+ stb__arrsize_(pp, size, limit, stb_arr_len2(p) STB__ARGS);
+}
+
+void stb__arr_setlen_(void **pp, int size, int newlen STB__PARAMS)
+{
+ void *p = *pp;
+ stb_arr_check2(p);
+ if (stb_arrcurmax2(p) < newlen || p == NULL) {
+ stb__arrsize_(pp, size, newlen, newlen STB__ARGS);
+ } else {
+ stb_arrhead2(p)->len = newlen;
+ }
+}
+
+void stb__arr_addlen_(void **p, int size, int addlen STB__PARAMS)
+{
+ stb__arr_setlen_(p, size, stb_arr_len2(*p) + addlen STB__ARGS);
+}
+
+void stb__arr_insertn_(void **pp, int size, int i, int n STB__PARAMS)
+{
+ void *p = *pp;
+ if (n) {
+ int z;
+
+ if (p == NULL) {
+ stb__arr_addlen_(pp, size, n STB__ARGS);
+ return;
+ }
+
+ z = stb_arr_len2(p);
+ stb__arr_addlen_(&p, size, n STB__ARGS);
+ memmove((char *) p + (i+n)*size, (char *) p + i*size, size * (z-i));
+ }
+ *pp = p;
+}
+
+void stb__arr_deleten_(void **pp, int size, int i, int n STB__PARAMS)
+{
+ void *p = *pp;
+ if (n) {
+ memmove((char *) p + i*size, (char *) p + (i+n)*size, size * (stb_arr_len2(p)-(i+n)));
+ stb_arrhead2(p)->len -= n;
+ }
+ *pp = p;
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Hashing
+//
+// typical use for this is to make a power-of-two hash table.
+//
+// let N = size of table (2^n)
+// let H = stb_hash(str)
+// let S = stb_rehash(H) | 1
+//
+// then hash probe sequence P(i) for i=0..N-1
+// P(i) = (H + S*i) & (N-1)
+//
+// the idea is that H has 32 bits of hash information, but the
+// table has only, say, 2^20 entries so only uses 20 of the bits.
+// then by rehashing the original H we get 2^12 different probe
+// sequences for a given initial probe location. (So it's optimal
+// for 64K tables and its optimality decreases past that.)
+//
+// ok, so I've added something that generates _two separate_
+// 32-bit hashes simultaneously which should scale better to
+// very large tables.
+
+
+STB_EXTERN unsigned int stb_hash(char *str);
+STB_EXTERN unsigned int stb_hashptr(void *p);
+STB_EXTERN unsigned int stb_hashlen(char *str, int len);
+STB_EXTERN unsigned int stb_rehash_improved(unsigned int v);
+STB_EXTERN unsigned int stb_hash_fast(void *p, int len);
+STB_EXTERN unsigned int stb_hash2(char *str, unsigned int *hash2_ptr);
+STB_EXTERN unsigned int stb_hash_number(unsigned int hash);
+
+#define stb_rehash(x) ((x) + ((x) >> 6) + ((x) >> 19))
+
+#ifdef STB_DEFINE
+unsigned int stb_hash(char *str)
+{
+ unsigned int hash = 0;
+ while (*str)
+ hash = (hash << 7) + (hash >> 25) + *str++;
+ return hash + (hash >> 16);
+}
+
+unsigned int stb_hashlen(char *str, int len)
+{
+ unsigned int hash = 0;
+ while (len-- > 0 && *str)
+ hash = (hash << 7) + (hash >> 25) + *str++;
+ return hash + (hash >> 16);
+}
+
+unsigned int stb_hashptr(void *p)
+{
+ unsigned int x = (unsigned int)(size_t) p;
+
+ // typically lacking in low bits and high bits
+ x = stb_rehash(x);
+ x += x << 16;
+
+ // pearson's shuffle
+ x ^= x << 3;
+ x += x >> 5;
+ x ^= x << 2;
+ x += x >> 15;
+ x ^= x << 10;
+ return stb_rehash(x);
+}
+
+unsigned int stb_rehash_improved(unsigned int v)
+{
+ return stb_hashptr((void *)(size_t) v);
+}
+
+unsigned int stb_hash2(char *str, unsigned int *hash2_ptr)
+{
+ unsigned int hash1 = 0x3141592c;
+ unsigned int hash2 = 0x77f044ed;
+ while (*str) {
+ hash1 = (hash1 << 7) + (hash1 >> 25) + *str;
+ hash2 = (hash2 << 11) + (hash2 >> 21) + *str;
+ ++str;
+ }
+ *hash2_ptr = hash2 + (hash1 >> 16);
+ return hash1 + (hash2 >> 16);
+}
+
+// Paul Hsieh hash
+#define stb__get16(p) ((p)[0] | ((p)[1] << 8))
+
+unsigned int stb_hash_fast(void *p, int len)
+{
+ unsigned char *q = (unsigned char *) p;
+ unsigned int hash = len;
+
+ if (len <= 0 || q == NULL) return 0;
+
+ /* Main loop */
+ for (;len > 3; len -= 4) {
+ unsigned int val;
+ hash += stb__get16(q);
+ val = (stb__get16(q+2) << 11);
+ hash = (hash << 16) ^ hash ^ val;
+ q += 4;
+ hash += hash >> 11;
+ }
+
+ /* Handle end cases */
+ switch (len) {
+ case 3: hash += stb__get16(q);
+ hash ^= hash << 16;
+ hash ^= q[2] << 18;
+ hash += hash >> 11;
+ break;
+ case 2: hash += stb__get16(q);
+ hash ^= hash << 11;
+ hash += hash >> 17;
+ break;
+ case 1: hash += q[0];
+ hash ^= hash << 10;
+ hash += hash >> 1;
+ break;
+ case 0: break;
+ }
+
+ /* Force "avalanching" of final 127 bits */
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+
+ return hash;
+}
+
+unsigned int stb_hash_number(unsigned int hash)
+{
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+ return hash;
+}
+
+#endif
+
+#ifdef STB_PERFECT_HASH
+//////////////////////////////////////////////////////////////////////////////
+//
+// Perfect hashing for ints/pointers
+//
+// This is mainly useful for making faster pointer-indexed tables
+// that don't change frequently. E.g. for stb_ischar().
+//
+
+typedef struct
+{
+ stb_uint32 addend;
+ stb_uint multiplicand;
+ stb_uint b_mask;
+ stb_uint8 small_bmap[16];
+ stb_uint16 *large_bmap;
+
+ stb_uint table_mask;
+ stb_uint32 *table;
+} stb_perfect;
+
+STB_EXTERN int stb_perfect_create(stb_perfect *,unsigned int*,int n);
+STB_EXTERN void stb_perfect_destroy(stb_perfect *);
+STB_EXTERN int stb_perfect_hash(stb_perfect *, unsigned int x);
+extern int stb_perfect_hash_max_failures;
+
+#ifdef STB_DEFINE
+
+int stb_perfect_hash_max_failures;
+
+int stb_perfect_hash(stb_perfect *p, unsigned int x)
+{
+ stb_uint m = x * p->multiplicand;
+ stb_uint y = x >> 16;
+ stb_uint bv = (m >> 24) + y;
+ stb_uint av = (m + y) >> 12;
+ if (p->table == NULL) return -1; // uninitialized table fails
+ bv &= p->b_mask;
+ av &= p->table_mask;
+ if (p->large_bmap)
+ av ^= p->large_bmap[bv];
+ else
+ av ^= p->small_bmap[bv];
+ return p->table[av] == x ? av : -1;
+}
+
+static void stb__perfect_prehash(stb_perfect *p, stb_uint x, stb_uint16 *a, stb_uint16 *b)
+{
+ stb_uint m = x * p->multiplicand;
+ stb_uint y = x >> 16;
+ stb_uint bv = (m >> 24) + y;
+ stb_uint av = (m + y) >> 12;
+ bv &= p->b_mask;
+ av &= p->table_mask;
+ *b = bv;
+ *a = av;
+}
+
+static unsigned long stb__perfect_rand(void)
+{
+ static unsigned long stb__rand;
+ stb__rand = stb__rand * 2147001325 + 715136305;
+ return 0x31415926 ^ ((stb__rand >> 16) + (stb__rand << 16));
+}
+
+typedef struct {
+ unsigned short count;
+ unsigned short b;
+ unsigned short map;
+ unsigned short *entries;
+} stb__slot;
+
+static int stb__slot_compare(const void *p, const void *q)
+{
+ stb__slot *a = (stb__slot *) p;
+ stb__slot *b = (stb__slot *) q;
+ return a->count > b->count ? -1 : a->count < b->count; // sort large to small
+}
+
+int stb_perfect_create(stb_perfect *p, unsigned int *v, int n)
+{
+ unsigned int buffer1[64], buffer2[64], buffer3[64], buffer4[64], buffer5[32];
+ unsigned short *as = (unsigned short *) stb_temp(buffer1, sizeof(*v)*n);
+ unsigned short *bs = (unsigned short *) stb_temp(buffer2, sizeof(*v)*n);
+ unsigned short *entries = (unsigned short *) stb_temp(buffer4, sizeof(*entries) * n);
+ int size = 1 << stb_log2_ceil(n), bsize=8;
+ int failure = 0,i,j,k;
+
+ assert(n <= 32768);
+ p->large_bmap = NULL;
+
+ for(;;) {
+ stb__slot *bcount = (stb__slot *) stb_temp(buffer3, sizeof(*bcount) * bsize);
+ unsigned short *bloc = (unsigned short *) stb_temp(buffer5, sizeof(*bloc) * bsize);
+ unsigned short *e;
+ int bad=0;
+
+ p->addend = stb__perfect_rand();
+ p->multiplicand = stb__perfect_rand() | 1;
+ p->table_mask = size-1;
+ p->b_mask = bsize-1;
+ p->table = (stb_uint32 *) malloc(size * sizeof(*p->table));
+
+ for (i=0; i < bsize; ++i) {
+ bcount[i].b = i;
+ bcount[i].count = 0;
+ bcount[i].map = 0;
+ }
+ for (i=0; i < n; ++i) {
+ stb__perfect_prehash(p, v[i], as+i, bs+i);
+ ++bcount[bs[i]].count;
+ }
+ qsort(bcount, bsize, sizeof(*bcount), stb__slot_compare);
+ e = entries; // now setup up their entries index
+ for (i=0; i < bsize; ++i) {
+ bcount[i].entries = e;
+ e += bcount[i].count;
+ bcount[i].count = 0;
+ bloc[bcount[i].b] = i;
+ }
+ // now fill them out
+ for (i=0; i < n; ++i) {
+ int b = bs[i];
+ int w = bloc[b];
+ bcount[w].entries[bcount[w].count++] = i;
+ }
+ stb_tempfree(buffer5,bloc);
+ // verify
+ for (i=0; i < bsize; ++i)
+ for (j=0; j < bcount[i].count; ++j)
+ assert(bs[bcount[i].entries[j]] == bcount[i].b);
+ memset(p->table, 0, size*sizeof(*p->table));
+
+ // check if any b has duplicate a
+ for (i=0; i < bsize; ++i) {
+ if (bcount[i].count > 1) {
+ for (j=0; j < bcount[i].count; ++j) {
+ if (p->table[as[bcount[i].entries[j]]])
+ bad = 1;
+ p->table[as[bcount[i].entries[j]]] = 1;
+ }
+ for (j=0; j < bcount[i].count; ++j) {
+ p->table[as[bcount[i].entries[j]]] = 0;
+ }
+ if (bad) break;
+ }
+ }
+
+ if (!bad) {
+ // go through the bs and populate the table, first fit
+ for (i=0; i < bsize; ++i) {
+ if (bcount[i].count) {
+ // go through the candidate table[b] values
+ for (j=0; j < size; ++j) {
+ // go through the a values and see if they fit
+ for (k=0; k < bcount[i].count; ++k) {
+ int a = as[bcount[i].entries[k]];
+ if (p->table[(a^j)&p->table_mask]) {
+ break; // fails
+ }
+ }
+ // if succeeded, accept
+ if (k == bcount[i].count) {
+ bcount[i].map = j;
+ for (k=0; k < bcount[i].count; ++k) {
+ int a = as[bcount[i].entries[k]];
+ p->table[(a^j)&p->table_mask] = 1;
+ }
+ break;
+ }
+ }
+ if (j == size)
+ break; // no match for i'th entry, so break out in failure
+ }
+ }
+ if (i == bsize) {
+ // success... fill out map
+ if (bsize <= 16 && size <= 256) {
+ p->large_bmap = NULL;
+ for (i=0; i < bsize; ++i)
+ p->small_bmap[bcount[i].b] = (stb_uint8) bcount[i].map;
+ } else {
+ p->large_bmap = (unsigned short *) malloc(sizeof(*p->large_bmap) * bsize);
+ for (i=0; i < bsize; ++i)
+ p->large_bmap[bcount[i].b] = bcount[i].map;
+ }
+
+ // initialize table to v[0], so empty slots will fail
+ for (i=0; i < size; ++i)
+ p->table[i] = v[0];
+
+ for (i=0; i < n; ++i)
+ if (p->large_bmap)
+ p->table[as[i] ^ p->large_bmap[bs[i]]] = v[i];
+ else
+ p->table[as[i] ^ p->small_bmap[bs[i]]] = v[i];
+
+ // and now validate that none of them collided
+ for (i=0; i < n; ++i)
+ assert(stb_perfect_hash(p, v[i]) >= 0);
+
+ stb_tempfree(buffer3, bcount);
+ break;
+ }
+ }
+ free(p->table);
+ p->table = NULL;
+ stb_tempfree(buffer3, bcount);
+
+ ++failure;
+ if (failure >= 4 && bsize < size) bsize *= 2;
+ if (failure >= 8 && (failure & 3) == 0 && size < 4*n) {
+ size *= 2;
+ bsize *= 2;
+ }
+ if (failure == 6) {
+ // make sure the input data is unique, so we don't infinite loop
+ unsigned int *data = (unsigned int *) stb_temp(buffer3, n * sizeof(*data));
+ memcpy(data, v, sizeof(*data) * n);
+ qsort(data, n, sizeof(*data), stb_intcmp(0));
+ for (i=1; i < n; ++i) {
+ if (data[i] == data[i-1])
+ size = 0; // size is return value, so 0 it
+ }
+ stb_tempfree(buffer3, data);
+ if (!size) break;
+ }
+ }
+
+ if (failure > stb_perfect_hash_max_failures)
+ stb_perfect_hash_max_failures = failure;
+
+ stb_tempfree(buffer1, as);
+ stb_tempfree(buffer2, bs);
+ stb_tempfree(buffer4, entries);
+
+ return size;
+}
+
+void stb_perfect_destroy(stb_perfect *p)
+{
+ if (p->large_bmap) free(p->large_bmap);
+ if (p->table ) free(p->table);
+ p->large_bmap = NULL;
+ p->table = NULL;
+ p->b_mask = 0;
+ p->table_mask = 0;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Perfect hash clients
+
+STB_EXTERN int stb_ischar(char s, char *set);
+
+#ifdef STB_DEFINE
+
+int stb_ischar(char c, char *set)
+{
+ static unsigned char bit[8] = { 1,2,4,8,16,32,64,128 };
+ static stb_perfect p;
+ static unsigned char (*tables)[256];
+ static char ** sets = NULL;
+
+ int z = stb_perfect_hash(&p, (int)(size_t) set);
+ if (z < 0) {
+ int i,k,n,j,f;
+ // special code that means free all existing data
+ if (set == NULL) {
+ stb_arr_free(sets);
+ free(tables);
+ tables = NULL;
+ stb_perfect_destroy(&p);
+ return 0;
+ }
+ stb_arr_push(sets, set);
+ stb_perfect_destroy(&p);
+ n = stb_perfect_create(&p, (unsigned int *) (char **) sets, stb_arr_len(sets));
+ assert(n != 0);
+ k = (n+7) >> 3;
+ tables = (unsigned char (*)[256]) realloc(tables, sizeof(*tables) * k);
+ memset(tables, 0, sizeof(*tables) * k);
+ for (i=0; i < stb_arr_len(sets); ++i) {
+ k = stb_perfect_hash(&p, (int)(size_t) sets[i]);
+ assert(k >= 0);
+ n = k >> 3;
+ f = bit[k&7];
+ for (j=0; !j || sets[i][j]; ++j) {
+ tables[n][(unsigned char) sets[i][j]] |= f;
+ }
+ }
+ z = stb_perfect_hash(&p, (int)(size_t) set);
+ }
+ return tables[z >> 3][(unsigned char) c] & bit[z & 7];
+}
+
+#endif
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Instantiated data structures
+//
+// This is an attempt to implement a templated data structure.
+//
+// Hash table: call stb_define_hash(TYPE,N,KEY,K1,K2,HASH,VALUE)
+// TYPE -- will define a structure type containing the hash table
+// N -- the name, will prefix functions named:
+// N create
+// N destroy
+// N get
+// N set, N add, N update,
+// N remove
+// KEY -- the type of the key. 'x == y' must be valid
+// K1,K2 -- keys never used by the app, used as flags in the hashtable
+// HASH -- a piece of code ending with 'return' that hashes key 'k'
+// VALUE -- the type of the value. 'x = y' must be valid
+//
+// Note that stb_define_hash_base can be used to define more sophisticated
+// hash tables, e.g. those that make copies of the key or use special
+// comparisons (e.g. strcmp).
+
+#define STB_(prefix,name) stb__##prefix##name
+#define STB__(prefix,name) prefix##name
+#define STB__use(x) x
+#define STB__skip(x)
+
+#define stb_declare_hash(PREFIX,TYPE,N,KEY,VALUE) \
+ typedef struct stb__st_##TYPE TYPE;\
+ PREFIX int STB__(N, init)(TYPE *h, int count);\
+ PREFIX int STB__(N, memory_usage)(TYPE *h);\
+ PREFIX TYPE * STB__(N, create)(void);\
+ PREFIX TYPE * STB__(N, copy)(TYPE *h);\
+ PREFIX void STB__(N, destroy)(TYPE *h);\
+ PREFIX int STB__(N,get_flag)(TYPE *a, KEY k, VALUE *v);\
+ PREFIX VALUE STB__(N,get)(TYPE *a, KEY k);\
+ PREFIX int STB__(N, set)(TYPE *a, KEY k, VALUE v);\
+ PREFIX int STB__(N, add)(TYPE *a, KEY k, VALUE v);\
+ PREFIX int STB__(N, update)(TYPE*a,KEY k,VALUE v);\
+ PREFIX int STB__(N, remove)(TYPE *a, KEY k, VALUE *v);
+
+#define STB_nocopy(x) (x)
+#define STB_nodelete(x) 0
+#define STB_nofields
+#define STB_nonullvalue(x)
+#define STB_nullvalue(x) x
+#define STB_safecompare(x) x
+#define STB_nosafe(x)
+#define STB_noprefix
+
+#ifdef __GNUC__
+#define STB__nogcc(x)
+#else
+#define STB__nogcc(x) x
+#endif
+
+#define stb_define_hash_base(PREFIX,TYPE,FIELDS,N,NC,LOAD_FACTOR, \
+ KEY,EMPTY,DEL,COPY,DISPOSE,SAFE, \
+ VCOMPARE,CCOMPARE,HASH, \
+ VALUE,HASVNULL,VNULL) \
+ \
+typedef struct \
+{ \
+ KEY k; \
+ VALUE v; \
+} STB_(N,_hashpair); \
+ \
+STB__nogcc( typedef struct stb__st_##TYPE TYPE; ) \
+struct stb__st_##TYPE { \
+ FIELDS \
+ STB_(N,_hashpair) *table; \
+ unsigned int mask; \
+ int count, limit; \
+ int deleted; \
+ \
+ int delete_threshhold; \
+ int grow_threshhold; \
+ int shrink_threshhold; \
+ unsigned char alloced, has_empty, has_del; \
+ VALUE ev; VALUE dv; \
+}; \
+ \
+static unsigned int STB_(N, hash)(KEY k) \
+{ \
+ HASH \
+} \
+ \
+PREFIX int STB__(N, init)(TYPE *h, int count) \
+{ \
+ int i; \
+ if (count < 4) count = 4; \
+ h->limit = count; \
+ h->count = 0; \
+ h->mask = count-1; \
+ h->deleted = 0; \
+ h->grow_threshhold = (int) (count * LOAD_FACTOR); \
+ h->has_empty = h->has_del = 0; \
+ h->alloced = 0; \
+ if (count <= 64) \
+ h->shrink_threshhold = 0; \
+ else \
+ h->shrink_threshhold = (int) (count * (LOAD_FACTOR/2.25)); \
+ h->delete_threshhold = (int) (count * (1-LOAD_FACTOR)/2); \
+ h->table = (STB_(N,_hashpair)*) malloc(sizeof(h->table[0]) * count); \
+ if (h->table == NULL) return 0; \
+ /* ideally this gets turned into a memset32 automatically */ \
+ for (i=0; i < count; ++i) \
+ h->table[i].k = EMPTY; \
+ return 1; \
+} \
+ \
+PREFIX int STB__(N, memory_usage)(TYPE *h) \
+{ \
+ return sizeof(*h) + h->limit * sizeof(h->table[0]); \
+} \
+ \
+PREFIX TYPE * STB__(N, create)(void) \
+{ \
+ TYPE *h = (TYPE *) malloc(sizeof(*h)); \
+ if (h) { \
+ if (STB__(N, init)(h, 16)) \
+ h->alloced = 1; \
+ else { free(h); h=NULL; } \
+ } \
+ return h; \
+} \
+ \
+PREFIX void STB__(N, destroy)(TYPE *a) \
+{ \
+ int i; \
+ for (i=0; i < a->limit; ++i) \
+ if (!CCOMPARE(a->table[i].k,EMPTY) && !CCOMPARE(a->table[i].k, DEL)) \
+ DISPOSE(a->table[i].k); \
+ free(a->table); \
+ if (a->alloced) \
+ free(a); \
+} \
+ \
+static void STB_(N, rehash)(TYPE *a, int count); \
+ \
+PREFIX int STB__(N,get_flag)(TYPE *a, KEY k, VALUE *v) \
+{ \
+ unsigned int h = STB_(N, hash)(k); \
+ unsigned int n = h & a->mask, s; \
+ if (CCOMPARE(k,EMPTY)){ if (a->has_empty) *v = a->ev; return a->has_empty;}\
+ if (CCOMPARE(k,DEL)) { if (a->has_del ) *v = a->dv; return a->has_del; }\
+ if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \
+ SAFE(if (!CCOMPARE(a->table[n].k,DEL))) \
+ if (VCOMPARE(a->table[n].k,k)) { *v = a->table[n].v; return 1; } \
+ s = stb_rehash(h) | 1; \
+ for(;;) { \
+ n = (n + s) & a->mask; \
+ if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \
+ SAFE(if (CCOMPARE(a->table[n].k,DEL)) continue;) \
+ if (VCOMPARE(a->table[n].k,k)) \
+ { *v = a->table[n].v; return 1; } \
+ } \
+} \
+ \
+HASVNULL( \
+ PREFIX VALUE STB__(N,get)(TYPE *a, KEY k) \
+ { \
+ VALUE v; \
+ if (STB__(N,get_flag)(a,k,&v)) return v; \
+ else return VNULL; \
+ } \
+) \
+ \
+PREFIX int STB__(N,getkey)(TYPE *a, KEY k, KEY *kout) \
+{ \
+ unsigned int h = STB_(N, hash)(k); \
+ unsigned int n = h & a->mask, s; \
+ if (CCOMPARE(k,EMPTY)||CCOMPARE(k,DEL)) return 0; \
+ if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \
+ SAFE(if (!CCOMPARE(a->table[n].k,DEL))) \
+ if (VCOMPARE(a->table[n].k,k)) { *kout = a->table[n].k; return 1; } \
+ s = stb_rehash(h) | 1; \
+ for(;;) { \
+ n = (n + s) & a->mask; \
+ if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \
+ SAFE(if (CCOMPARE(a->table[n].k,DEL)) continue;) \
+ if (VCOMPARE(a->table[n].k,k)) \
+ { *kout = a->table[n].k; return 1; } \
+ } \
+} \
+ \
+static int STB_(N,addset)(TYPE *a, KEY k, VALUE v, \
+ int allow_new, int allow_old, int copy) \
+{ \
+ unsigned int h = STB_(N, hash)(k); \
+ unsigned int n = h & a->mask; \
+ int b = -1; \
+ if (CCOMPARE(k,EMPTY)) { \
+ if (a->has_empty ? allow_old : allow_new) { \
+ n=a->has_empty; a->ev = v; a->has_empty = 1; return !n; \
+ } else return 0; \
+ } \
+ if (CCOMPARE(k,DEL)) { \
+ if (a->has_del ? allow_old : allow_new) { \
+ n=a->has_del; a->dv = v; a->has_del = 1; return !n; \
+ } else return 0; \
+ } \
+ if (!CCOMPARE(a->table[n].k, EMPTY)) { \
+ unsigned int s; \
+ if (CCOMPARE(a->table[n].k, DEL)) \
+ b = n; \
+ else if (VCOMPARE(a->table[n].k,k)) { \
+ if (allow_old) \
+ a->table[n].v = v; \
+ return !allow_new; \
+ } \
+ s = stb_rehash(h) | 1; \
+ for(;;) { \
+ n = (n + s) & a->mask; \
+ if (CCOMPARE(a->table[n].k, EMPTY)) break; \
+ if (CCOMPARE(a->table[n].k, DEL)) { \
+ if (b < 0) b = n; \
+ } else if (VCOMPARE(a->table[n].k,k)) { \
+ if (allow_old) \
+ a->table[n].v = v; \
+ return !allow_new; \
+ } \
+ } \
+ } \
+ if (!allow_new) return 0; \
+ if (b < 0) b = n; else --a->deleted; \
+ a->table[b].k = copy ? COPY(k) : k; \
+ a->table[b].v = v; \
+ ++a->count; \
+ if (a->count > a->grow_threshhold) \
+ STB_(N,rehash)(a, a->limit*2); \
+ return 1; \
+} \
+ \
+PREFIX int STB__(N, set)(TYPE *a, KEY k, VALUE v){return STB_(N,addset)(a,k,v,1,1,1);}\
+PREFIX int STB__(N, add)(TYPE *a, KEY k, VALUE v){return STB_(N,addset)(a,k,v,1,0,1);}\
+PREFIX int STB__(N, update)(TYPE*a,KEY k,VALUE v){return STB_(N,addset)(a,k,v,0,1,1);}\
+ \
+PREFIX int STB__(N, remove)(TYPE *a, KEY k, VALUE *v) \
+{ \
+ unsigned int h = STB_(N, hash)(k); \
+ unsigned int n = h & a->mask, s; \
+ if (CCOMPARE(k,EMPTY)) { if (a->has_empty) { if(v)*v = a->ev; a->has_empty=0; return 1; } return 0; } \
+ if (CCOMPARE(k,DEL)) { if (a->has_del ) { if(v)*v = a->dv; a->has_del =0; return 1; } return 0; } \
+ if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \
+ if (SAFE(CCOMPARE(a->table[n].k,DEL) || ) !VCOMPARE(a->table[n].k,k)) { \
+ s = stb_rehash(h) | 1; \
+ for(;;) { \
+ n = (n + s) & a->mask; \
+ if (CCOMPARE(a->table[n].k,EMPTY)) return 0; \
+ SAFE(if (CCOMPARE(a->table[n].k, DEL)) continue;) \
+ if (VCOMPARE(a->table[n].k,k)) break; \
+ } \
+ } \
+ DISPOSE(a->table[n].k); \
+ a->table[n].k = DEL; \
+ --a->count; \
+ ++a->deleted; \
+ if (v != NULL) \
+ *v = a->table[n].v; \
+ if (a->count < a->shrink_threshhold) \
+ STB_(N, rehash)(a, a->limit >> 1); \
+ else if (a->deleted > a->delete_threshhold) \
+ STB_(N, rehash)(a, a->limit); \
+ return 1; \
+} \
+ \
+PREFIX TYPE * STB__(NC, copy)(TYPE *a) \
+{ \
+ int i; \
+ TYPE *h = (TYPE *) malloc(sizeof(*h)); \
+ if (!h) return NULL; \
+ if (!STB__(N, init)(h, a->limit)) { free(h); return NULL; } \
+ h->count = a->count; \
+ h->deleted = a->deleted; \
+ h->alloced = 1; \
+ h->ev = a->ev; h->dv = a->dv; \
+ h->has_empty = a->has_empty; h->has_del = a->has_del; \
+ memcpy(h->table, a->table, h->limit * sizeof(h->table[0])); \
+ for (i=0; i < a->limit; ++i) \
+ if (!CCOMPARE(h->table[i].k,EMPTY) && !CCOMPARE(h->table[i].k,DEL)) \
+ h->table[i].k = COPY(h->table[i].k); \
+ return h; \
+} \
+ \
+static void STB_(N, rehash)(TYPE *a, int count) \
+{ \
+ int i; \
+ TYPE b; \
+ STB__(N, init)(&b, count); \
+ for (i=0; i < a->limit; ++i) \
+ if (!CCOMPARE(a->table[i].k,EMPTY) && !CCOMPARE(a->table[i].k,DEL)) \
+ STB_(N,addset)(&b, a->table[i].k, a->table[i].v,1,1,0); \
+ free(a->table); \
+ a->table = b.table; \
+ a->mask = b.mask; \
+ a->count = b.count; \
+ a->limit = b.limit; \
+ a->deleted = b.deleted; \
+ a->delete_threshhold = b.delete_threshhold; \
+ a->grow_threshhold = b.grow_threshhold; \
+ a->shrink_threshhold = b.shrink_threshhold; \
+}
+
+#define STB_equal(a,b) ((a) == (b))
+
+#define stb_define_hash(TYPE,N,KEY,EMPTY,DEL,HASH,VALUE) \
+ stb_define_hash_base(STB_noprefix, TYPE,STB_nofields,N,NC,0.85f, \
+ KEY,EMPTY,DEL,STB_nocopy,STB_nodelete,STB_nosafe, \
+ STB_equal,STB_equal,HASH, \
+ VALUE,STB_nonullvalue,0)
+
+#define stb_define_hash_vnull(TYPE,N,KEY,EMPTY,DEL,HASH,VALUE,VNULL) \
+ stb_define_hash_base(STB_noprefix, TYPE,STB_nofields,N,NC,0.85f, \
+ KEY,EMPTY,DEL,STB_nocopy,STB_nodelete,STB_nosafe, \
+ STB_equal,STB_equal,HASH, \
+ VALUE,STB_nullvalue,VNULL)
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_ptrmap
+//
+// An stb_ptrmap data structure is an O(1) hash table between pointers. One
+// application is to let you store "extra" data associated with pointers,
+// which is why it was originally called stb_extra.
+
+stb_declare_hash(STB_EXTERN, stb_ptrmap, stb_ptrmap_, void *, void *)
+stb_declare_hash(STB_EXTERN, stb_idict, stb_idict_, stb_int32, stb_int32)
+stb_declare_hash(STB_EXTERN, stb_uidict, stbi_uidict_, stb_uint32, stb_uint32)
+
+STB_EXTERN void stb_ptrmap_delete(stb_ptrmap *e, void (*free_func)(void *));
+STB_EXTERN stb_ptrmap *stb_ptrmap_new(void);
+
+STB_EXTERN stb_idict * stb_idict_new_size(int size);
+STB_EXTERN void stb_idict_remove_all(stb_idict *e);
+STB_EXTERN void stb_uidict_reset(stb_uidict *e);
+
+#ifdef STB_DEFINE
+
+#define STB_EMPTY ((void *) 2)
+#define STB_EDEL ((void *) 6)
+
+stb_define_hash_base(STB_noprefix,stb_ptrmap, STB_nofields, stb_ptrmap_,stb_ptrmap_,0.85f,
+ void *,STB_EMPTY,STB_EDEL,STB_nocopy,STB_nodelete,STB_nosafe,
+ STB_equal,STB_equal,return stb_hashptr(k);,
+ void *,STB_nullvalue,NULL)
+
+stb_ptrmap *stb_ptrmap_new(void)
+{
+ return stb_ptrmap_create();
+}
+
+void stb_ptrmap_delete(stb_ptrmap *e, void (*free_func)(void *))
+{
+ int i;
+ if (free_func)
+ for (i=0; i < e->limit; ++i)
+ if (e->table[i].k != STB_EMPTY && e->table[i].k != STB_EDEL) {
+ if (free_func == free)
+ free(e->table[i].v); // allow STB_MALLOC_WRAPPER to operate
+ else
+ free_func(e->table[i].v);
+ }
+ stb_ptrmap_destroy(e);
+}
+
+// extra fields needed for stua_dict
+#define STB_IEMPTY ((int) 1)
+#define STB_IDEL ((int) 3)
+stb_define_hash_base(STB_noprefix, stb_idict, short type; short gc; STB_nofields, stb_idict_,stb_idict_,0.95f,
+ stb_int32,STB_IEMPTY,STB_IDEL,STB_nocopy,STB_nodelete,STB_nosafe,
+ STB_equal,STB_equal,
+ return stb_rehash_improved(k);,stb_int32,STB_nonullvalue,0)
+
+stb_idict * stb_idict_new_size(int size)
+{
+ stb_idict *e = (stb_idict *) malloc(sizeof(*e));
+ if (e) {
+ if (!stb_is_pow2(size))
+ size = 1 << stb_log2_ceil(size);
+ stb_idict_init(e, size);
+ e->alloced = 1;
+ }
+ return e;
+}
+
+void stb_idict_remove_all(stb_idict *e)
+{
+ int n;
+ for (n=0; n < e->limit; ++n)
+ e->table[n].k = STB_IEMPTY;
+ e->has_empty = e->has_del = 0;
+ e->count = 0;
+ e->deleted = 0;
+}
+
+stb_define_hash_base(STB_noprefix, stb_uidict, STB_nofields, stb_uidict_,stb_uidict_,0.85f,
+ stb_int32,0xffffffff,0xfffffffe,STB_nocopy,STB_nodelete,STB_nosafe,
+ STB_equal,STB_equal,
+ return stb_rehash_improved(k);,stb_uint32,STB_nonullvalue,0)
+
+void stb_uidict_reset(stb_uidict *e)
+{
+ int n;
+ for (n=0; n < e->limit; ++n)
+ e->table[n].k = 0xffffffff;
+ e->has_empty = e->has_del = 0;
+ e->count = 0;
+ e->deleted = 0;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_sparse_ptr_matrix
+//
+// An stb_ptrmap data structure is an O(1) hash table storing an arbitrary
+// block of data for a given pair of pointers.
+//
+// If create=0, returns
+
+typedef struct stb__st_stb_spmatrix stb_spmatrix;
+
+STB_EXTERN stb_spmatrix * stb_sparse_ptr_matrix_new(int val_size);
+STB_EXTERN void stb_sparse_ptr_matrix_free(stb_spmatrix *z);
+STB_EXTERN void * stb_sparse_ptr_matrix_get(stb_spmatrix *z, void *a, void *b, int create);
+
+#ifdef STB_DEFINE
+typedef struct
+{
+ void *a;
+ void *b;
+} stb__ptrpair;
+
+static stb__ptrpair stb__ptrpair_empty = { (void *) 1, (void *) 1 };
+static stb__ptrpair stb__ptrpair_del = { (void *) 2, (void *) 2 };
+
+#define STB__equal_ptrpair(x,y) ((x).a == (y).a && (x).b == (y).b)
+
+stb_define_hash_base(STB_noprefix, stb_spmatrix, int val_size; void *arena;, stb__spmatrix_,stb__spmatrix_, 0.85,
+ stb__ptrpair, stb__ptrpair_empty, stb__ptrpair_del,
+ STB_nocopy, STB_nodelete, STB_nosafe,
+ STB__equal_ptrpair, STB__equal_ptrpair, return stb_rehash(stb_hashptr(k.a))+stb_hashptr(k.b);,
+ void *, STB_nullvalue, 0)
+
+stb_spmatrix *stb_sparse_ptr_matrix_new(int val_size)
+{
+ stb_spmatrix *m = stb__spmatrix_create();
+ if (m) m->val_size = val_size;
+ if (m) m->arena = stb_malloc_global(1);
+ return m;
+}
+
+void stb_sparse_ptr_matrix_free(stb_spmatrix *z)
+{
+ if (z->arena) stb_free(z->arena);
+ stb__spmatrix_destroy(z);
+}
+
+void *stb_sparse_ptr_matrix_get(stb_spmatrix *z, void *a, void *b, int create)
+{
+ stb__ptrpair t = { a,b };
+ void *data = stb__spmatrix_get(z, t);
+ if (!data && create) {
+ data = stb_malloc_raw(z->arena, z->val_size);
+ if (!data) return NULL;
+ memset(data, 0, z->val_size);
+ stb__spmatrix_add(z, t, data);
+ }
+ return data;
+}
+#endif
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// SDICT: Hash Table for Strings (symbol table)
+//
+// if "use_arena=1", then strings will be copied
+// into blocks and never freed until the sdict is freed;
+// otherwise they're malloc()ed and free()d on the fly.
+// (specify use_arena=1 if you never stb_sdict_remove)
+
+stb_declare_hash(STB_EXTERN, stb_sdict, stb_sdict_, char *, void *)
+
+STB_EXTERN stb_sdict * stb_sdict_new(int use_arena);
+STB_EXTERN stb_sdict * stb_sdict_copy(stb_sdict*);
+STB_EXTERN void stb_sdict_delete(stb_sdict *);
+STB_EXTERN void * stb_sdict_change(stb_sdict *, char *str, void *p);
+STB_EXTERN int stb_sdict_count(stb_sdict *d);
+
+STB_EXTERN int stb_sdict_internal_limit(stb_sdict *d);
+STB_EXTERN char * stb_sdict_internal_key(stb_sdict *d, int n);
+STB_EXTERN void * stb_sdict_internal_value(stb_sdict *d, int n);
+
+#define stb_sdict_for(d,i,q,z) \
+ for(i=0; i < stb_sdict_internal_limit(d) ? (q=stb_sdict_internal_key(d,i),z=stb_sdict_internal_value(d,i),1) : 0; ++i) \
+ if (q==NULL||q==(void *) 1);else // reversed makes macro friendly
+
+#ifdef STB_DEFINE
+
+// if in same translation unit, for speed, don't call accessors
+#undef stb_sdict_for
+#define stb_sdict_for(d,i,q,z) \
+ for(i=0; i < (d)->limit ? (q=(d)->table[i].k,z=(d)->table[i].v,1) : 0; ++i) \
+ if (q==NULL||q==(void *) 1);else // reversed makes macro friendly
+
+#define STB_DEL ((void *) 1)
+#define STB_SDEL ((char *) 1)
+
+#define stb_sdict__copy(x) \
+ stb_p_strcpy_s(a->arena ? stb_malloc_string(a->arena, strlen(x)+1) \
+ : (char *) malloc(strlen(x)+1), strlen(x)+1, x)
+
+#define stb_sdict__dispose(x) if (!a->arena) free(x)
+
+stb_define_hash_base(STB_noprefix, stb_sdict, void*arena;, stb_sdict_,stb_sdictinternal_, 0.85f,
+ char *, NULL, STB_SDEL, stb_sdict__copy, stb_sdict__dispose,
+ STB_safecompare, !strcmp, STB_equal, return stb_hash(k);,
+ void *, STB_nullvalue, NULL)
+
+int stb_sdict_count(stb_sdict *a)
+{
+ return a->count;
+}
+
+int stb_sdict_internal_limit(stb_sdict *a)
+{
+ return a->limit;
+}
+char* stb_sdict_internal_key(stb_sdict *a, int n)
+{
+ return a->table[n].k;
+}
+void* stb_sdict_internal_value(stb_sdict *a, int n)
+{
+ return a->table[n].v;
+}
+
+stb_sdict * stb_sdict_new(int use_arena)
+{
+ stb_sdict *d = stb_sdict_create();
+ if (d == NULL) return NULL;
+ d->arena = use_arena ? stb_malloc_global(1) : NULL;
+ return d;
+}
+
+stb_sdict* stb_sdict_copy(stb_sdict *old)
+{
+ stb_sdict *n;
+ void *old_arena = old->arena;
+ void *new_arena = old_arena ? stb_malloc_global(1) : NULL;
+ old->arena = new_arena;
+ n = stb_sdictinternal_copy(old);
+ old->arena = old_arena;
+ if (n)
+ n->arena = new_arena;
+ else if (new_arena)
+ stb_free(new_arena);
+ return n;
+}
+
+
+void stb_sdict_delete(stb_sdict *d)
+{
+ if (d->arena)
+ stb_free(d->arena);
+ stb_sdict_destroy(d);
+}
+
+void * stb_sdict_change(stb_sdict *d, char *str, void *p)
+{
+ void *q = stb_sdict_get(d, str);
+ stb_sdict_set(d, str, p);
+ return q;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Instantiated data structures
+//
+// This is an attempt to implement a templated data structure.
+// What you do is define a struct foo, and then include several
+// pointer fields to struct foo in your struct. Then you call
+// the instantiator, which creates the functions that implement
+// the data structure. This requires massive undebuggable #defines,
+// so we limit the cases where we do this.
+//
+// AA tree is an encoding of a 2-3 tree whereas RB trees encode a 2-3-4 tree;
+// much simpler code due to fewer cases.
+
+#define stb__bst_parent(x) x
+#define stb__bst_noparent(x)
+
+#define stb_bst_fields(N) \
+ *STB_(N,left), *STB_(N,right); \
+ unsigned char STB_(N,level)
+
+#define stb_bst_fields_parent(N) \
+ *STB_(N,left), *STB_(N,right), *STB_(N,parent); \
+ unsigned char STB_(N,level)
+
+#define STB__level(N,x) ((x) ? (x)->STB_(N,level) : 0)
+
+#define stb_bst_base(TYPE, N, TREE, M, compare, PAR) \
+ \
+static int STB_(N,_compare)(TYPE *p, TYPE *q) \
+{ \
+ compare \
+} \
+ \
+static void STB_(N,setleft)(TYPE *q, TYPE *v) \
+{ \
+ q->STB_(N,left) = v; \
+ PAR(if (v) v->STB_(N,parent) = q;) \
+} \
+ \
+static void STB_(N,setright)(TYPE *q, TYPE *v) \
+{ \
+ q->STB_(N,right) = v; \
+ PAR(if (v) v->STB_(N,parent) = q;) \
+} \
+ \
+static TYPE *STB_(N,skew)(TYPE *q) \
+{ \
+ if (q == NULL) return q; \
+ if (q->STB_(N,left) \
+ && q->STB_(N,left)->STB_(N,level) == q->STB_(N,level)) { \
+ TYPE *p = q->STB_(N,left); \
+ STB_(N,setleft)(q, p->STB_(N,right)); \
+ STB_(N,setright)(p, q); \
+ return p; \
+ } \
+ return q; \
+} \
+ \
+static TYPE *STB_(N,split)(TYPE *p) \
+{ \
+ TYPE *q = p->STB_(N,right); \
+ if (q && q->STB_(N,right) \
+ && q->STB_(N,right)->STB_(N,level) == p->STB_(N,level)) { \
+ STB_(N,setright)(p, q->STB_(N,left)); \
+ STB_(N,setleft)(q,p); \
+ ++q->STB_(N,level); \
+ return q; \
+ } \
+ return p; \
+} \
+ \
+TYPE *STB__(N,insert)(TYPE *tree, TYPE *item) \
+{ \
+ int c; \
+ if (tree == NULL) { \
+ item->STB_(N,left) = NULL; \
+ item->STB_(N,right) = NULL; \
+ item->STB_(N,level) = 1; \
+ PAR(item->STB_(N,parent) = NULL;) \
+ return item; \
+ } \
+ c = STB_(N,_compare)(item,tree); \
+ if (c == 0) { \
+ if (item != tree) { \
+ STB_(N,setleft)(item, tree->STB_(N,left)); \
+ STB_(N,setright)(item, tree->STB_(N,right)); \
+ item->STB_(N,level) = tree->STB_(N,level); \
+ PAR(item->STB_(N,parent) = NULL;) \
+ } \
+ return item; \
+ } \
+ if (c < 0) \
+ STB_(N,setleft )(tree, STB__(N,insert)(tree->STB_(N,left), item)); \
+ else \
+ STB_(N,setright)(tree, STB__(N,insert)(tree->STB_(N,right), item)); \
+ tree = STB_(N,skew)(tree); \
+ tree = STB_(N,split)(tree); \
+ PAR(tree->STB_(N,parent) = NULL;) \
+ return tree; \
+} \
+ \
+TYPE *STB__(N,remove)(TYPE *tree, TYPE *item) \
+{ \
+ static TYPE *delnode, *leaf, *restore; \
+ if (tree == NULL) return NULL; \
+ leaf = tree; \
+ if (STB_(N,_compare)(item, tree) < 0) { \
+ STB_(N,setleft)(tree, STB__(N,remove)(tree->STB_(N,left), item)); \
+ } else { \
+ TYPE *r; \
+ delnode = tree; \
+ r = STB__(N,remove)(tree->STB_(N,right), item); \
+ /* maybe move 'leaf' up to this location */ \
+ if (restore == tree) { tree = leaf; leaf = restore = NULL; } \
+ STB_(N,setright)(tree,r); \
+ assert(tree->STB_(N,right) != tree); \
+ } \
+ if (tree == leaf) { \
+ if (delnode == item) { \
+ tree = tree->STB_(N,right); \
+ assert(leaf->STB_(N,left) == NULL); \
+ /* move leaf (the right sibling) up to delnode */ \
+ STB_(N,setleft )(leaf, item->STB_(N,left )); \
+ STB_(N,setright)(leaf, item->STB_(N,right)); \
+ leaf->STB_(N,level) = item->STB_(N,level); \
+ if (leaf != item) \
+ restore = delnode; \
+ } \
+ delnode = NULL; \
+ } else { \
+ if (STB__level(N,tree->STB_(N,left) ) < tree->STB_(N,level)-1 || \
+ STB__level(N,tree->STB_(N,right)) < tree->STB_(N,level)-1) { \
+ --tree->STB_(N,level); \
+ if (STB__level(N,tree->STB_(N,right)) > tree->STB_(N,level)) \
+ tree->STB_(N,right)->STB_(N,level) = tree->STB_(N,level); \
+ tree = STB_(N,skew)(tree); \
+ STB_(N,setright)(tree, STB_(N,skew)(tree->STB_(N,right))); \
+ if (tree->STB_(N,right)) \
+ STB_(N,setright)(tree->STB_(N,right), \
+ STB_(N,skew)(tree->STB_(N,right)->STB_(N,right))); \
+ tree = STB_(N,split)(tree); \
+ if (tree->STB_(N,right)) \
+ STB_(N,setright)(tree, STB_(N,split)(tree->STB_(N,right))); \
+ } \
+ } \
+ PAR(if (tree) tree->STB_(N,parent) = NULL;) \
+ return tree; \
+} \
+ \
+TYPE *STB__(N,last)(TYPE *tree) \
+{ \
+ if (tree) \
+ while (tree->STB_(N,right)) tree = tree->STB_(N,right); \
+ return tree; \
+} \
+ \
+TYPE *STB__(N,first)(TYPE *tree) \
+{ \
+ if (tree) \
+ while (tree->STB_(N,left)) tree = tree->STB_(N,left); \
+ return tree; \
+} \
+ \
+TYPE *STB__(N,next)(TYPE *tree, TYPE *item) \
+{ \
+ TYPE *next = NULL; \
+ if (item->STB_(N,right)) \
+ return STB__(N,first)(item->STB_(N,right)); \
+ PAR( \
+ while(item->STB_(N,parent)) { \
+ TYPE *up = item->STB_(N,parent); \
+ if (up->STB_(N,left) == item) return up; \
+ item = up; \
+ } \
+ return NULL; \
+ ) \
+ while (tree != item) { \
+ if (STB_(N,_compare)(item, tree) < 0) { \
+ next = tree; \
+ tree = tree->STB_(N,left); \
+ } else { \
+ tree = tree->STB_(N,right); \
+ } \
+ } \
+ return next; \
+} \
+ \
+TYPE *STB__(N,prev)(TYPE *tree, TYPE *item) \
+{ \
+ TYPE *next = NULL; \
+ if (item->STB_(N,left)) \
+ return STB__(N,last)(item->STB_(N,left)); \
+ PAR( \
+ while(item->STB_(N,parent)) { \
+ TYPE *up = item->STB_(N,parent); \
+ if (up->STB_(N,right) == item) return up; \
+ item = up; \
+ } \
+ return NULL; \
+ ) \
+ while (tree != item) { \
+ if (STB_(N,_compare)(item, tree) < 0) { \
+ tree = tree->STB_(N,left); \
+ } else { \
+ next = tree; \
+ tree = tree->STB_(N,right); \
+ } \
+ } \
+ return next; \
+} \
+ \
+STB__DEBUG( \
+ void STB__(N,_validate)(TYPE *tree, int root) \
+ { \
+ if (tree == NULL) return; \
+ PAR(if(root) assert(tree->STB_(N,parent) == NULL);) \
+ assert(STB__level(N,tree->STB_(N,left) ) == tree->STB_(N,level)-1); \
+ assert(STB__level(N,tree->STB_(N,right)) <= tree->STB_(N,level)); \
+ assert(STB__level(N,tree->STB_(N,right)) >= tree->STB_(N,level)-1); \
+ if (tree->STB_(N,right)) { \
+ assert(STB__level(N,tree->STB_(N,right)->STB_(N,right)) \
+ != tree->STB_(N,level)); \
+ PAR(assert(tree->STB_(N,right)->STB_(N,parent) == tree);) \
+ } \
+ PAR(if(tree->STB_(N,left)) assert(tree->STB_(N,left)->STB_(N,parent) == tree);) \
+ STB__(N,_validate)(tree->STB_(N,left) ,0); \
+ STB__(N,_validate)(tree->STB_(N,right),0); \
+ } \
+) \
+ \
+typedef struct \
+{ \
+ TYPE *root; \
+} TREE; \
+ \
+void STB__(M,Insert)(TREE *tree, TYPE *item) \
+{ tree->root = STB__(N,insert)(tree->root, item); } \
+void STB__(M,Remove)(TREE *tree, TYPE *item) \
+{ tree->root = STB__(N,remove)(tree->root, item); } \
+TYPE *STB__(M,Next)(TREE *tree, TYPE *item) \
+{ return STB__(N,next)(tree->root, item); } \
+TYPE *STB__(M,Prev)(TREE *tree, TYPE *item) \
+{ return STB__(N,prev)(tree->root, item); } \
+TYPE *STB__(M,First)(TREE *tree) { return STB__(N,first)(tree->root); } \
+TYPE *STB__(M,Last) (TREE *tree) { return STB__(N,last) (tree->root); } \
+void STB__(M,Init)(TREE *tree) { tree->root = NULL; }
+
+
+#define stb_bst_find(N,tree,fcompare) \
+{ \
+ int c; \
+ while (tree != NULL) { \
+ fcompare \
+ if (c == 0) return tree; \
+ if (c < 0) tree = tree->STB_(N,left); \
+ else tree = tree->STB_(N,right); \
+ } \
+ return NULL; \
+}
+
+#define stb_bst_raw(TYPE,N,TREE,M,vfield,VTYPE,compare,PAR) \
+ stb_bst_base(TYPE,N,TREE,M, \
+ VTYPE a = p->vfield; VTYPE b = q->vfield; return (compare);, PAR ) \
+ \
+TYPE *STB__(N,find)(TYPE *tree, VTYPE a) \
+ stb_bst_find(N,tree,VTYPE b = tree->vfield; c = (compare);) \
+TYPE *STB__(M,Find)(TREE *tree, VTYPE a) \
+{ return STB__(N,find)(tree->root, a); }
+
+#define stb_bst(TYPE,N,TREE,M,vfield,VTYPE,compare) \
+ stb_bst_raw(TYPE,N,TREE,M,vfield,VTYPE,compare,stb__bst_noparent)
+#define stb_bst_parent(TYPE,N,TREE,M,vfield,VTYPE,compare) \
+ stb_bst_raw(TYPE,N,TREE,M,vfield,VTYPE,compare,stb__bst_parent)
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Pointer Nulling
+//
+// This lets you automatically NULL dangling pointers to "registered"
+// objects. Note that you have to make sure you call the appropriate
+// functions when you free or realloc blocks of memory that contain
+// pointers or pointer targets. stb.h can automatically do this for
+// stb_arr, or for all frees/reallocs if it's wrapping them.
+//
+
+#ifdef STB_NPTR
+
+STB_EXTERN void stb_nptr_set(void *address_of_pointer, void *value_to_write);
+STB_EXTERN void stb_nptr_didset(void *address_of_pointer);
+
+STB_EXTERN void stb_nptr_didfree(void *address_being_freed, int len);
+STB_EXTERN void stb_nptr_free(void *address_being_freed, int len);
+
+STB_EXTERN void stb_nptr_didrealloc(void *new_address, void *old_address, int len);
+STB_EXTERN void stb_nptr_recache(void); // recache all known pointers
+ // do this after pointer sets outside your control, slow
+
+#ifdef STB_DEFINE
+// for fast updating on free/realloc, we need to be able to find
+// all the objects (pointers and targets) within a given block;
+// this precludes hashing
+
+// we use a three-level hierarchy of memory to minimize storage:
+// level 1: 65536 pointers to stb__memory_node (always uses 256 KB)
+// level 2: each stb__memory_node represents a 64K block of memory
+// with 256 stb__memory_leafs (worst case 64MB)
+// level 3: each stb__memory_leaf represents 256 bytes of memory
+// using a list of target locations and a list of pointers
+// (which are hopefully fairly short normally!)
+
+// this approach won't work in 64-bit, which has a much larger address
+// space. need to redesign
+
+#define STB__NPTR_ROOT_LOG2 16
+#define STB__NPTR_ROOT_NUM (1 << STB__NPTR_ROOT_LOG2)
+#define STB__NPTR_ROOT_SHIFT (32 - STB__NPTR_ROOT_LOG2)
+
+#define STB__NPTR_NODE_LOG2 5
+#define STB__NPTR_NODE_NUM (1 << STB__NPTR_NODE_LOG2)
+#define STB__NPTR_NODE_MASK (STB__NPTR_NODE_NUM-1)
+#define STB__NPTR_NODE_SHIFT (STB__NPTR_ROOT_SHIFT - STB__NPTR_NODE_LOG2)
+#define STB__NPTR_NODE_OFFSET(x) (((x) >> STB__NPTR_NODE_SHIFT) & STB__NPTR_NODE_MASK)
+
+typedef struct stb__st_nptr
+{
+ void *ptr; // address of actual pointer
+ struct stb__st_nptr *next; // next pointer with same target
+ struct stb__st_nptr **prev; // prev pointer with same target, address of 'next' field (or first)
+ struct stb__st_nptr *next_in_block;
+} stb__nptr;
+
+typedef struct stb__st_nptr_target
+{
+ void *ptr; // address of target
+ stb__nptr *first; // address of first nptr pointing to this
+ struct stb__st_nptr_target *next_in_block;
+} stb__nptr_target;
+
+typedef struct
+{
+ stb__nptr *pointers;
+ stb__nptr_target *targets;
+} stb__memory_leaf;
+
+typedef struct
+{
+ stb__memory_leaf *children[STB__NPTR_NODE_NUM];
+} stb__memory_node;
+
+stb__memory_node *stb__memtab_root[STB__NPTR_ROOT_NUM];
+
+static stb__memory_leaf *stb__nptr_find_leaf(void *mem)
+{
+ stb_uint32 address = (stb_uint32) mem;
+ stb__memory_node *z = stb__memtab_root[address >> STB__NPTR_ROOT_SHIFT];
+ if (z)
+ return z->children[STB__NPTR_NODE_OFFSET(address)];
+ else
+ return NULL;
+}
+
+static void * stb__nptr_alloc(int size)
+{
+ return stb__realloc_raw(0,size);
+}
+
+static void stb__nptr_free(void *p)
+{
+ stb__realloc_raw(p,0);
+}
+
+static stb__memory_leaf *stb__nptr_make_leaf(void *mem)
+{
+ stb_uint32 address = (stb_uint32) mem;
+ stb__memory_node *z = stb__memtab_root[address >> STB__NPTR_ROOT_SHIFT];
+ stb__memory_leaf *f;
+ if (!z) {
+ int i;
+ z = (stb__memory_node *) stb__nptr_alloc(sizeof(*stb__memtab_root[0]));
+ stb__memtab_root[address >> STB__NPTR_ROOT_SHIFT] = z;
+ for (i=0; i < 256; ++i)
+ z->children[i] = 0;
+ }
+ f = (stb__memory_leaf *) stb__nptr_alloc(sizeof(*f));
+ z->children[STB__NPTR_NODE_OFFSET(address)] = f;
+ f->pointers = NULL;
+ f->targets = NULL;
+ return f;
+}
+
+static stb__nptr_target *stb__nptr_find_target(void *target, int force)
+{
+ stb__memory_leaf *p = stb__nptr_find_leaf(target);
+ if (p) {
+ stb__nptr_target *t = p->targets;
+ while (t) {
+ if (t->ptr == target)
+ return t;
+ t = t->next_in_block;
+ }
+ }
+ if (force) {
+ stb__nptr_target *t = (stb__nptr_target*) stb__nptr_alloc(sizeof(*t));
+ if (!p) p = stb__nptr_make_leaf(target);
+ t->ptr = target;
+ t->first = NULL;
+ t->next_in_block = p->targets;
+ p->targets = t;
+ return t;
+ } else
+ return NULL;
+}
+
+static stb__nptr *stb__nptr_find_pointer(void *ptr, int force)
+{
+ stb__memory_leaf *p = stb__nptr_find_leaf(ptr);
+ if (p) {
+ stb__nptr *t = p->pointers;
+ while (t) {
+ if (t->ptr == ptr)
+ return t;
+ t = t->next_in_block;
+ }
+ }
+ if (force) {
+ stb__nptr *t = (stb__nptr *) stb__nptr_alloc(sizeof(*t));
+ if (!p) p = stb__nptr_make_leaf(ptr);
+ t->ptr = ptr;
+ t->next = NULL;
+ t->prev = NULL;
+ t->next_in_block = p->pointers;
+ p->pointers = t;
+ return t;
+ } else
+ return NULL;
+}
+
+void stb_nptr_set(void *address_of_pointer, void *value_to_write)
+{
+ if (*(void **)address_of_pointer != value_to_write) {
+ *(void **) address_of_pointer = value_to_write;
+ stb_nptr_didset(address_of_pointer);
+ }
+}
+
+void stb_nptr_didset(void *address_of_pointer)
+{
+ // first unlink from old chain
+ void *new_address;
+ stb__nptr *p = stb__nptr_find_pointer(address_of_pointer, 1); // force building if doesn't exist
+ if (p->prev) { // if p->prev is NULL, we just built it, or it was NULL
+ *(p->prev) = p->next;
+ if (p->next) p->next->prev = p->prev;
+ }
+ // now add to new chain
+ new_address = *(void **)address_of_pointer;
+ if (new_address != NULL) {
+ stb__nptr_target *t = stb__nptr_find_target(new_address, 1);
+ p->next = t->first;
+ if (p->next) p->next->prev = &p->next;
+ p->prev = &t->first;
+ t->first = p;
+ } else {
+ p->prev = NULL;
+ p->next = NULL;
+ }
+}
+
+void stb__nptr_block(void *address, int len, void (*function)(stb__memory_leaf *f, int datum, void *start, void *end), int datum)
+{
+ void *end_address = (void *) ((char *) address + len - 1);
+ stb__memory_node *n;
+ stb_uint32 start = (stb_uint32) address;
+ stb_uint32 end = start + len - 1;
+
+ int b0 = start >> STB__NPTR_ROOT_SHIFT;
+ int b1 = end >> STB__NPTR_ROOT_SHIFT;
+ int b=b0,i,e0,e1;
+
+ e0 = STB__NPTR_NODE_OFFSET(start);
+
+ if (datum <= 0) {
+ // first block
+ n = stb__memtab_root[b0];
+ if (n) {
+ if (b0 != b1)
+ e1 = STB__NPTR_NODE_NUM-1;
+ else
+ e1 = STB__NPTR_NODE_OFFSET(end);
+ for (i=e0; i <= e1; ++i)
+ if (n->children[i])
+ function(n->children[i], datum, address, end_address);
+ }
+ if (b1 > b0) {
+ // blocks other than the first and last block
+ for (b=b0+1; b < b1; ++b) {
+ n = stb__memtab_root[b];
+ if (n)
+ for (i=0; i <= STB__NPTR_NODE_NUM-1; ++i)
+ if (n->children[i])
+ function(n->children[i], datum, address, end_address);
+ }
+ // last block
+ n = stb__memtab_root[b1];
+ if (n) {
+ e1 = STB__NPTR_NODE_OFFSET(end);
+ for (i=0; i <= e1; ++i)
+ if (n->children[i])
+ function(n->children[i], datum, address, end_address);
+ }
+ }
+ } else {
+ if (b1 > b0) {
+ // last block
+ n = stb__memtab_root[b1];
+ if (n) {
+ e1 = STB__NPTR_NODE_OFFSET(end);
+ for (i=e1; i >= 0; --i)
+ if (n->children[i])
+ function(n->children[i], datum, address, end_address);
+ }
+ // blocks other than the first and last block
+ for (b=b1-1; b > b0; --b) {
+ n = stb__memtab_root[b];
+ if (n)
+ for (i=STB__NPTR_NODE_NUM-1; i >= 0; --i)
+ if (n->children[i])
+ function(n->children[i], datum, address, end_address);
+ }
+ }
+ // first block
+ n = stb__memtab_root[b0];
+ if (n) {
+ if (b0 != b1)
+ e1 = STB__NPTR_NODE_NUM-1;
+ else
+ e1 = STB__NPTR_NODE_OFFSET(end);
+ for (i=e1; i >= e0; --i)
+ if (n->children[i])
+ function(n->children[i], datum, address, end_address);
+ }
+ }
+}
+
+static void stb__nptr_delete_pointers(stb__memory_leaf *f, int offset, void *start, void *end)
+{
+ stb__nptr **p = &f->pointers;
+ while (*p) {
+ stb__nptr *n = *p;
+ if (n->ptr >= start && n->ptr <= end) {
+ // unlink
+ if (n->prev) {
+ *(n->prev) = n->next;
+ if (n->next) n->next->prev = n->prev;
+ }
+ *p = n->next_in_block;
+ stb__nptr_free(n);
+ } else
+ p = &(n->next_in_block);
+ }
+}
+
+static void stb__nptr_delete_targets(stb__memory_leaf *f, int offset, void *start, void *end)
+{
+ stb__nptr_target **p = &f->targets;
+ while (*p) {
+ stb__nptr_target *n = *p;
+ if (n->ptr >= start && n->ptr <= end) {
+ // null pointers
+ stb__nptr *z = n->first;
+ while (z) {
+ stb__nptr *y = z->next;
+ z->prev = NULL;
+ z->next = NULL;
+ *(void **) z->ptr = NULL;
+ z = y;
+ }
+ // unlink this target
+ *p = n->next_in_block;
+ stb__nptr_free(n);
+ } else
+ p = &(n->next_in_block);
+ }
+}
+
+void stb_nptr_didfree(void *address_being_freed, int len)
+{
+ // step one: delete all pointers in this block
+ stb__nptr_block(address_being_freed, len, stb__nptr_delete_pointers, 0);
+ // step two: NULL all pointers to this block; do this second to avoid NULLing deleted pointers
+ stb__nptr_block(address_being_freed, len, stb__nptr_delete_targets, 0);
+}
+
+void stb_nptr_free(void *address_being_freed, int len)
+{
+ free(address_being_freed);
+ stb_nptr_didfree(address_being_freed, len);
+}
+
+static void stb__nptr_move_targets(stb__memory_leaf *f, int offset, void *start, void *end)
+{
+ stb__nptr_target **t = &f->targets;
+ while (*t) {
+ stb__nptr_target *n = *t;
+ if (n->ptr >= start && n->ptr <= end) {
+ stb__nptr *z;
+ stb__memory_leaf *f;
+ // unlink n
+ *t = n->next_in_block;
+ // update n to new address
+ n->ptr = (void *) ((char *) n->ptr + offset);
+ f = stb__nptr_find_leaf(n->ptr);
+ if (!f) f = stb__nptr_make_leaf(n->ptr);
+ n->next_in_block = f->targets;
+ f->targets = n;
+ // now go through all pointers and make them point here
+ z = n->first;
+ while (z) {
+ *(void**) z->ptr = n->ptr;
+ z = z->next;
+ }
+ } else
+ t = &(n->next_in_block);
+ }
+}
+
+static void stb__nptr_move_pointers(stb__memory_leaf *f, int offset, void *start, void *end)
+{
+ stb__nptr **p = &f->pointers;
+ while (*p) {
+ stb__nptr *n = *p;
+ if (n->ptr >= start && n->ptr <= end) {
+ // unlink
+ *p = n->next_in_block;
+ n->ptr = (void *) ((int) n->ptr + offset);
+ // move to new block
+ f = stb__nptr_find_leaf(n->ptr);
+ if (!f) f = stb__nptr_make_leaf(n->ptr);
+ n->next_in_block = f->pointers;
+ f->pointers = n;
+ } else
+ p = &(n->next_in_block);
+ }
+}
+
+void stb_nptr_realloc(void *new_address, void *old_address, int len)
+{
+ if (new_address == old_address) return;
+
+ // have to move the pointers first, because moving the targets
+ // requires writing to the pointers-to-the-targets, and if some of those moved too,
+ // we need to make sure we don't write to the old memory
+
+ // step one: move all pointers within the block
+ stb__nptr_block(old_address, len, stb__nptr_move_pointers, (char *) new_address - (char *) old_address);
+ // step two: move all targets within the block
+ stb__nptr_block(old_address, len, stb__nptr_move_targets, (char *) new_address - (char *) old_address);
+}
+
+void stb_nptr_move(void *new_address, void *old_address)
+{
+ stb_nptr_realloc(new_address, old_address, 1);
+}
+
+void stb_nptr_recache(void)
+{
+ int i,j;
+ for (i=0; i < STB__NPTR_ROOT_NUM; ++i)
+ if (stb__memtab_root[i])
+ for (j=0; j < STB__NPTR_NODE_NUM; ++j)
+ if (stb__memtab_root[i]->children[j]) {
+ stb__nptr *p = stb__memtab_root[i]->children[j]->pointers;
+ while (p) {
+ stb_nptr_didset(p->ptr);
+ p = p->next_in_block;
+ }
+ }
+}
+
+#endif // STB_DEFINE
+#endif // STB_NPTR
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// File Processing
+//
+
+
+#ifdef _WIN32
+ #define stb_rename(x,y) _wrename((const wchar_t *)stb__from_utf8(x), (const wchar_t *)stb__from_utf8_alt(y))
+#else
+ #define stb_rename rename
+#endif
+
+STB_EXTERN void stb_fput_varlen64(FILE *f, stb_uint64 v);
+STB_EXTERN stb_uint64 stb_fget_varlen64(FILE *f);
+STB_EXTERN int stb_size_varlen64(stb_uint64 v);
+
+
+#define stb_filec (char *) stb_file
+#define stb_fileu (unsigned char *) stb_file
+STB_EXTERN void * stb_file(char *filename, size_t *length);
+STB_EXTERN void * stb_file_max(char *filename, size_t *length);
+STB_EXTERN size_t stb_filelen(FILE *f);
+STB_EXTERN int stb_filewrite(char *filename, void *data, size_t length);
+STB_EXTERN int stb_filewritestr(char *filename, char *data);
+STB_EXTERN char ** stb_stringfile(char *filename, int *len);
+STB_EXTERN char ** stb_stringfile_trimmed(char *name, int *len, char comm);
+STB_EXTERN char * stb_fgets(char *buffer, int buflen, FILE *f);
+STB_EXTERN char * stb_fgets_malloc(FILE *f);
+STB_EXTERN int stb_fexists(char *filename);
+STB_EXTERN int stb_fcmp(char *s1, char *s2);
+STB_EXTERN int stb_feq(char *s1, char *s2);
+STB_EXTERN time_t stb_ftimestamp(char *filename);
+
+STB_EXTERN int stb_fullpath(char *abs, int abs_size, char *rel);
+STB_EXTERN FILE * stb_fopen(char *filename, const char *mode);
+STB_EXTERN int stb_fclose(FILE *f, int keep);
+
+enum
+{
+ stb_keep_no = 0,
+ stb_keep_yes = 1,
+ stb_keep_if_different = 2,
+};
+
+STB_EXTERN int stb_copyfile(char *src, char *dest);
+
+STB_EXTERN void stb_fput_varlen64(FILE *f, stb_uint64 v);
+STB_EXTERN stb_uint64 stb_fget_varlen64(FILE *f);
+STB_EXTERN int stb_size_varlen64(stb_uint64 v);
+
+STB_EXTERN void stb_fwrite32(FILE *f, stb_uint32 datum);
+STB_EXTERN void stb_fput_varlen (FILE *f, int v);
+STB_EXTERN void stb_fput_varlenu(FILE *f, unsigned int v);
+STB_EXTERN int stb_fget_varlen (FILE *f);
+STB_EXTERN stb_uint stb_fget_varlenu(FILE *f);
+STB_EXTERN void stb_fput_ranged (FILE *f, int v, int b, stb_uint n);
+STB_EXTERN int stb_fget_ranged (FILE *f, int b, stb_uint n);
+STB_EXTERN int stb_size_varlen (int v);
+STB_EXTERN int stb_size_varlenu(unsigned int v);
+STB_EXTERN int stb_size_ranged (int b, stb_uint n);
+
+STB_EXTERN int stb_fread(void *data, size_t len, size_t count, void *f);
+STB_EXTERN int stb_fwrite(void *data, size_t len, size_t count, void *f);
+
+#if 0
+typedef struct
+{
+ FILE *base_file;
+ char *buffer;
+ int buffer_size;
+ int buffer_off;
+ int buffer_left;
+} STBF;
+
+STB_EXTERN STBF *stb_tfopen(char *filename, char *mode);
+STB_EXTERN int stb_tfread(void *data, size_t len, size_t count, STBF *f);
+STB_EXTERN int stb_tfwrite(void *data, size_t len, size_t count, STBF *f);
+#endif
+
+#ifdef STB_DEFINE
+
+#if 0
+STBF *stb_tfopen(char *filename, char *mode)
+{
+ STBF *z;
+ FILE *f = stb_p_fopen(filename, mode);
+ if (!f) return NULL;
+ z = (STBF *) malloc(sizeof(*z));
+ if (!z) { fclose(f); return NULL; }
+ z->base_file = f;
+ if (!strcmp(mode, "rb") || !strcmp(mode, "wb")) {
+ z->buffer_size = 4096;
+ z->buffer_off = z->buffer_size;
+ z->buffer_left = 0;
+ z->buffer = malloc(z->buffer_size);
+ if (!z->buffer) { free(z); fclose(f); return NULL; }
+ } else {
+ z->buffer = 0;
+ z->buffer_size = 0;
+ z->buffer_left = 0;
+ }
+ return z;
+}
+
+int stb_tfread(void *data, size_t len, size_t count, STBF *f)
+{
+ int total = len*count, done=0;
+ if (!total) return 0;
+ if (total <= z->buffer_left) {
+ memcpy(data, z->buffer + z->buffer_off, total);
+ z->buffer_off += total;
+ z->buffer_left -= total;
+ return count;
+ } else {
+ char *out = (char *) data;
+
+ // consume all buffered data
+ memcpy(data, z->buffer + z->buffer_off, z->buffer_left);
+ done = z->buffer_left;
+ out += z->buffer_left;
+ z->buffer_left=0;
+
+ if (total-done > (z->buffer_size >> 1)) {
+ done += fread(out
+ }
+ }
+}
+#endif
+
+void stb_fwrite32(FILE *f, stb_uint32 x)
+{
+ fwrite(&x, 4, 1, f);
+}
+
+#if defined(_WIN32)
+ #define stb__stat _stat
+#else
+ #define stb__stat stat
+#endif
+
+int stb_fexists(char *filename)
+{
+ struct stb__stat buf;
+ return stb__windows(
+ _wstat((const wchar_t *)stb__from_utf8(filename), &buf),
+ stat(filename,&buf)
+ ) == 0;
+}
+
+time_t stb_ftimestamp(char *filename)
+{
+ struct stb__stat buf;
+ if (stb__windows(
+ _wstat((const wchar_t *)stb__from_utf8(filename), &buf),
+ stat(filename,&buf)
+ ) == 0)
+ {
+ return buf.st_mtime;
+ } else {
+ return 0;
+ }
+}
+
+size_t stb_filelen(FILE *f)
+{
+ long len, pos;
+ pos = ftell(f);
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, pos, SEEK_SET);
+ return (size_t) len;
+}
+
+void *stb_file(char *filename, size_t *length)
+{
+ FILE *f = stb__fopen(filename, "rb");
+ char *buffer;
+ size_t len, len2;
+ if (!f) return NULL;
+ len = stb_filelen(f);
+ buffer = (char *) malloc(len+2); // nul + extra
+ len2 = fread(buffer, 1, len, f);
+ if (len2 == len) {
+ if (length) *length = len;
+ buffer[len] = 0;
+ } else {
+ free(buffer);
+ buffer = NULL;
+ }
+ fclose(f);
+ return buffer;
+}
+
+int stb_filewrite(char *filename, void *data, size_t length)
+{
+ FILE *f = stb_fopen(filename, "wb");
+ if (f) {
+ unsigned char *data_ptr = (unsigned char *) data;
+ size_t remaining = length;
+ while (remaining > 0) {
+ size_t len2 = remaining > 65536 ? 65536 : remaining;
+ size_t len3 = fwrite(data_ptr, 1, len2, f);
+ if (len2 != len3) {
+ fprintf(stderr, "Failed while writing %s\n", filename);
+ break;
+ }
+ remaining -= len2;
+ data_ptr += len2;
+ }
+ stb_fclose(f, stb_keep_if_different);
+ }
+ return f != NULL;
+}
+
+int stb_filewritestr(char *filename, char *data)
+{
+ return stb_filewrite(filename, data, strlen(data));
+}
+
+void * stb_file_max(char *filename, size_t *length)
+{
+ FILE *f = stb__fopen(filename, "rb");
+ char *buffer;
+ size_t len, maxlen;
+ if (!f) return NULL;
+ maxlen = *length;
+ buffer = (char *) malloc(maxlen+1);
+ len = fread(buffer, 1, maxlen, f);
+ buffer[len] = 0;
+ fclose(f);
+ *length = len;
+ return buffer;
+}
+
+char ** stb_stringfile(char *filename, int *plen)
+{
+ FILE *f = stb__fopen(filename, "rb");
+ char *buffer, **list=NULL, *s;
+ size_t len, count, i;
+
+ if (!f) return NULL;
+ len = stb_filelen(f);
+ buffer = (char *) malloc(len+1);
+ len = fread(buffer, 1, len, f);
+ buffer[len] = 0;
+ fclose(f);
+
+ // two passes through: first time count lines, second time set them
+ for (i=0; i < 2; ++i) {
+ s = buffer;
+ if (i == 1)
+ list[0] = s;
+ count = 1;
+ while (*s) {
+ if (*s == '\n' || *s == '\r') {
+ // detect if both cr & lf are together
+ int crlf = (s[0] + s[1]) == ('\n' + '\r');
+ if (i == 1) *s = 0;
+ if (crlf) ++s;
+ if (s[1]) { // it's not over yet
+ if (i == 1) list[count] = s+1;
+ ++count;
+ }
+ }
+ ++s;
+ }
+ if (i == 0) {
+ list = (char **) malloc(sizeof(*list) * (count+1) + len+1);
+ if (!list) return NULL;
+ list[count] = 0;
+ // recopy the file so there's just a single allocation to free
+ memcpy(&list[count+1], buffer, len+1);
+ free(buffer);
+ buffer = (char *) &list[count+1];
+ if (plen) *plen = (int) count;
+ }
+ }
+ return list;
+}
+
+char ** stb_stringfile_trimmed(char *name, int *len, char comment)
+{
+ int i,n,o=0;
+ char **s = stb_stringfile(name, &n);
+ if (s == NULL) return NULL;
+ for (i=0; i < n; ++i) {
+ char *p = stb_skipwhite(s[i]);
+ if (*p && *p != comment)
+ s[o++] = p;
+ }
+ s[o] = NULL;
+ if (len) *len = o;
+ return s;
+}
+
+char * stb_fgets(char *buffer, int buflen, FILE *f)
+{
+ char *p;
+ buffer[0] = 0;
+ p = fgets(buffer, buflen, f);
+ if (p) {
+ int n = (int) (strlen(p)-1);
+ if (n >= 0)
+ if (p[n] == '\n')
+ p[n] = 0;
+ }
+ return p;
+}
+
+char * stb_fgets_malloc(FILE *f)
+{
+ // avoid reallocing for small strings
+ char quick_buffer[800];
+ quick_buffer[sizeof(quick_buffer)-2] = 0;
+ if (!fgets(quick_buffer, sizeof(quick_buffer), f))
+ return NULL;
+
+ if (quick_buffer[sizeof(quick_buffer)-2] == 0) {
+ size_t n = strlen(quick_buffer);
+ if (n > 0 && quick_buffer[n-1] == '\n')
+ quick_buffer[n-1] = 0;
+ return stb_p_strdup(quick_buffer);
+ } else {
+ char *p;
+ char *a = stb_p_strdup(quick_buffer);
+ size_t len = sizeof(quick_buffer)-1;
+
+ while (!feof(f)) {
+ if (a[len-1] == '\n') break;
+ a = (char *) realloc(a, len*2);
+ p = &a[len];
+ p[len-2] = 0;
+ if (!fgets(p, (int) len, f))
+ break;
+ if (p[len-2] == 0) {
+ len += strlen(p);
+ break;
+ }
+ len = len + (len-1);
+ }
+ if (a[len-1] == '\n')
+ a[len-1] = 0;
+ return a;
+ }
+}
+
+int stb_fullpath(char *abs, int abs_size, char *rel)
+{
+ #ifdef _WIN32
+ return _fullpath(abs, rel, abs_size) != NULL;
+ #else
+ if (rel[0] == '/' || rel[0] == '~') {
+ if ((int) strlen(rel) >= abs_size)
+ return 0;
+ stb_p_strcpy_s(abs,65536,rel);
+ return STB_TRUE;
+ } else {
+ int n;
+ getcwd(abs, abs_size);
+ n = strlen(abs);
+ if (n+(int) strlen(rel)+2 <= abs_size) {
+ abs[n] = '/';
+ stb_p_strcpy_s(abs+n+1, 65536,rel);
+ return STB_TRUE;
+ } else {
+ return STB_FALSE;
+ }
+ }
+ #endif
+}
+
+static int stb_fcmp_core(FILE *f, FILE *g)
+{
+ char buf1[1024],buf2[1024];
+ int n1,n2, res=0;
+
+ while (1) {
+ n1 = (int) fread(buf1, 1, sizeof(buf1), f);
+ n2 = (int) fread(buf2, 1, sizeof(buf2), g);
+ res = memcmp(buf1,buf2,stb_min(n1,n2));
+ if (res)
+ break;
+ if (n1 != n2) {
+ res = n1 < n2 ? -1 : 1;
+ break;
+ }
+ if (n1 == 0)
+ break;
+ }
+
+ fclose(f);
+ fclose(g);
+ return res;
+}
+
+int stb_fcmp(char *s1, char *s2)
+{
+ FILE *f = stb__fopen(s1, "rb");
+ FILE *g = stb__fopen(s2, "rb");
+
+ if (f == NULL || g == NULL) {
+ if (f) fclose(f);
+ if (g) {
+ fclose(g);
+ return STB_TRUE;
+ }
+ return f != NULL;
+ }
+
+ return stb_fcmp_core(f,g);
+}
+
+int stb_feq(char *s1, char *s2)
+{
+ FILE *f = stb__fopen(s1, "rb");
+ FILE *g = stb__fopen(s2, "rb");
+
+ if (f == NULL || g == NULL) {
+ if (f) fclose(f);
+ if (g) fclose(g);
+ return f == g;
+ }
+
+ // feq is faster because it shortcuts if they're different length
+ if (stb_filelen(f) != stb_filelen(g)) {
+ fclose(f);
+ fclose(g);
+ return 0;
+ }
+
+ return !stb_fcmp_core(f,g);
+}
+
+static stb_ptrmap *stb__files;
+
+typedef struct
+{
+ char *temp_name;
+ char *name;
+ int errors;
+} stb__file_data;
+
+static FILE *stb__open_temp_file(char *temp_name, char *src_name, const char *mode)
+{
+ size_t p;
+#ifdef _MSC_VER
+ int j;
+#endif
+ FILE *f;
+ // try to generate a temporary file in the same directory
+ p = strlen(src_name)-1;
+ while (p > 0 && src_name[p] != '/' && src_name[p] != '\\'
+ && src_name[p] != ':' && src_name[p] != '~')
+ --p;
+ ++p;
+
+ memcpy(temp_name, src_name, p);
+
+ #ifdef _MSC_VER
+ // try multiple times to make a temp file... just in
+ // case some other process makes the name first
+ for (j=0; j < 32; ++j) {
+ stb_p_strcpy_s(temp_name+p, 65536, "stmpXXXXXX");
+ if (!stb_p_mktemp(temp_name))
+ return 0;
+
+ f = stb_p_fopen(temp_name, mode);
+ if (f != NULL)
+ break;
+ }
+ #else
+ {
+ stb_p_strcpy_s(temp_name+p, 65536, "stmpXXXXXX");
+ #ifdef __MINGW32__
+ int fd = open(stb_p_mktemp(temp_name), O_RDWR);
+ #else
+ int fd = mkstemp(temp_name);
+ #endif
+ if (fd == -1) return NULL;
+ f = fdopen(fd, mode);
+ if (f == NULL) {
+ unlink(temp_name);
+ close(fd);
+ return NULL;
+ }
+ }
+ #endif
+ return f;
+}
+
+
+FILE * stb_fopen(char *filename, const char *mode)
+{
+ FILE *f;
+ char name_full[4096];
+ char temp_full[sizeof(name_full) + 12];
+
+ // @TODO: if the file doesn't exist, we can also use the fastpath here
+ if (mode[0] != 'w' && !strchr(mode, '+'))
+ return stb__fopen(filename, mode);
+
+ // save away the full path to the file so if the program
+ // changes the cwd everything still works right! unix has
+ // better ways to do this, but we have to work in windows
+ name_full[0] = '\0'; // stb_fullpath reads name_full[0]
+ if (stb_fullpath(name_full, sizeof(name_full), filename)==0)
+ return 0;
+
+ f = stb__open_temp_file(temp_full, name_full, mode);
+ if (f != NULL) {
+ stb__file_data *d = (stb__file_data *) malloc(sizeof(*d));
+ if (!d) { assert(0); /* NOTREACHED */fclose(f); return NULL; }
+ if (stb__files == NULL) stb__files = stb_ptrmap_create();
+ d->temp_name = stb_p_strdup(temp_full);
+ d->name = stb_p_strdup(name_full);
+ d->errors = 0;
+ stb_ptrmap_add(stb__files, f, d);
+ return f;
+ }
+
+ return NULL;
+}
+
+int stb_fclose(FILE *f, int keep)
+{
+ stb__file_data *d;
+
+ int ok = STB_FALSE;
+ if (f == NULL) return 0;
+
+ if (ferror(f))
+ keep = stb_keep_no;
+
+ fclose(f);
+
+ if (stb__files && stb_ptrmap_remove(stb__files, f, (void **) &d)) {
+ if (stb__files->count == 0) {
+ stb_ptrmap_destroy(stb__files);
+ stb__files = NULL;
+ }
+ } else
+ return STB_TRUE; // not special
+
+ if (keep == stb_keep_if_different) {
+ // check if the files are identical
+ if (stb_feq(d->name, d->temp_name)) {
+ keep = stb_keep_no;
+ ok = STB_TRUE; // report success if no change
+ }
+ }
+
+ if (keep == stb_keep_no) {
+ remove(d->temp_name);
+ } else {
+ if (!stb_fexists(d->name)) {
+ // old file doesn't exist, so just move the new file over it
+ stb_rename(d->temp_name, d->name);
+ } else {
+ // don't delete the old file yet in case there are troubles! First rename it!
+ char preserved_old_file[4096];
+
+ // generate a temp filename in the same directory (also creates it, which we don't need)
+ FILE *dummy = stb__open_temp_file(preserved_old_file, d->name, "wb");
+ if (dummy != NULL) {
+ // we don't actually want the open file
+ fclose(dummy);
+
+ // discard what we just created
+ remove(preserved_old_file); // if this fails, there's nothing we can do, and following logic handles it as best as possible anyway
+
+ // move the existing file to the preserved name
+ if (0 != stb_rename(d->name, preserved_old_file)) { // 0 on success
+ // failed, state is:
+ // filename -> old file
+ // tempname -> new file
+ // keep tempname around so we don't lose data
+ } else {
+ // state is:
+ // preserved -> old file
+ // tempname -> new file
+ // move the new file to the old name
+ if (0 == stb_rename(d->temp_name, d->name)) {
+ // state is:
+ // preserved -> old file
+ // filename -> new file
+ ok = STB_TRUE;
+
+ // 'filename -> new file' has always been the goal, so clean up
+ remove(preserved_old_file); // nothing to be done if it fails
+ } else {
+ // couldn't rename, so try renaming preserved file back
+
+ // state is:
+ // preserved -> old file
+ // tempname -> new file
+ stb_rename(preserved_old_file, d->name);
+ // if the rename failed, there's nothing more we can do
+ }
+ }
+ } else {
+ // we couldn't get a temp filename. do this the naive way; the worst case failure here
+ // leaves the filename pointing to nothing and the new file as a tempfile
+ remove(d->name);
+ stb_rename(d->temp_name, d->name);
+ }
+ }
+ }
+
+ free(d->temp_name);
+ free(d->name);
+ free(d);
+
+ return ok;
+}
+
+int stb_copyfile(char *src, char *dest)
+{
+ char raw_buffer[1024];
+ char *buffer;
+ int buf_size = 65536;
+
+ FILE *f, *g;
+
+ // if file already exists at destination, do nothing
+ if (stb_feq(src, dest)) return STB_TRUE;
+
+ // open file
+ f = stb__fopen(src, "rb");
+ if (f == NULL) return STB_FALSE;
+
+ // open file for writing
+ g = stb__fopen(dest, "wb");
+ if (g == NULL) {
+ fclose(f);
+ return STB_FALSE;
+ }
+
+ buffer = (char *) malloc(buf_size);
+ if (buffer == NULL) {
+ buffer = raw_buffer;
+ buf_size = sizeof(raw_buffer);
+ }
+
+ while (!feof(f)) {
+ size_t n = fread(buffer, 1, buf_size, f);
+ if (n != 0)
+ fwrite(buffer, 1, n, g);
+ }
+
+ fclose(f);
+ if (buffer != raw_buffer)
+ free(buffer);
+
+ fclose(g);
+ return STB_TRUE;
+}
+
+// varlen:
+// v' = (v >> 31) + (v < 0 ? ~v : v)<<1; // small abs(v) => small v'
+// output v as big endian v'+k for v' <= k:
+// 1 byte : v' <= 0x00000080 ( -64 <= v < 64) 7 bits
+// 2 bytes: v' <= 0x00004000 (-8192 <= v < 8192) 14 bits
+// 3 bytes: v' <= 0x00200000 21 bits
+// 4 bytes: v' <= 0x10000000 28 bits
+// the number of most significant 1-bits in the first byte
+// equals the number of bytes after the first
+
+#define stb__varlen_xform(v) (v<0 ? (~v << 1)+1 : (v << 1))
+
+int stb_size_varlen(int v) { return stb_size_varlenu(stb__varlen_xform(v)); }
+int stb_size_varlenu(unsigned int v)
+{
+ if (v < 0x00000080) return 1;
+ if (v < 0x00004000) return 2;
+ if (v < 0x00200000) return 3;
+ if (v < 0x10000000) return 4;
+ return 5;
+}
+
+void stb_fput_varlen(FILE *f, int v) { stb_fput_varlenu(f, stb__varlen_xform(v)); }
+
+void stb_fput_varlenu(FILE *f, unsigned int z)
+{
+ if (z >= 0x10000000) fputc(0xF0,f);
+ if (z >= 0x00200000) fputc((z < 0x10000000 ? 0xE0 : 0)+(z>>24),f);
+ if (z >= 0x00004000) fputc((z < 0x00200000 ? 0xC0 : 0)+(z>>16),f);
+ if (z >= 0x00000080) fputc((z < 0x00004000 ? 0x80 : 0)+(z>> 8),f);
+ fputc(z,f);
+}
+
+#define stb_fgetc(f) ((unsigned char) fgetc(f))
+
+int stb_fget_varlen(FILE *f)
+{
+ unsigned int z = stb_fget_varlenu(f);
+ return (z & 1) ? ~(z>>1) : (z>>1);
+}
+
+unsigned int stb_fget_varlenu(FILE *f)
+{
+ unsigned int z;
+ unsigned char d;
+ d = stb_fgetc(f);
+
+ if (d >= 0x80) {
+ if (d >= 0xc0) {
+ if (d >= 0xe0) {
+ if (d == 0xf0) z = stb_fgetc(f) << 24;
+ else z = (d - 0xe0) << 24;
+ z += stb_fgetc(f) << 16;
+ }
+ else
+ z = (d - 0xc0) << 16;
+ z += stb_fgetc(f) << 8;
+ } else
+ z = (d - 0x80) << 8;
+ z += stb_fgetc(f);
+ } else
+ z = d;
+ return z;
+}
+
+stb_uint64 stb_fget_varlen64(FILE *f)
+{
+ stb_uint64 z;
+ unsigned char d;
+ d = stb_fgetc(f);
+
+ if (d >= 0x80) {
+ if (d >= 0xc0) {
+ if (d >= 0xe0) {
+ if (d >= 0xf0) {
+ if (d >= 0xf8) {
+ if (d >= 0xfc) {
+ if (d >= 0xfe) {
+ if (d >= 0xff)
+ z = (stb_uint64) stb_fgetc(f) << 56;
+ else
+ z = (stb_uint64) (d - 0xfe) << 56;
+ z |= (stb_uint64) stb_fgetc(f) << 48;
+ } else z = (stb_uint64) (d - 0xfc) << 48;
+ z |= (stb_uint64) stb_fgetc(f) << 40;
+ } else z = (stb_uint64) (d - 0xf8) << 40;
+ z |= (stb_uint64) stb_fgetc(f) << 32;
+ } else z = (stb_uint64) (d - 0xf0) << 32;
+ z |= (stb_uint) stb_fgetc(f) << 24;
+ } else z = (stb_uint) (d - 0xe0) << 24;
+ z |= (stb_uint) stb_fgetc(f) << 16;
+ } else z = (stb_uint) (d - 0xc0) << 16;
+ z |= (stb_uint) stb_fgetc(f) << 8;
+ } else z = (stb_uint) (d - 0x80) << 8;
+ z |= stb_fgetc(f);
+ } else
+ z = d;
+
+ return (z & 1) ? ~(z >> 1) : (z >> 1);
+}
+
+int stb_size_varlen64(stb_uint64 v)
+{
+ if (v < 0x00000080) return 1;
+ if (v < 0x00004000) return 2;
+ if (v < 0x00200000) return 3;
+ if (v < 0x10000000) return 4;
+ if (v < STB_IMM_UINT64(0x0000000800000000)) return 5;
+ if (v < STB_IMM_UINT64(0x0000040000000000)) return 6;
+ if (v < STB_IMM_UINT64(0x0002000000000000)) return 7;
+ if (v < STB_IMM_UINT64(0x0100000000000000)) return 8;
+ return 9;
+}
+
+void stb_fput_varlen64(FILE *f, stb_uint64 v)
+{
+ stb_uint64 z = stb__varlen_xform(v);
+ int first=1;
+ if (z >= STB_IMM_UINT64(0x100000000000000)) {
+ fputc(0xff,f);
+ first=0;
+ }
+ if (z >= STB_IMM_UINT64(0x02000000000000)) fputc((first ? 0xFE : 0)+(char)(z>>56),f), first=0;
+ if (z >= STB_IMM_UINT64(0x00040000000000)) fputc((first ? 0xFC : 0)+(char)(z>>48),f), first=0;
+ if (z >= STB_IMM_UINT64(0x00000800000000)) fputc((first ? 0xF8 : 0)+(char)(z>>40),f), first=0;
+ if (z >= STB_IMM_UINT64(0x00000010000000)) fputc((first ? 0xF0 : 0)+(char)(z>>32),f), first=0;
+ if (z >= STB_IMM_UINT64(0x00000000200000)) fputc((first ? 0xE0 : 0)+(char)(z>>24),f), first=0;
+ if (z >= STB_IMM_UINT64(0x00000000004000)) fputc((first ? 0xC0 : 0)+(char)(z>>16),f), first=0;
+ if (z >= STB_IMM_UINT64(0x00000000000080)) fputc((first ? 0x80 : 0)+(char)(z>> 8),f), first=0;
+ fputc((char)z,f);
+}
+
+void stb_fput_ranged(FILE *f, int v, int b, stb_uint n)
+{
+ v -= b;
+ if (n <= (1 << 31))
+ assert((stb_uint) v < n);
+ if (n > (1 << 24)) fputc(v >> 24, f);
+ if (n > (1 << 16)) fputc(v >> 16, f);
+ if (n > (1 << 8)) fputc(v >> 8, f);
+ fputc(v,f);
+}
+
+int stb_fget_ranged(FILE *f, int b, stb_uint n)
+{
+ unsigned int v=0;
+ if (n > (1 << 24)) v += stb_fgetc(f) << 24;
+ if (n > (1 << 16)) v += stb_fgetc(f) << 16;
+ if (n > (1 << 8)) v += stb_fgetc(f) << 8;
+ v += stb_fgetc(f);
+ return b+v;
+}
+
+int stb_size_ranged(int b, stb_uint n)
+{
+ if (n > (1 << 24)) return 4;
+ if (n > (1 << 16)) return 3;
+ if (n > (1 << 8)) return 2;
+ return 1;
+}
+
+void stb_fput_string(FILE *f, char *s)
+{
+ size_t len = strlen(s);
+ stb_fput_varlenu(f, (unsigned int) len);
+ fwrite(s, 1, len, f);
+}
+
+// inverse of the above algorithm
+char *stb_fget_string(FILE *f, void *p)
+{
+ char *s;
+ int len = stb_fget_varlenu(f);
+ if (len > 4096) return NULL;
+ s = p ? stb_malloc_string(p, len+1) : (char *) malloc(len+1);
+ fread(s, 1, len, f);
+ s[len] = 0;
+ return s;
+}
+
+char *stb_strdup(char *str, void *pool)
+{
+ size_t len = strlen(str);
+ char *p = stb_malloc_string(pool, len+1);
+ stb_p_strcpy_s(p, len+1, str);
+ return p;
+}
+
+// strip the trailing '/' or '\\' from a directory so we can refer to it
+// as a file for _stat()
+char *stb_strip_final_slash(char *t)
+{
+ if (t[0]) {
+ char *z = t + strlen(t) - 1;
+ // *z is the last character
+ if (*z == '\\' || *z == '/')
+ if (z != t+2 || t[1] != ':') // but don't strip it if it's e.g. "c:/"
+ *z = 0;
+ if (*z == '\\')
+ *z = '/'; // canonicalize to make sure it matches db
+ }
+ return t;
+}
+
+char *stb_strip_final_slash_regardless(char *t)
+{
+ if (t[0]) {
+ char *z = t + strlen(t) - 1;
+ // *z is the last character
+ if (*z == '\\' || *z == '/')
+ *z = 0;
+ if (*z == '\\')
+ *z = '/'; // canonicalize to make sure it matches db
+ }
+ return t;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Options parsing
+//
+
+STB_EXTERN char **stb_getopt_param(int *argc, char **argv, char *param);
+STB_EXTERN char **stb_getopt(int *argc, char **argv);
+STB_EXTERN void stb_getopt_free(char **opts);
+
+#ifdef STB_DEFINE
+
+void stb_getopt_free(char **opts)
+{
+ int i;
+ char ** o2 = opts;
+ for (i=0; i < stb_arr_len(o2); ++i)
+ free(o2[i]);
+ stb_arr_free(o2);
+}
+
+char **stb_getopt(int *argc, char **argv)
+{
+ return stb_getopt_param(argc, argv, (char*) "");
+}
+
+char **stb_getopt_param(int *argc, char **argv, char *param)
+{
+ char ** opts=NULL;
+ int i,j=1;
+ for (i=1; i < *argc; ++i) {
+ if (argv[i][0] != '-') {
+ argv[j++] = argv[i];
+ } else {
+ if (argv[i][1] == 0) { // plain - == don't parse further options
+ ++i;
+ while (i < *argc)
+ argv[j++] = argv[i++];
+ break;
+ } else if (argv[i][1] == '-') {
+ // copy argument through including initial '-' for clarity
+ stb_arr_push(opts, stb_p_strdup(argv[i]));
+ } else {
+ int k;
+ char *q = argv[i]; // traverse options list
+ for (k=1; q[k]; ++k) {
+ char *s;
+ if (strchr(param, q[k])) { // does it take a parameter?
+ char *t = &q[k+1], z = q[k];
+ size_t len=0;
+ if (*t == 0) {
+ if (i == *argc-1) { // takes a parameter, but none found
+ *argc = 0;
+ stb_getopt_free(opts);
+ return NULL;
+ }
+ t = argv[++i];
+ } else
+ k += (int) strlen(t);
+ len = strlen(t);
+ s = (char *) malloc(len+2);
+ if (!s) return NULL;
+ s[0] = z;
+ stb_p_strcpy_s(s+1, len+2, t);
+ } else {
+ // no parameter
+ s = (char *) malloc(2);
+ if (!s) return NULL;
+ s[0] = q[k];
+ s[1] = 0;
+ }
+ stb_arr_push(opts, s);
+ }
+ }
+ }
+ }
+ stb_arr_push(opts, NULL);
+ *argc = j;
+ return opts;
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Portable directory reading
+//
+
+STB_EXTERN char **stb_readdir_files (char *dir);
+STB_EXTERN char **stb_readdir_files_mask(char *dir, char *wild);
+STB_EXTERN char **stb_readdir_subdirs(char *dir);
+STB_EXTERN char **stb_readdir_subdirs_mask(char *dir, char *wild);
+STB_EXTERN void stb_readdir_free (char **files);
+STB_EXTERN char **stb_readdir_recursive(char *dir, char *filespec);
+STB_EXTERN void stb_delete_directory_recursive(char *dir);
+
+#ifdef STB_DEFINE
+
+#ifdef _MSC_VER
+#include
+#else
+#include
+#include
+#endif
+
+void stb_readdir_free(char **files)
+{
+ char **f2 = files;
+ int i;
+ for (i=0; i < stb_arr_len(f2); ++i)
+ free(f2[i]);
+ stb_arr_free(f2);
+}
+
+static int isdotdirname(char *name)
+{
+ if (name[0] == '.')
+ return (name[1] == '.') ? !name[2] : !name[1];
+ return 0;
+}
+
+STB_EXTERN int stb_wildmatchi(char *expr, char *candidate);
+static char **readdir_raw(char *dir, int return_subdirs, char *mask)
+{
+ char **results = NULL;
+ char buffer[4096], with_slash[4096];
+ size_t n;
+
+ #ifdef WIN32
+ stb__wchar *ws;
+ struct _wfinddata_t data;
+ #ifdef _WIN64
+ const intptr_t none = -1;
+ intptr_t z;
+ #else
+ const long none = -1;
+ long z;
+ #endif
+ #else // !WIN32
+ const DIR *none = NULL;
+ DIR *z;
+ #endif
+
+ n = stb_strscpy(buffer,dir,sizeof(buffer));
+ if (!n || n >= sizeof(buffer))
+ return NULL;
+ stb_fixpath(buffer);
+
+ if (n > 0 && (buffer[n-1] != '/')) {
+ buffer[n++] = '/';
+ }
+ buffer[n] = 0;
+ if (!stb_strscpy(with_slash,buffer,sizeof(with_slash)))
+ return NULL;
+
+ #ifdef WIN32
+ if (!stb_strscpy(buffer+n,"*.*",sizeof(buffer)-n))
+ return NULL;
+ ws = stb__from_utf8(buffer);
+ z = _wfindfirst((wchar_t *)ws, &data);
+ #else
+ z = opendir(dir);
+ #endif
+
+ if (z != none) {
+ int nonempty = STB_TRUE;
+ #ifndef WIN32
+ struct dirent *data = readdir(z);
+ nonempty = (data != NULL);
+ #endif
+
+ if (nonempty) {
+
+ do {
+ int is_subdir;
+ #ifdef WIN32
+ char *name = stb__to_utf8((stb__wchar *)data.name);
+ if (name == NULL) {
+ fprintf(stderr, "%s to convert '%S' to %s!\n", "Unable", data.name, "utf8");
+ continue;
+ }
+ is_subdir = !!(data.attrib & _A_SUBDIR);
+ #else
+ char *name = data->d_name;
+ if (!stb_strscpy(buffer+n,name,sizeof(buffer)-n))
+ break;
+ // Could follow DT_LNK, but would need to check for recursive links.
+ is_subdir = !!(data->d_type & DT_DIR);
+ #endif
+
+ if (is_subdir == return_subdirs) {
+ if (!is_subdir || !isdotdirname(name)) {
+ if (!mask || stb_wildmatchi(mask, name)) {
+ char buffer[4096],*p=buffer;
+ if ( stb_snprintf(buffer, sizeof(buffer), "%s%s", with_slash, name) < 0 )
+ break;
+ if (buffer[0] == '.' && buffer[1] == '/')
+ p = buffer+2;
+ stb_arr_push(results, stb_p_strdup(p));
+ }
+ }
+ }
+ }
+ #ifdef WIN32
+ while (0 == _wfindnext(z, &data));
+ #else
+ while ((data = readdir(z)) != NULL);
+ #endif
+ }
+ #ifdef WIN32
+ _findclose(z);
+ #else
+ closedir(z);
+ #endif
+ }
+ return results;
+}
+
+char **stb_readdir_files (char *dir) { return readdir_raw(dir, 0, NULL); }
+char **stb_readdir_subdirs(char *dir) { return readdir_raw(dir, 1, NULL); }
+char **stb_readdir_files_mask(char *dir, char *wild) { return readdir_raw(dir, 0, wild); }
+char **stb_readdir_subdirs_mask(char *dir, char *wild) { return readdir_raw(dir, 1, wild); }
+
+int stb__rec_max=0x7fffffff;
+static char **stb_readdir_rec(char **sofar, char *dir, char *filespec)
+{
+ char **files;
+ char ** dirs;
+ char **p;
+
+ if (stb_arr_len(sofar) >= stb__rec_max) return sofar;
+
+ files = stb_readdir_files_mask(dir, filespec);
+ stb_arr_for(p, files) {
+ stb_arr_push(sofar, stb_p_strdup(*p));
+ if (stb_arr_len(sofar) >= stb__rec_max) break;
+ }
+ stb_readdir_free(files);
+ if (stb_arr_len(sofar) >= stb__rec_max) return sofar;
+
+ dirs = stb_readdir_subdirs(dir);
+ stb_arr_for(p, dirs)
+ sofar = stb_readdir_rec(sofar, *p, filespec);
+ stb_readdir_free(dirs);
+ return sofar;
+}
+
+char **stb_readdir_recursive(char *dir, char *filespec)
+{
+ return stb_readdir_rec(NULL, dir, filespec);
+}
+
+void stb_delete_directory_recursive(char *dir)
+{
+ char **list = stb_readdir_subdirs(dir);
+ int i;
+ for (i=0; i < stb_arr_len(list); ++i)
+ stb_delete_directory_recursive(list[i]);
+ stb_arr_free(list);
+ list = stb_readdir_files(dir);
+ for (i=0; i < stb_arr_len(list); ++i)
+ if (!remove(list[i])) {
+ // on windows, try again after making it writeable; don't ALWAYS
+ // do this first since that would be slow in the normal case
+ #ifdef _MSC_VER
+ _chmod(list[i], _S_IWRITE);
+ remove(list[i]);
+ #endif
+ }
+ stb_arr_free(list);
+ stb__windows(_rmdir,rmdir)(dir);
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// construct trees from filenames; useful for cmirror summaries
+
+typedef struct stb_dirtree2 stb_dirtree2;
+
+struct stb_dirtree2
+{
+ stb_dirtree2 **subdirs;
+
+ // make convenient for stb_summarize_tree
+ int num_subdir;
+ float weight;
+
+ // actual data
+ char *fullpath;
+ char *relpath;
+ char **files;
+};
+
+STB_EXTERN stb_dirtree2 *stb_dirtree2_from_files_relative(char *src, char **filelist, int count);
+STB_EXTERN stb_dirtree2 *stb_dirtree2_from_files(char **filelist, int count);
+STB_EXTERN int stb_dir_is_prefix(char *dir, int dirlen, char *file);
+
+#ifdef STB_DEFINE
+
+int stb_dir_is_prefix(char *dir, int dirlen, char *file)
+{
+ if (dirlen == 0) return STB_TRUE;
+ if (stb_strnicmp(dir, file, dirlen)) return STB_FALSE;
+ if (file[dirlen] == '/' || file[dirlen] == '\\') return STB_TRUE;
+ return STB_FALSE;
+}
+
+stb_dirtree2 *stb_dirtree2_from_files_relative(char *src, char **filelist, int count)
+{
+ char buffer1[1024];
+ int i;
+ int dlen = (int) strlen(src), elen;
+ stb_dirtree2 *d;
+ char ** descendents = NULL;
+ char ** files = NULL;
+ char *s;
+ if (!count) return NULL;
+ // first find all the ones that belong here... note this is will take O(NM) with N files and M subdirs
+ for (i=0; i < count; ++i) {
+ if (stb_dir_is_prefix(src, dlen, filelist[i])) {
+ stb_arr_push(descendents, filelist[i]);
+ }
+ }
+ if (descendents == NULL)
+ return NULL;
+ elen = dlen;
+ // skip a leading slash
+ if (elen == 0 && (descendents[0][0] == '/' || descendents[0][0] == '\\'))
+ ++elen;
+ else if (elen)
+ ++elen;
+ // now extract all the ones that have their root here
+ for (i=0; i < stb_arr_len(descendents);) {
+ if (!stb_strchr2(descendents[i]+elen, '/', '\\')) {
+ stb_arr_push(files, descendents[i]);
+ descendents[i] = descendents[stb_arr_len(descendents)-1];
+ stb_arr_pop(descendents);
+ } else
+ ++i;
+ }
+ // now create a record
+ d = (stb_dirtree2 *) malloc(sizeof(*d));
+ d->files = files;
+ d->subdirs = NULL;
+ d->fullpath = stb_p_strdup(src);
+ s = stb_strrchr2(d->fullpath, '/', '\\');
+ if (s)
+ ++s;
+ else
+ s = d->fullpath;
+ d->relpath = s;
+ // now create the children
+ qsort(descendents, stb_arr_len(descendents), sizeof(char *), stb_qsort_stricmp(0));
+ buffer1[0] = 0;
+ for (i=0; i < stb_arr_len(descendents); ++i) {
+ char buffer2[1024];
+ char *s = descendents[i] + elen, *t;
+ t = stb_strchr2(s, '/', '\\');
+ assert(t);
+ stb_strncpy(buffer2, descendents[i], (int) (t-descendents[i]+1));
+ if (stb_stricmp(buffer1, buffer2)) {
+ stb_dirtree2 *t = stb_dirtree2_from_files_relative(buffer2, descendents, stb_arr_len(descendents));
+ assert(t != NULL);
+ stb_p_strcpy_s(buffer1, sizeof(buffer1), buffer2);
+ stb_arr_push(d->subdirs, t);
+ }
+ }
+ d->num_subdir = stb_arr_len(d->subdirs);
+ d->weight = 0;
+ return d;
+}
+
+stb_dirtree2 *stb_dirtree2_from_files(char **filelist, int count)
+{
+ return stb_dirtree2_from_files_relative((char*) "", filelist, count);
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Checksums: CRC-32, ADLER32, SHA-1
+//
+// CRC-32 and ADLER32 allow streaming blocks
+// SHA-1 requires either a complete buffer, max size 2^32 - 73
+// or it can checksum directly from a file, max 2^61
+
+#define STB_ADLER32_SEED 1
+#define STB_CRC32_SEED 0 // note that we logical NOT this in the code
+
+STB_EXTERN stb_uint
+ stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uint buflen);
+STB_EXTERN stb_uint
+ stb_crc32_block(stb_uint crc32, stb_uchar *buffer, stb_uint len);
+STB_EXTERN stb_uint stb_crc32(unsigned char *buffer, stb_uint len);
+
+STB_EXTERN void stb_sha1(
+ unsigned char output[20], unsigned char *buffer, unsigned int len);
+STB_EXTERN int stb_sha1_file(unsigned char output[20], char *file);
+
+STB_EXTERN void stb_sha1_readable(char display[27], unsigned char sha[20]);
+
+#ifdef STB_DEFINE
+stb_uint stb_crc32_block(stb_uint crc, unsigned char *buffer, stb_uint len)
+{
+ static stb_uint crc_table[256];
+ stb_uint i,j,s;
+ crc = ~crc;
+
+ if (crc_table[1] == 0)
+ for(i=0; i < 256; i++) {
+ for (s=i, j=0; j < 8; ++j)
+ s = (s >> 1) ^ (s & 1 ? 0xedb88320 : 0);
+ crc_table[i] = s;
+ }
+ for (i=0; i < len; ++i)
+ crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
+ return ~crc;
+}
+
+stb_uint stb_crc32(unsigned char *buffer, stb_uint len)
+{
+ return stb_crc32_block(0, buffer, len);
+}
+
+stb_uint stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uint buflen)
+{
+ const unsigned long ADLER_MOD = 65521;
+ unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16;
+ unsigned long blocklen, i;
+
+ blocklen = buflen % 5552;
+ while (buflen) {
+ for (i=0; i + 7 < blocklen; i += 8) {
+ s1 += buffer[0], s2 += s1;
+ s1 += buffer[1], s2 += s1;
+ s1 += buffer[2], s2 += s1;
+ s1 += buffer[3], s2 += s1;
+ s1 += buffer[4], s2 += s1;
+ s1 += buffer[5], s2 += s1;
+ s1 += buffer[6], s2 += s1;
+ s1 += buffer[7], s2 += s1;
+
+ buffer += 8;
+ }
+
+ for (; i < blocklen; ++i)
+ s1 += *buffer++, s2 += s1;
+
+ s1 %= ADLER_MOD, s2 %= ADLER_MOD;
+ buflen -= blocklen;
+ blocklen = 5552;
+ }
+ return (s2 << 16) + s1;
+}
+
+static void stb__sha1(stb_uchar *chunk, stb_uint h[5])
+{
+ int i;
+ stb_uint a,b,c,d,e;
+ stb_uint w[80];
+
+ for (i=0; i < 16; ++i)
+ w[i] = stb_big32(&chunk[i*4]);
+ for (i=16; i < 80; ++i) {
+ stb_uint t;
+ t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
+ w[i] = (t + t) | (t >> 31);
+ }
+
+ a = h[0];
+ b = h[1];
+ c = h[2];
+ d = h[3];
+ e = h[4];
+
+ #define STB__SHA1(k,f) \
+ { \
+ stb_uint temp = (a << 5) + (a >> 27) + (f) + e + (k) + w[i]; \
+ e = d; \
+ d = c; \
+ c = (b << 30) + (b >> 2); \
+ b = a; \
+ a = temp; \
+ }
+
+ i=0;
+ for (; i < 20; ++i) STB__SHA1(0x5a827999, d ^ (b & (c ^ d)) );
+ for (; i < 40; ++i) STB__SHA1(0x6ed9eba1, b ^ c ^ d );
+ for (; i < 60; ++i) STB__SHA1(0x8f1bbcdc, (b & c) + (d & (b ^ c)) );
+ for (; i < 80; ++i) STB__SHA1(0xca62c1d6, b ^ c ^ d );
+
+ #undef STB__SHA1
+
+ h[0] += a;
+ h[1] += b;
+ h[2] += c;
+ h[3] += d;
+ h[4] += e;
+}
+
+void stb_sha1(stb_uchar output[20], stb_uchar *buffer, stb_uint len)
+{
+ unsigned char final_block[128];
+ stb_uint end_start, final_len, j;
+ int i;
+
+ stb_uint h[5];
+
+ h[0] = 0x67452301;
+ h[1] = 0xefcdab89;
+ h[2] = 0x98badcfe;
+ h[3] = 0x10325476;
+ h[4] = 0xc3d2e1f0;
+
+ // we need to write padding to the last one or two
+ // blocks, so build those first into 'final_block'
+
+ // we have to write one special byte, plus the 8-byte length
+
+ // compute the block where the data runs out
+ end_start = len & ~63;
+
+ // compute the earliest we can encode the length
+ if (((len+9) & ~63) == end_start) {
+ // it all fits in one block, so fill a second-to-last block
+ end_start -= 64;
+ }
+
+ final_len = end_start + 128;
+
+ // now we need to copy the data in
+ assert(end_start + 128 >= len+9);
+ assert(end_start < len || len < 64-9);
+
+ j = 0;
+ if (end_start > len)
+ j = (stb_uint) - (int) end_start;
+
+ for (; end_start + j < len; ++j)
+ final_block[j] = buffer[end_start + j];
+ final_block[j++] = 0x80;
+ while (j < 128-5) // 5 byte length, so write 4 extra padding bytes
+ final_block[j++] = 0;
+ // big-endian size
+ final_block[j++] = len >> 29;
+ final_block[j++] = len >> 21;
+ final_block[j++] = len >> 13;
+ final_block[j++] = len >> 5;
+ final_block[j++] = len << 3;
+ assert(j == 128 && end_start + j == final_len);
+
+ for (j=0; j < final_len; j += 64) { // 512-bit chunks
+ if (j+64 >= end_start+64)
+ stb__sha1(&final_block[j - end_start], h);
+ else
+ stb__sha1(&buffer[j], h);
+ }
+
+ for (i=0; i < 5; ++i) {
+ output[i*4 + 0] = h[i] >> 24;
+ output[i*4 + 1] = h[i] >> 16;
+ output[i*4 + 2] = h[i] >> 8;
+ output[i*4 + 3] = h[i] >> 0;
+ }
+}
+
+#ifdef _MSC_VER
+int stb_sha1_file(stb_uchar output[20], char *file)
+{
+ int i;
+ stb_uint64 length=0;
+ unsigned char buffer[128];
+
+ FILE *f = stb__fopen(file, "rb");
+ stb_uint h[5];
+
+ if (f == NULL) return 0; // file not found
+
+ h[0] = 0x67452301;
+ h[1] = 0xefcdab89;
+ h[2] = 0x98badcfe;
+ h[3] = 0x10325476;
+ h[4] = 0xc3d2e1f0;
+
+ for(;;) {
+ size_t n = fread(buffer, 1, 64, f);
+ if (n == 64) {
+ stb__sha1(buffer, h);
+ length += n;
+ } else {
+ int block = 64;
+
+ length += n;
+
+ buffer[n++] = 0x80;
+
+ // if there isn't enough room for the length, double the block
+ if (n + 8 > 64)
+ block = 128;
+
+ // pad to end
+ memset(buffer+n, 0, block-8-n);
+
+ i = block - 8;
+ buffer[i++] = (stb_uchar) (length >> 53);
+ buffer[i++] = (stb_uchar) (length >> 45);
+ buffer[i++] = (stb_uchar) (length >> 37);
+ buffer[i++] = (stb_uchar) (length >> 29);
+ buffer[i++] = (stb_uchar) (length >> 21);
+ buffer[i++] = (stb_uchar) (length >> 13);
+ buffer[i++] = (stb_uchar) (length >> 5);
+ buffer[i++] = (stb_uchar) (length << 3);
+ assert(i == block);
+ stb__sha1(buffer, h);
+ if (block == 128)
+ stb__sha1(buffer+64, h);
+ else
+ assert(block == 64);
+ break;
+ }
+ }
+ fclose(f);
+
+ for (i=0; i < 5; ++i) {
+ output[i*4 + 0] = h[i] >> 24;
+ output[i*4 + 1] = h[i] >> 16;
+ output[i*4 + 2] = h[i] >> 8;
+ output[i*4 + 3] = h[i] >> 0;
+ }
+
+ return 1;
+}
+#endif // _MSC_VER
+
+// client can truncate this wherever they like
+void stb_sha1_readable(char display[27], unsigned char sha[20])
+{
+ char encoding[65] = "0123456789abcdefghijklmnopqrstuv"
+ "wxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%$";
+ int num_bits = 0, acc=0;
+ int i=0,o=0;
+ while (o < 26) {
+ int v;
+ // expand the accumulator
+ if (num_bits < 6) {
+ assert(i != 20);
+ acc += sha[i++] << num_bits;
+ num_bits += 8;
+ }
+ v = acc & ((1 << 6) - 1);
+ display[o++] = encoding[v];
+ acc >>= 6;
+ num_bits -= 6;
+ }
+ assert(num_bits == 20*8 - 26*6);
+ display[o++] = encoding[acc];
+}
+
+#endif // STB_DEFINE
+
+///////////////////////////////////////////////////////////
+//
+// simplified WINDOWS registry interface... hopefully
+// we'll never actually use this?
+
+#if defined(_WIN32)
+
+STB_EXTERN void * stb_reg_open(const char *mode, const char *where); // mode: "rHKLM" or "rHKCU" or "w.."
+STB_EXTERN void stb_reg_close(void *reg);
+STB_EXTERN int stb_reg_read(void *zreg, const char *str, void *data, unsigned long len);
+STB_EXTERN int stb_reg_read_string(void *zreg, const char *str, char *data, int len);
+STB_EXTERN void stb_reg_write(void *zreg, const char *str, const void *data, unsigned long len);
+STB_EXTERN void stb_reg_write_string(void *zreg, const char *str, const char *data);
+
+#if defined(STB_DEFINE) && !defined(STB_NO_REGISTRY)
+
+#define STB_HAS_REGISTRY
+
+#ifndef _WINDOWS_
+
+#define HKEY void *
+
+STB_EXTERN __declspec(dllimport) long __stdcall RegCloseKey ( HKEY hKey );
+STB_EXTERN __declspec(dllimport) long __stdcall RegCreateKeyExA ( HKEY hKey, const char * lpSubKey,
+ int Reserved, char * lpClass, int dwOptions,
+ int samDesired, void *lpSecurityAttributes, HKEY * phkResult, int * lpdwDisposition );
+STB_EXTERN __declspec(dllimport) long __stdcall RegDeleteKeyA ( HKEY hKey, const char * lpSubKey );
+STB_EXTERN __declspec(dllimport) long __stdcall RegQueryValueExA ( HKEY hKey, const char * lpValueName,
+ int * lpReserved, unsigned long * lpType, unsigned char * lpData, unsigned long * lpcbData );
+STB_EXTERN __declspec(dllimport) long __stdcall RegSetValueExA ( HKEY hKey, const char * lpValueName,
+ int Reserved, int dwType, const unsigned char* lpData, int cbData );
+STB_EXTERN __declspec(dllimport) long __stdcall RegOpenKeyExA ( HKEY hKey, const char * lpSubKey,
+ int ulOptions, int samDesired, HKEY * phkResult );
+
+#endif // _WINDOWS_
+
+#define STB__REG_OPTION_NON_VOLATILE 0
+#define STB__REG_KEY_ALL_ACCESS 0x000f003f
+#define STB__REG_KEY_READ 0x00020019
+
+#ifdef _M_AMD64
+#define STB__HKEY_CURRENT_USER 0x80000001ull
+#define STB__HKEY_LOCAL_MACHINE 0x80000002ull
+#else
+#define STB__HKEY_CURRENT_USER 0x80000001
+#define STB__HKEY_LOCAL_MACHINE 0x80000002
+#endif
+
+void *stb_reg_open(const char *mode, const char *where)
+{
+ long res;
+ HKEY base;
+ HKEY zreg;
+ if (!stb_stricmp(mode+1, "cu") || !stb_stricmp(mode+1, "hkcu"))
+ base = (HKEY) STB__HKEY_CURRENT_USER;
+ else if (!stb_stricmp(mode+1, "lm") || !stb_stricmp(mode+1, "hklm"))
+ base = (HKEY) STB__HKEY_LOCAL_MACHINE;
+ else
+ return NULL;
+
+ if (mode[0] == 'r')
+ res = RegOpenKeyExA(base, where, 0, STB__REG_KEY_READ, &zreg);
+ else if (mode[0] == 'w')
+ res = RegCreateKeyExA(base, where, 0, NULL, STB__REG_OPTION_NON_VOLATILE, STB__REG_KEY_ALL_ACCESS, NULL, &zreg, NULL);
+ else
+ return NULL;
+
+ return res ? NULL : zreg;
+}
+
+void stb_reg_close(void *reg)
+{
+ RegCloseKey((HKEY) reg);
+}
+
+#define STB__REG_SZ 1
+#define STB__REG_BINARY 3
+#define STB__REG_DWORD 4
+
+int stb_reg_read(void *zreg, const char *str, void *data, unsigned long len)
+{
+ unsigned long type;
+ unsigned long alen = len;
+ if (0 == RegQueryValueExA((HKEY) zreg, str, 0, &type, (unsigned char *) data, &len))
+ if (type == STB__REG_BINARY || type == STB__REG_SZ || type == STB__REG_DWORD) {
+ if (len < alen)
+ *((char *) data + len) = 0;
+ return 1;
+ }
+ return 0;
+}
+
+void stb_reg_write(void *zreg, const char *str, const void *data, unsigned long len)
+{
+ if (zreg)
+ RegSetValueExA((HKEY) zreg, str, 0, STB__REG_BINARY, (const unsigned char *) data, len);
+}
+
+int stb_reg_read_string(void *zreg, const char *str, char *data, int len)
+{
+ if (!stb_reg_read(zreg, str, data, len)) return 0;
+ data[len-1] = 0; // force a 0 at the end of the string no matter what
+ return 1;
+}
+
+void stb_reg_write_string(void *zreg, const char *str, const char *data)
+{
+ if (zreg)
+ RegSetValueExA((HKEY) zreg, str, 0, STB__REG_SZ, (const unsigned char *) data, (int) strlen(data)+1);
+}
+#endif // STB_DEFINE
+#endif // _WIN32
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_cfg - This is like the registry, but the config info
+// is all stored in plain old files where we can
+// backup and restore them easily. The LOCATION of
+// the config files is gotten from... the registry!
+
+#ifndef STB_NO_STB_STRINGS
+typedef struct stb_cfg_st stb_cfg;
+
+STB_EXTERN stb_cfg * stb_cfg_open(char *config, const char *mode); // mode = "r", "w"
+STB_EXTERN void stb_cfg_close(stb_cfg *cfg);
+STB_EXTERN int stb_cfg_read(stb_cfg *cfg, char *key, void *value, int len);
+STB_EXTERN void stb_cfg_write(stb_cfg *cfg, char *key, void *value, int len);
+STB_EXTERN int stb_cfg_read_string(stb_cfg *cfg, char *key, char *value, int len);
+STB_EXTERN void stb_cfg_write_string(stb_cfg *cfg, char *key, char *value);
+STB_EXTERN int stb_cfg_delete(stb_cfg *cfg, char *key);
+STB_EXTERN void stb_cfg_set_directory(char *dir);
+
+#ifdef STB_DEFINE
+
+typedef struct
+{
+ char *key;
+ void *value;
+ int value_len;
+} stb__cfg_item;
+
+struct stb_cfg_st
+{
+ stb__cfg_item *data;
+ char *loaded_file; // this needs to be freed
+ FILE *f; // write the data to this file on close
+};
+
+static const char *stb__cfg_sig = "sTbCoNfIg!\0\0";
+static char stb__cfg_dir[512];
+STB_EXTERN void stb_cfg_set_directory(char *dir)
+{
+ stb_p_strcpy_s(stb__cfg_dir, sizeof(stb__cfg_dir), dir);
+}
+
+STB_EXTERN stb_cfg * stb_cfg_open(char *config, const char *mode)
+{
+ size_t len;
+ stb_cfg *z;
+ char file[512];
+ if (mode[0] != 'r' && mode[0] != 'w') return NULL;
+
+ if (!stb__cfg_dir[0]) {
+ #ifdef _WIN32
+ stb_p_strcpy_s(stb__cfg_dir, sizeof(stb__cfg_dir), "c:/stb");
+ #else
+ strcpy(stb__cfg_dir, "~/.stbconfig");
+ #endif
+
+ #ifdef STB_HAS_REGISTRY
+ {
+ void *reg = stb_reg_open("rHKLM", "Software\\SilverSpaceship\\stb");
+ if (reg) {
+ stb_reg_read_string(reg, "config_dir", stb__cfg_dir, sizeof(stb__cfg_dir));
+ stb_reg_close(reg);
+ }
+ }
+ #endif
+ }
+
+ stb_p_sprintf(file stb_p_size(sizeof(file)), "%s/%s.cfg", stb__cfg_dir, config);
+
+ z = (stb_cfg *) stb_malloc(0, sizeof(*z));
+ z->data = NULL;
+
+ z->loaded_file = stb_filec(file, &len);
+ if (z->loaded_file) {
+ char *s = z->loaded_file;
+ if (!memcmp(s, stb__cfg_sig, 12)) {
+ char *s = z->loaded_file + 12;
+ while (s < z->loaded_file + len) {
+ stb__cfg_item a;
+ int n = *(stb_int16 *) s;
+ a.key = s+2;
+ s = s+2 + n;
+ a.value_len = *(int *) s;
+ s += 4;
+ a.value = s;
+ s += a.value_len;
+ stb_arr_push(z->data, a);
+ }
+ assert(s == z->loaded_file + len);
+ }
+ }
+
+ if (mode[0] == 'w')
+ z->f = stb_p_fopen(file, "wb");
+ else
+ z->f = NULL;
+
+ return z;
+}
+
+void stb_cfg_close(stb_cfg *z)
+{
+ if (z->f) {
+ int i;
+ // write the file out
+ fwrite(stb__cfg_sig, 12, 1, z->f);
+ for (i=0; i < stb_arr_len(z->data); ++i) {
+ stb_int16 n = (stb_int16) strlen(z->data[i].key)+1;
+ fwrite(&n, 2, 1, z->f);
+ fwrite(z->data[i].key, n, 1, z->f);
+ fwrite(&z->data[i].value_len, 4, 1, z->f);
+ fwrite(z->data[i].value, z->data[i].value_len, 1, z->f);
+ }
+ fclose(z->f);
+ }
+ stb_arr_free(z->data);
+ stb_free(z);
+}
+
+int stb_cfg_read(stb_cfg *z, char *key, void *value, int len)
+{
+ int i;
+ for (i=0; i < stb_arr_len(z->data); ++i) {
+ if (!stb_stricmp(z->data[i].key, key)) {
+ int n = stb_min(len, z->data[i].value_len);
+ memcpy(value, z->data[i].value, n);
+ if (n < len)
+ *((char *) value + n) = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void stb_cfg_write(stb_cfg *z, char *key, void *value, int len)
+{
+ int i;
+ for (i=0; i < stb_arr_len(z->data); ++i)
+ if (!stb_stricmp(z->data[i].key, key))
+ break;
+ if (i == stb_arr_len(z->data)) {
+ stb__cfg_item p;
+ p.key = stb_strdup(key, z);
+ p.value = NULL;
+ p.value_len = 0;
+ stb_arr_push(z->data, p);
+ }
+ z->data[i].value = stb_malloc(z, len);
+ z->data[i].value_len = len;
+ memcpy(z->data[i].value, value, len);
+}
+
+int stb_cfg_delete(stb_cfg *z, char *key)
+{
+ int i;
+ for (i=0; i < stb_arr_len(z->data); ++i)
+ if (!stb_stricmp(z->data[i].key, key)) {
+ stb_arr_fastdelete(z->data, i);
+ return 1;
+ }
+ return 0;
+}
+
+int stb_cfg_read_string(stb_cfg *z, char *key, char *value, int len)
+{
+ if (!stb_cfg_read(z, key, value, len)) return 0;
+ value[len-1] = 0;
+ return 1;
+}
+
+void stb_cfg_write_string(stb_cfg *z, char *key, char *value)
+{
+ stb_cfg_write(z, key, value, (int) strlen(value)+1);
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_dirtree - load a description of a directory tree
+// uses a cache and stat()s the directories for changes
+// MUCH faster on NTFS, _wrong_ on FAT32, so should
+// ignore the db on FAT32
+
+#ifdef _WIN32
+
+typedef struct
+{
+ char * path; // full path from passed-in root
+ time_t last_modified;
+ int num_files;
+ int flag;
+} stb_dirtree_dir;
+
+typedef struct
+{
+ char *name; // name relative to path
+ int dir; // index into dirs[] array
+ stb_int64 size; // size, max 4GB
+ time_t last_modified;
+ int flag;
+} stb_dirtree_file;
+
+typedef struct
+{
+ stb_dirtree_dir *dirs;
+ stb_dirtree_file *files;
+
+ // internal use
+ void * string_pool; // used to free data en masse
+} stb_dirtree;
+
+extern void stb_dirtree_free ( stb_dirtree *d );
+extern stb_dirtree *stb_dirtree_get ( char *dir);
+extern stb_dirtree *stb_dirtree_get_dir ( char *dir, char *cache_dir);
+extern stb_dirtree *stb_dirtree_get_with_file ( char *dir, char *cache_file);
+
+// get a list of all the files recursively underneath 'dir'
+//
+// cache_file is used to store a copy of the directory tree to speed up
+// later calls. It must be unique to 'dir' and the current working
+// directory! Otherwise who knows what will happen (a good solution
+// is to put it _in_ dir, but this API doesn't force that).
+//
+// Also, it might be possible to break this if you have two different processes
+// do a call to stb_dirtree_get() with the same cache file at about the same
+// time, but I _think_ it might just work.
+
+// i needed to build an identical data structure representing the state of
+// a mirrored copy WITHOUT bothering to rescan it (i.e. we're mirroring to
+// it WITHOUT scanning it, e.g. it's over the net), so this requires access
+// to all of the innards.
+extern void stb_dirtree_db_add_dir(stb_dirtree *active, char *path, time_t last);
+extern void stb_dirtree_db_add_file(stb_dirtree *active, char *name, int dir, stb_int64 size, time_t last);
+extern void stb_dirtree_db_read(stb_dirtree *target, char *filename, char *dir);
+extern void stb_dirtree_db_write(stb_dirtree *target, char *filename, char *dir);
+
+#ifdef STB_DEFINE
+static void stb__dirtree_add_dir(char *path, time_t last, stb_dirtree *active)
+{
+ stb_dirtree_dir d;
+ d.last_modified = last;
+ d.num_files = 0;
+ d.path = stb_strdup(path, active->string_pool);
+ stb_arr_push(active->dirs, d);
+}
+
+static void stb__dirtree_add_file(char *name, int dir, stb_int64 size, time_t last, stb_dirtree *active)
+{
+ stb_dirtree_file f;
+ f.dir = dir;
+ f.size = size;
+ f.last_modified = last;
+ f.name = stb_strdup(name, active->string_pool);
+ ++active->dirs[dir].num_files;
+ stb_arr_push(active->files, f);
+}
+
+// version 02 supports > 4GB files
+static char stb__signature[12] = { 's', 'T', 'b', 'D', 'i', 'R', 't', 'R', 'e', 'E', '0', '2' };
+
+static void stb__dirtree_save_db(char *filename, stb_dirtree *data, char *root)
+{
+ int i, num_dirs_final=0, num_files_final;
+ char *info = root ? root : (char*)"";
+ int *remap;
+ FILE *f = stb_p_fopen(filename, "wb");
+ if (!f) return;
+
+ fwrite(stb__signature, sizeof(stb__signature), 1, f);
+ fwrite(info, strlen(info)+1, 1, f);
+ // need to be slightly tricky and not write out NULLed directories, nor the root
+
+ // build remapping table of all dirs we'll be writing out
+ remap = (int *) malloc(sizeof(remap[0]) * stb_arr_len(data->dirs));
+ for (i=0; i < stb_arr_len(data->dirs); ++i) {
+ if (data->dirs[i].path == NULL || (root && 0==stb_stricmp(data->dirs[i].path, root))) {
+ remap[i] = -1;
+ } else {
+ remap[i] = num_dirs_final++;
+ }
+ }
+
+ fwrite(&num_dirs_final, 4, 1, f);
+ for (i=0; i < stb_arr_len(data->dirs); ++i) {
+ if (remap[i] >= 0) {
+ fwrite(&data->dirs[i].last_modified, 4, 1, f);
+ stb_fput_string(f, data->dirs[i].path);
+ }
+ }
+
+ num_files_final = 0;
+ for (i=0; i < stb_arr_len(data->files); ++i)
+ if (remap[data->files[i].dir] >= 0 && data->files[i].name)
+ ++num_files_final;
+
+ fwrite(&num_files_final, 4, 1, f);
+ for (i=0; i < stb_arr_len(data->files); ++i) {
+ if (remap[data->files[i].dir] >= 0 && data->files[i].name) {
+ stb_fput_ranged(f, remap[data->files[i].dir], 0, num_dirs_final);
+ stb_fput_varlen64(f, data->files[i].size);
+ fwrite(&data->files[i].last_modified, 4, 1, f);
+ stb_fput_string(f, data->files[i].name);
+ }
+ }
+
+ fclose(f);
+}
+
+// note: stomps any existing data, rather than appending
+static void stb__dirtree_load_db(char *filename, stb_dirtree *data, char *dir)
+{
+ char sig[2048];
+ int i,n;
+ FILE *f = stb_p_fopen(filename, "rb");
+
+ if (!f) return;
+
+ data->string_pool = stb_malloc(0,1);
+
+ fread(sig, sizeof(stb__signature), 1, f);
+ if (memcmp(stb__signature, sig, sizeof(stb__signature))) { fclose(f); return; }
+ if (!fread(sig, strlen(dir)+1, 1, f)) { fclose(f); return; }
+ if (stb_stricmp(sig,dir)) { fclose(f); return; }
+
+ // we can just read them straight in, because they're guaranteed to be valid
+ fread(&n, 4, 1, f);
+ stb_arr_setlen(data->dirs, n);
+ for(i=0; i < stb_arr_len(data->dirs); ++i) {
+ fread(&data->dirs[i].last_modified, 4, 1, f);
+ data->dirs[i].path = stb_fget_string(f, data->string_pool);
+ if (data->dirs[i].path == NULL) goto bail;
+ }
+ fread(&n, 4, 1, f);
+ stb_arr_setlen(data->files, n);
+ for (i=0; i < stb_arr_len(data->files); ++i) {
+ data->files[i].dir = stb_fget_ranged(f, 0, stb_arr_len(data->dirs));
+ data->files[i].size = stb_fget_varlen64(f);
+ fread(&data->files[i].last_modified, 4, 1, f);
+ data->files[i].name = stb_fget_string(f, data->string_pool);
+ if (data->files[i].name == NULL) goto bail;
+ }
+
+ if (0) {
+ bail:
+ stb_arr_free(data->dirs);
+ stb_arr_free(data->files);
+ }
+ fclose(f);
+}
+
+FILE *hlog;
+
+static int stb__dircount, stb__dircount_mask, stb__showfile;
+static void stb__dirtree_scandir(char *path, time_t last_time, stb_dirtree *active)
+{
+ // this is dumb depth first; theoretically it might be faster
+ // to fully traverse each directory before visiting its children,
+ // but it's complicated and didn't seem like a gain in the test app
+
+ int n;
+
+ struct _wfinddatai64_t c_file;
+ long hFile;
+ stb__wchar full_path[1024];
+ int has_slash;
+ if (stb__showfile) printf("<");
+
+ has_slash = (path[0] && path[strlen(path)-1] == '/');
+
+ // @TODO: do this concatenation without using swprintf to avoid this mess:
+#if (defined(_MSC_VER) && _MSC_VER < 1400) // || (defined(__clang__))
+ // confusingly, Windows Kits\10 needs to go down this path?!?
+ // except now it doesn't, I don't know what changed
+ if (has_slash)
+ swprintf(full_path, L"%s*", stb__from_utf8(path));
+ else
+ swprintf(full_path, L"%s/*", stb__from_utf8(path));
+#else
+ if (has_slash)
+ swprintf((wchar_t *) full_path, (size_t) 1024, L"%s*", (wchar_t *) stb__from_utf8(path));
+ else
+ swprintf((wchar_t *) full_path, (size_t) 1024, L"%s/*", (wchar_t *) stb__from_utf8(path));
+#endif
+
+ // it's possible this directory is already present: that means it was in the
+ // cache, but its parent wasn't... in that case, we're done with it
+ if (stb__showfile) printf("C[%d]", stb_arr_len(active->dirs));
+ for (n=0; n < stb_arr_len(active->dirs); ++n)
+ if (0 == stb_stricmp(active->dirs[n].path, path)) {
+ if (stb__showfile) printf("D");
+ return;
+ }
+ if (stb__showfile) printf("E");
+
+ // otherwise, we need to add it
+ stb__dirtree_add_dir(path, last_time, active);
+ n = stb_arr_lastn(active->dirs);
+
+ if (stb__showfile) printf("[");
+ if( (hFile = (long) _wfindfirsti64( (wchar_t *) full_path, &c_file )) != -1L ) {
+ do {
+ if (stb__showfile) printf(")");
+ if (c_file.attrib & _A_SUBDIR) {
+ // ignore subdirectories starting with '.', e.g. "." and ".."
+ if (c_file.name[0] != '.') {
+ char *new_path = (char *) full_path;
+ char *temp = stb__to_utf8((stb__wchar *) c_file.name);
+
+ if (has_slash)
+ stb_p_sprintf(new_path stb_p_size(sizeof(full_path)), "%s%s", path, temp);
+ else
+ stb_p_sprintf(new_path stb_p_size(sizeof(full_path)), "%s/%s", path, temp);
+
+ if (stb__dircount_mask) {
+ ++stb__dircount;
+ if (!(stb__dircount & stb__dircount_mask)) {
+ char dummy_path[128], *pad;
+ stb_strncpy(dummy_path, new_path, sizeof(dummy_path)-1);
+ if (strlen(dummy_path) > 96) {
+ stb_p_strcpy_s(dummy_path+96/2-1,128, "...");
+ stb_p_strcpy_s(dummy_path+96/2+2,128, new_path + strlen(new_path)-96/2+2);
+ }
+ pad = dummy_path + strlen(dummy_path);
+ while (pad < dummy_path+98)
+ *pad++ = ' ';
+ *pad = 0;
+ printf("%s\r", dummy_path);
+ #if 0
+ if (hlog == 0) {
+ hlog = stb_p_fopen("c:/x/temp.log", "w");
+ fprintf(hlog, "%s\n", dummy_path);
+ }
+ #endif
+ }
+ }
+
+ stb__dirtree_scandir(new_path, c_file.time_write, active);
+ }
+ } else {
+ char *temp = stb__to_utf8((stb__wchar *) c_file.name);
+ stb__dirtree_add_file(temp, n, c_file.size, c_file.time_write, active);
+ }
+ if (stb__showfile) printf("(");
+ } while( _wfindnexti64( hFile, &c_file ) == 0 );
+ if (stb__showfile) printf("]");
+ _findclose( hFile );
+ }
+ if (stb__showfile) printf(">\n");
+}
+
+// scan the database and see if it's all valid
+static int stb__dirtree_update_db(stb_dirtree *db, stb_dirtree *active)
+{
+ int changes_detected = STB_FALSE;
+ int i;
+ int *remap;
+ int *rescan=NULL;
+ remap = (int *) malloc(sizeof(remap[0]) * stb_arr_len(db->dirs));
+ memset(remap, 0, sizeof(remap[0]) * stb_arr_len(db->dirs));
+ rescan = NULL;
+
+ for (i=0; i < stb_arr_len(db->dirs); ++i) {
+ struct _stat info;
+ if (stb__dircount_mask) {
+ ++stb__dircount;
+ if (!(stb__dircount & stb__dircount_mask)) {
+ printf(".");
+ }
+ }
+ if (0 == _stat(db->dirs[i].path, &info)) {
+ if (info.st_mode & _S_IFDIR) {
+ // it's still a directory, as expected
+ int n = abs((int) (info.st_mtime - db->dirs[i].last_modified));
+ if (n > 1 && n != 3600) { // the 3600 is a hack because sometimes this jumps for no apparent reason, even when no time zone or DST issues are at play
+ // it's changed! force a rescan
+ // we don't want to scan it until we've stat()d its
+ // subdirs, though, so we queue it
+ if (stb__showfile) printf("Changed: %s - %08x:%08x\n", db->dirs[i].path, (unsigned int) db->dirs[i].last_modified, (unsigned int) info.st_mtime);
+ stb_arr_push(rescan, i);
+ // update the last_mod time
+ db->dirs[i].last_modified = info.st_mtime;
+ // ignore existing files in this dir
+ remap[i] = -1;
+ changes_detected = STB_TRUE;
+ } else {
+ // it hasn't changed, just copy it through unchanged
+ stb__dirtree_add_dir(db->dirs[i].path, db->dirs[i].last_modified, active);
+ remap[i] = stb_arr_lastn(active->dirs);
+ }
+ } else {
+ // this path used to refer to a directory, but now it's a file!
+ // assume that the parent directory is going to be forced to rescan anyway
+ goto delete_entry;
+ }
+ } else {
+ delete_entry:
+ // directory no longer exists, so don't copy it
+ // we don't free it because it's in the string pool now
+ db->dirs[i].path = NULL;
+ remap[i] = -1;
+ changes_detected = STB_TRUE;
+ }
+ }
+
+ // at this point, we have:
+ //
+ // holds a list of directory indices that need to be scanned due to being out of date
+ // holds the directory index in for each dir in , if it exists; -1 if not
+ // directories in are not in yet
+
+ // so we can go ahead and remap all the known files right now
+ for (i=0; i < stb_arr_len(db->files); ++i) {
+ int dir = db->files[i].dir;
+ if (remap[dir] >= 0) {
+ stb__dirtree_add_file(db->files[i].name, remap[dir], db->files[i].size, db->files[i].last_modified, active);
+ }
+ }
+
+ // at this point we're done with db->files, and done with remap
+ free(remap);
+
+ // now scan those directories using the standard scan
+ for (i=0; i < stb_arr_len(rescan); ++i) {
+ int z = rescan[i];
+ stb__dirtree_scandir(db->dirs[z].path, db->dirs[z].last_modified, active);
+ }
+ stb_arr_free(rescan);
+
+ return changes_detected;
+}
+
+static void stb__dirtree_free_raw(stb_dirtree *d)
+{
+ stb_free(d->string_pool);
+ stb_arr_free(d->dirs);
+ stb_arr_free(d->files);
+}
+
+stb_dirtree *stb_dirtree_get_with_file(char *dir, char *cache_file)
+{
+ stb_dirtree *output = (stb_dirtree *) malloc(sizeof(*output));
+ stb_dirtree db,active;
+ int prev_dir_count, cache_mismatch;
+
+ char *stripped_dir; // store the directory name without a trailing '/' or '\\'
+
+ // load the database of last-known state on disk
+ db.string_pool = NULL;
+ db.files = NULL;
+ db.dirs = NULL;
+
+ stripped_dir = stb_strip_final_slash(stb_p_strdup(dir));
+
+ if (cache_file != NULL)
+ stb__dirtree_load_db(cache_file, &db, stripped_dir);
+ else if (stb__showfile)
+ printf("No cache file\n");
+
+ active.files = NULL;
+ active.dirs = NULL;
+ active.string_pool = stb_malloc(0,1); // @TODO: share string pools between both?
+
+ // check all the directories in the database; make note if
+ // anything we scanned had changed, and rescan those things
+ cache_mismatch = stb__dirtree_update_db(&db, &active);
+
+ // check the root tree
+ prev_dir_count = stb_arr_len(active.dirs); // record how many directories we've seen
+
+ stb__dirtree_scandir(stripped_dir, 0, &active); // no last_modified time available for root
+
+ if (stb__dircount_mask)
+ printf(" \r");
+
+ // done with the DB; write it back out if any changes, i.e. either
+ // 1. any inconsistency found between cached information and actual disk
+ // or 2. if scanning the root found any new directories--which we detect because
+ // more than one directory got added to the active db during that scan
+ if (cache_mismatch || stb_arr_len(active.dirs) > prev_dir_count+1)
+ stb__dirtree_save_db(cache_file, &active, stripped_dir);
+
+ free(stripped_dir);
+
+ stb__dirtree_free_raw(&db);
+
+ *output = active;
+ return output;
+}
+
+stb_dirtree *stb_dirtree_get_dir(char *dir, char *cache_dir)
+{
+ int i;
+ stb_uint8 sha[20];
+ char dir_lower[1024];
+ char cache_file[1024],*s;
+ if (cache_dir == NULL)
+ return stb_dirtree_get_with_file(dir, NULL);
+ stb_p_strcpy_s(dir_lower, sizeof(dir_lower), dir);
+ stb_tolower(dir_lower);
+ stb_sha1(sha, (unsigned char *) dir_lower, (unsigned int) strlen(dir_lower));
+ stb_p_strcpy_s(cache_file, sizeof(cache_file), cache_dir);
+ s = cache_file + strlen(cache_file);
+ if (s[-1] != '/' && s[-1] != '\\') *s++ = '/';
+ stb_p_strcpy_s(s, sizeof(cache_file), "dirtree_");
+ s += strlen(s);
+ for (i=0; i < 8; ++i) {
+ char *hex = (char*)"0123456789abcdef";
+ stb_uint z = sha[i];
+ *s++ = hex[z >> 4];
+ *s++ = hex[z & 15];
+ }
+ stb_p_strcpy_s(s, sizeof(cache_file), ".bin");
+ return stb_dirtree_get_with_file(dir, cache_file);
+}
+
+stb_dirtree *stb_dirtree_get(char *dir)
+{
+ char cache_dir[256];
+ stb_p_strcpy_s(cache_dir, sizeof(cache_dir), "c:/bindata");
+ #ifdef STB_HAS_REGISTRY
+ {
+ void *reg = stb_reg_open("rHKLM", "Software\\SilverSpaceship\\stb");
+ if (reg) {
+ stb_reg_read(reg, "dirtree", cache_dir, sizeof(cache_dir));
+ stb_reg_close(reg);
+ }
+ }
+ #endif
+ return stb_dirtree_get_dir(dir, cache_dir);
+}
+
+void stb_dirtree_free(stb_dirtree *d)
+{
+ stb__dirtree_free_raw(d);
+ free(d);
+}
+
+void stb_dirtree_db_add_dir(stb_dirtree *active, char *path, time_t last)
+{
+ stb__dirtree_add_dir(path, last, active);
+}
+
+void stb_dirtree_db_add_file(stb_dirtree *active, char *name, int dir, stb_int64 size, time_t last)
+{
+ stb__dirtree_add_file(name, dir, size, last, active);
+}
+
+void stb_dirtree_db_read(stb_dirtree *target, char *filename, char *dir)
+{
+ char *s = stb_strip_final_slash(stb_p_strdup(dir));
+ target->dirs = 0;
+ target->files = 0;
+ target->string_pool = 0;
+ stb__dirtree_load_db(filename, target, s);
+ free(s);
+}
+
+void stb_dirtree_db_write(stb_dirtree *target, char *filename, char *dir)
+{
+ stb__dirtree_save_db(filename, target, 0); // don't strip out any directories
+}
+
+#endif // STB_DEFINE
+
+#endif // _WIN32
+#endif // STB_NO_STB_STRINGS
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// STB_MALLOC_WRAPPER
+//
+// you can use the wrapper functions with your own malloc wrapper,
+// or define STB_MALLOC_WRAPPER project-wide to have
+// malloc/free/realloc/strdup all get vectored to it
+
+// this has too many very specific error messages you could google for and find in stb.h,
+// so don't use it if they don't want any stb.h-identifiable strings
+#if defined(STB_DEFINE) && !defined(STB_NO_STB_STRINGS)
+
+typedef struct
+{
+ void *p;
+ char *file;
+ int line;
+ size_t size;
+} stb_malloc_record;
+
+#ifndef STB_MALLOC_HISTORY_COUNT
+#define STB_MALLOC_HISTORY_COUNT 50 // 800 bytes
+#endif
+
+stb_malloc_record *stb__allocations;
+static int stb__alloc_size, stb__alloc_limit, stb__alloc_mask;
+int stb__alloc_count;
+
+stb_malloc_record stb__alloc_history[STB_MALLOC_HISTORY_COUNT];
+int stb__history_pos;
+
+static int stb__hashfind(void *p)
+{
+ stb_uint32 h = stb_hashptr(p);
+ int s,n = h & stb__alloc_mask;
+ if (stb__allocations[n].p == p)
+ return n;
+ s = stb_rehash(h)|1;
+ for(;;) {
+ if (stb__allocations[n].p == NULL)
+ return -1;
+ n = (n+s) & stb__alloc_mask;
+ if (stb__allocations[n].p == p)
+ return n;
+ }
+}
+
+size_t stb_wrapper_allocsize(void *p)
+{
+ int n = stb__hashfind(p);
+ if (n < 0) return 0;
+ return stb__allocations[n].size;
+}
+
+static int stb__historyfind(void *p)
+{
+ int n = stb__history_pos;
+ int i;
+ for (i=0; i < STB_MALLOC_HISTORY_COUNT; ++i) {
+ if (--n < 0) n = STB_MALLOC_HISTORY_COUNT-1;
+ if (stb__alloc_history[n].p == p)
+ return n;
+ }
+ return -1;
+}
+
+static void stb__add_alloc(void *p, size_t sz, char *file, int line);
+static void stb__grow_alloc(void)
+{
+ int i,old_num = stb__alloc_size;
+ stb_malloc_record *old = stb__allocations;
+ if (stb__alloc_size == 0)
+ stb__alloc_size = 64;
+ else
+ stb__alloc_size *= 2;
+
+ stb__allocations = (stb_malloc_record *) stb__realloc_raw(NULL, stb__alloc_size * sizeof(stb__allocations[0]));
+ if (stb__allocations == NULL)
+ stb_fatal("Internal error: couldn't grow malloc wrapper table");
+ memset(stb__allocations, 0, stb__alloc_size * sizeof(stb__allocations[0]));
+ stb__alloc_limit = (stb__alloc_size*3)>>2;
+ stb__alloc_mask = stb__alloc_size-1;
+
+ stb__alloc_count = 0;
+
+ for (i=0; i < old_num; ++i)
+ if (old[i].p > STB_DEL) {
+ stb__add_alloc(old[i].p, old[i].size, old[i].file, old[i].line);
+ assert(stb__hashfind(old[i].p) >= 0);
+ }
+ for (i=0; i < old_num; ++i)
+ if (old[i].p > STB_DEL)
+ assert(stb__hashfind(old[i].p) >= 0);
+ stb__realloc_raw(old, 0);
+}
+
+static void stb__add_alloc(void *p, size_t sz, char *file, int line)
+{
+ stb_uint32 h;
+ int n;
+ if (stb__alloc_count >= stb__alloc_limit)
+ stb__grow_alloc();
+ h = stb_hashptr(p);
+ n = h & stb__alloc_mask;
+ if (stb__allocations[n].p > STB_DEL) {
+ int s = stb_rehash(h)|1;
+ do {
+ n = (n+s) & stb__alloc_mask;
+ } while (stb__allocations[n].p > STB_DEL);
+ }
+ assert(stb__allocations[n].p == NULL || stb__allocations[n].p == STB_DEL);
+ stb__allocations[n].p = p;
+ stb__allocations[n].size = sz;
+ stb__allocations[n].line = line;
+ stb__allocations[n].file = file;
+ ++stb__alloc_count;
+}
+
+static void stb__remove_alloc(int n, char *file, int line)
+{
+ stb__alloc_history[stb__history_pos] = stb__allocations[n];
+ stb__alloc_history[stb__history_pos].file = file;
+ stb__alloc_history[stb__history_pos].line = line;
+ if (++stb__history_pos == STB_MALLOC_HISTORY_COUNT)
+ stb__history_pos = 0;
+ stb__allocations[n].p = STB_DEL;
+ --stb__alloc_count;
+}
+
+void stb_wrapper_malloc(void *p, size_t sz, char *file, int line)
+{
+ if (!p) return;
+ stb__add_alloc(p,sz,file,line);
+}
+
+void stb_wrapper_free(void *p, char *file, int line)
+{
+ int n;
+
+ if (p == NULL) return;
+
+ n = stb__hashfind(p);
+
+ if (n >= 0)
+ stb__remove_alloc(n, file, line);
+ else {
+ // tried to free something we hadn't allocated!
+ n = stb__historyfind(p);
+ assert(0); /* NOTREACHED */
+ if (n >= 0)
+ stb_fatal("Attempted to free %d-byte block %p at %s:%d previously freed/realloced at %s:%d",
+ stb__alloc_history[n].size, p,
+ file, line,
+ stb__alloc_history[n].file, stb__alloc_history[n].line);
+ else
+ stb_fatal("Attempted to free unknown block %p at %s:%d", p, file,line);
+ }
+}
+
+void stb_wrapper_check(void *p)
+{
+ int n;
+
+ if (p == NULL) return;
+
+ n = stb__hashfind(p);
+
+ if (n >= 0) return;
+
+ for (n=0; n < stb__alloc_size; ++n)
+ if (stb__allocations[n].p == p)
+ stb_fatal("Internal error: pointer %p was allocated, but hash search failed", p);
+
+ // tried to free something that wasn't allocated!
+ n = stb__historyfind(p);
+ if (n >= 0)
+ stb_fatal("Checked %d-byte block %p previously freed/realloced at %s:%d",
+ stb__alloc_history[n].size, p,
+ stb__alloc_history[n].file, stb__alloc_history[n].line);
+ stb_fatal("Checked unknown block %p");
+}
+
+void stb_wrapper_realloc(void *p, void *q, size_t sz, char *file, int line)
+{
+ int n;
+ if (p == NULL) { stb_wrapper_malloc(q, sz, file, line); return; }
+ if (q == NULL) return; // nothing happened
+
+ n = stb__hashfind(p);
+ if (n == -1) {
+ // tried to free something we hadn't allocated!
+ // this is weird, though, because we got past the realloc!
+ n = stb__historyfind(p);
+ assert(0); /* NOTREACHED */
+ if (n >= 0)
+ stb_fatal("Attempted to realloc %d-byte block %p at %s:%d previously freed/realloced at %s:%d",
+ stb__alloc_history[n].size, p,
+ file, line,
+ stb__alloc_history[n].file, stb__alloc_history[n].line);
+ else
+ stb_fatal("Attempted to realloc unknown block %p at %s:%d", p, file,line);
+ } else {
+ if (q == p) {
+ stb__allocations[n].size = sz;
+ stb__allocations[n].file = file;
+ stb__allocations[n].line = line;
+ } else {
+ stb__remove_alloc(n, file, line);
+ stb__add_alloc(q,sz,file,line);
+ }
+ }
+}
+
+void stb_wrapper_listall(void (*func)(void *ptr, size_t sz, char *file, int line))
+{
+ int i;
+ for (i=0; i < stb__alloc_size; ++i)
+ if (stb__allocations[i].p > STB_DEL)
+ func(stb__allocations[i].p , stb__allocations[i].size,
+ stb__allocations[i].file, stb__allocations[i].line);
+}
+
+void stb_wrapper_dump(char *filename)
+{
+ int i;
+ FILE *f = stb_p_fopen(filename, "w");
+ if (!f) return;
+ for (i=0; i < stb__alloc_size; ++i)
+ if (stb__allocations[i].p > STB_DEL)
+ fprintf(f, "%p %7d - %4d %s\n",
+ stb__allocations[i].p , (int) stb__allocations[i].size,
+ stb__allocations[i].line, stb__allocations[i].file);
+}
+#endif // STB_DEFINE
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_pointer_set
+//
+//
+// For data structures that support querying by key, data structure
+// classes always hand-wave away the issue of what to do if two entries
+// have the same key: basically, store a linked list of all the nodes
+// which have the same key (a LISP-style list).
+//
+// The thing is, it's not that trivial. If you have an O(log n)
+// lookup data structure, but then n/4 items have the same value,
+// you don't want to spend O(n) time scanning that list when
+// deleting an item if you already have a pointer to the item.
+// (You have to spend O(n) time enumerating all the items with
+// a given key, sure, and you can't accelerate deleting a particular
+// item if you only have the key, not a pointer to the item.)
+//
+// I'm going to call this data structure, whatever it turns out to
+// be, a "pointer set", because we don't store any associated data for
+// items in this data structure, we just answer the question of
+// whether an item is in it or not (it's effectively one bit per pointer).
+// Technically they don't have to be pointers; you could cast ints
+// to (void *) if you want, but you can't store 0 or 1 because of the
+// hash table.
+//
+// Since the fastest data structure we might want to add support for
+// identical-keys to is a hash table with O(1)-ish lookup time,
+// that means that the conceptual "linked list of all items with
+// the same indexed value" that we build needs to have the same
+// performance; that way when we index a table we think is arbitrary
+// ints, but in fact half of them are 0, we don't get screwed.
+//
+// Therefore, it needs to be a hash table, at least when it gets
+// large. On the other hand, when the data has totally arbitrary ints
+// or floats, there won't be many collisions, and we'll have tons of
+// 1-item bitmaps. That will be grossly inefficient as hash tables;
+// trade-off; the hash table is reasonably efficient per-item when
+// it's large, but not when it's small. So we need to do something
+// Judy-like and use different strategies depending on the size.
+//
+// Like Judy, we'll use the bottom bit to encode the strategy:
+//
+// bottom bits:
+// 00 - direct pointer
+// 01 - 4-item bucket (16 bytes, no length, NULLs)
+// 10 - N-item array
+// 11 - hash table
+
+typedef struct stb_ps stb_ps;
+
+STB_EXTERN int stb_ps_find (stb_ps *ps, void *value);
+STB_EXTERN stb_ps * stb_ps_add (stb_ps *ps, void *value);
+STB_EXTERN stb_ps * stb_ps_remove(stb_ps *ps, void *value);
+STB_EXTERN stb_ps * stb_ps_remove_any(stb_ps *ps, void **value);
+STB_EXTERN void stb_ps_delete(stb_ps *ps);
+STB_EXTERN int stb_ps_count (stb_ps *ps);
+
+STB_EXTERN stb_ps * stb_ps_copy (stb_ps *ps);
+STB_EXTERN int stb_ps_subset(stb_ps *bigger, stb_ps *smaller);
+STB_EXTERN int stb_ps_eq (stb_ps *p0, stb_ps *p1);
+
+STB_EXTERN void ** stb_ps_getlist (stb_ps *ps, int *count);
+STB_EXTERN int stb_ps_writelist(stb_ps *ps, void **list, int size );
+
+// enum and fastlist don't allocate storage, but you must consume the
+// list before there's any chance the data structure gets screwed up;
+STB_EXTERN int stb_ps_enum (stb_ps *ps, void *data,
+ int (*func)(void *value, void*data) );
+STB_EXTERN void ** stb_ps_fastlist(stb_ps *ps, int *count);
+// result:
+// returns a list, *count is the length of that list,
+// but some entries of the list may be invalid;
+// test with 'stb_ps_fastlist_valid(x)'
+
+#define stb_ps_fastlist_valid(x) ((stb_uinta) (x) > 1)
+
+#ifdef STB_DEFINE
+
+enum
+{
+ STB_ps_direct = 0,
+ STB_ps_bucket = 1,
+ STB_ps_array = 2,
+ STB_ps_hash = 3,
+};
+
+#define STB_BUCKET_SIZE 4
+
+typedef struct
+{
+ void *p[STB_BUCKET_SIZE];
+} stb_ps_bucket;
+#define GetBucket(p) ((stb_ps_bucket *) ((char *) (p) - STB_ps_bucket))
+#define EncodeBucket(p) ((stb_ps *) ((char *) (p) + STB_ps_bucket))
+
+static void stb_bucket_free(stb_ps_bucket *b)
+{
+ free(b);
+}
+
+static stb_ps_bucket *stb_bucket_create2(void *v0, void *v1)
+{
+ stb_ps_bucket *b = (stb_ps_bucket*) malloc(sizeof(*b));
+ b->p[0] = v0;
+ b->p[1] = v1;
+ b->p[2] = NULL;
+ b->p[3] = NULL;
+ return b;
+}
+
+static stb_ps_bucket * stb_bucket_create3(void **v)
+{
+ stb_ps_bucket *b = (stb_ps_bucket*) malloc(sizeof(*b));
+ b->p[0] = v[0];
+ b->p[1] = v[1];
+ b->p[2] = v[2];
+ b->p[3] = NULL;
+ return b;
+}
+
+
+// could use stb_arr, but this will save us memory
+typedef struct
+{
+ int count;
+ void *p[1];
+} stb_ps_array;
+#define GetArray(p) ((stb_ps_array *) ((char *) (p) - STB_ps_array))
+#define EncodeArray(p) ((stb_ps *) ((char *) (p) + STB_ps_array))
+
+static int stb_ps_array_max = 13;
+
+typedef struct
+{
+ int size, mask;
+ int count, count_deletes;
+ int grow_threshhold;
+ int shrink_threshhold;
+ int rehash_threshhold;
+ int any_offset;
+ void *table[1];
+} stb_ps_hash;
+#define GetHash(p) ((stb_ps_hash *) ((char *) (p) - STB_ps_hash))
+#define EncodeHash(p) ((stb_ps *) ((char *) (p) + STB_ps_hash))
+
+#define stb_ps_empty(v) (((stb_uint32) v) <= 1)
+
+static stb_ps_hash *stb_ps_makehash(int size, int old_size, void **old_data)
+{
+ int i;
+ stb_ps_hash *h = (stb_ps_hash *) malloc(sizeof(*h) + (size-1) * sizeof(h->table[0]));
+ assert(stb_is_pow2(size));
+ h->size = size;
+ h->mask = size-1;
+ h->shrink_threshhold = (int) (0.3f * size);
+ h-> grow_threshhold = (int) (0.8f * size);
+ h->rehash_threshhold = (int) (0.9f * size);
+ h->count = 0;
+ h->count_deletes = 0;
+ h->any_offset = 0;
+ memset(h->table, 0, size * sizeof(h->table[0]));
+ for (i=0; i < old_size; ++i)
+ if (!stb_ps_empty((size_t)old_data[i]))
+ stb_ps_add(EncodeHash(h), old_data[i]);
+ return h;
+}
+
+void stb_ps_delete(stb_ps *ps)
+{
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct: break;
+ case STB_ps_bucket: stb_bucket_free(GetBucket(ps)); break;
+ case STB_ps_array : free(GetArray(ps)); break;
+ case STB_ps_hash : free(GetHash(ps)); break;
+ }
+}
+
+stb_ps *stb_ps_copy(stb_ps *ps)
+{
+ int i;
+ // not a switch: order based on expected performance/power-law distribution
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct: return ps;
+ case STB_ps_bucket: {
+ stb_ps_bucket *n = (stb_ps_bucket *) malloc(sizeof(*n));
+ *n = *GetBucket(ps);
+ return EncodeBucket(n);
+ }
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ stb_ps_array *n = (stb_ps_array *) malloc(sizeof(*n) + stb_ps_array_max * sizeof(n->p[0]));
+ n->count = a->count;
+ for (i=0; i < a->count; ++i)
+ n->p[i] = a->p[i];
+ return EncodeArray(n);
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ stb_ps_hash *n = stb_ps_makehash(h->size, h->size, h->table);
+ return EncodeHash(n);
+ }
+ }
+ assert(0); /* NOTREACHED */
+ return NULL;
+}
+
+int stb_ps_find(stb_ps *ps, void *value)
+{
+ int i, code = 3 & (int)(size_t) ps;
+ assert((3 & (int)(size_t) value) == STB_ps_direct);
+ assert(stb_ps_fastlist_valid(value));
+ // not a switch: order based on expected performance/power-law distribution
+ if (code == STB_ps_direct)
+ return value == ps;
+ if (code == STB_ps_bucket) {
+ stb_ps_bucket *b = GetBucket(ps);
+ assert(STB_BUCKET_SIZE == 4);
+ if (b->p[0] == value || b->p[1] == value ||
+ b->p[2] == value || b->p[3] == value)
+ return STB_TRUE;
+ return STB_FALSE;
+ }
+ if (code == STB_ps_array) {
+ stb_ps_array *a = GetArray(ps);
+ for (i=0; i < a->count; ++i)
+ if (a->p[i] == value)
+ return STB_TRUE;
+ return STB_FALSE;
+ } else {
+ stb_ps_hash *h = GetHash(ps);
+ stb_uint32 hash = stb_hashptr(value);
+ stb_uint32 s, n = hash & h->mask;
+ void **t = h->table;
+ if (t[n] == value) return STB_TRUE;
+ if (t[n] == NULL) return STB_FALSE;
+ s = stb_rehash(hash) | 1;
+ do {
+ n = (n + s) & h->mask;
+ if (t[n] == value) return STB_TRUE;
+ } while (t[n] != NULL);
+ return STB_FALSE;
+ }
+}
+
+stb_ps * stb_ps_add (stb_ps *ps, void *value)
+{
+ #ifdef STB_DEBUG
+ assert(!stb_ps_find(ps,value));
+ #endif
+ if (value == NULL) return ps; // ignore NULL adds to avoid bad breakage
+ assert((3 & (int)(size_t) value) == STB_ps_direct);
+ assert(stb_ps_fastlist_valid(value));
+ assert(value != STB_DEL); // STB_DEL is less likely
+
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct:
+ if (ps == NULL) return (stb_ps *) value;
+ return EncodeBucket(stb_bucket_create2(ps,value));
+
+ case STB_ps_bucket: {
+ stb_ps_bucket *b = GetBucket(ps);
+ stb_ps_array *a;
+ assert(STB_BUCKET_SIZE == 4);
+ if (b->p[0] == NULL) { b->p[0] = value; return ps; }
+ if (b->p[1] == NULL) { b->p[1] = value; return ps; }
+ if (b->p[2] == NULL) { b->p[2] = value; return ps; }
+ if (b->p[3] == NULL) { b->p[3] = value; return ps; }
+ a = (stb_ps_array *) malloc(sizeof(*a) + 7 * sizeof(a->p[0])); // 8 slots, must be 2^k
+ memcpy(a->p, b, sizeof(*b));
+ a->p[4] = value;
+ a->count = 5;
+ stb_bucket_free(b);
+ return EncodeArray(a);
+ }
+
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ if (a->count == stb_ps_array_max) {
+ // promote from array to hash
+ stb_ps_hash *h = stb_ps_makehash(2 << stb_log2_ceil(a->count), a->count, a->p);
+ free(a);
+ return stb_ps_add(EncodeHash(h), value);
+ }
+ // do we need to resize the array? the array doubles in size when it
+ // crosses a power-of-two
+ if ((a->count & (a->count-1))==0) {
+ int newsize = a->count*2;
+ // clamp newsize to max if:
+ // 1. it's larger than max
+ // 2. newsize*1.5 is larger than max (to avoid extra resizing)
+ if (newsize + a->count > stb_ps_array_max)
+ newsize = stb_ps_array_max;
+ a = (stb_ps_array *) realloc(a, sizeof(*a) + (newsize-1) * sizeof(a->p[0]));
+ }
+ a->p[a->count++] = value;
+ return EncodeArray(a);
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ stb_uint32 hash = stb_hashptr(value);
+ stb_uint32 n = hash & h->mask;
+ void **t = h->table;
+ // find first NULL or STB_DEL entry
+ if (!stb_ps_empty((size_t)t[n])) {
+ stb_uint32 s = stb_rehash(hash) | 1;
+ do {
+ n = (n + s) & h->mask;
+ } while (!stb_ps_empty((size_t)t[n]));
+ }
+ if (t[n] == STB_DEL)
+ -- h->count_deletes;
+ t[n] = value;
+ ++ h->count;
+ if (h->count == h->grow_threshhold) {
+ stb_ps_hash *h2 = stb_ps_makehash(h->size*2, h->size, t);
+ free(h);
+ return EncodeHash(h2);
+ }
+ if (h->count + h->count_deletes == h->rehash_threshhold) {
+ stb_ps_hash *h2 = stb_ps_makehash(h->size, h->size, t);
+ free(h);
+ return EncodeHash(h2);
+ }
+ return ps;
+ }
+ }
+ return NULL; /* NOTREACHED */
+}
+
+stb_ps *stb_ps_remove(stb_ps *ps, void *value)
+{
+ #ifdef STB_DEBUG
+ assert(stb_ps_find(ps, value));
+ #endif
+ assert((3 & (int)(size_t) value) == STB_ps_direct);
+ if (value == NULL) return ps; // ignore NULL removes to avoid bad breakage
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct:
+ return ps == value ? NULL : ps;
+ case STB_ps_bucket: {
+ stb_ps_bucket *b = GetBucket(ps);
+ int count=0;
+ assert(STB_BUCKET_SIZE == 4);
+ if (b->p[0] == value) b->p[0] = NULL; else count += (b->p[0] != NULL);
+ if (b->p[1] == value) b->p[1] = NULL; else count += (b->p[1] != NULL);
+ if (b->p[2] == value) b->p[2] = NULL; else count += (b->p[2] != NULL);
+ if (b->p[3] == value) b->p[3] = NULL; else count += (b->p[3] != NULL);
+ if (count == 1) { // shrink bucket at size 1
+ value = b->p[0];
+ if (value == NULL) value = b->p[1];
+ if (value == NULL) value = b->p[2];
+ if (value == NULL) value = b->p[3];
+ assert(value != NULL);
+ stb_bucket_free(b);
+ return (stb_ps *) value; // return STB_ps_direct of value
+ }
+ return ps;
+ }
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ int i;
+ for (i=0; i < a->count; ++i) {
+ if (a->p[i] == value) {
+ a->p[i] = a->p[--a->count];
+ if (a->count == 3) { // shrink to bucket!
+ stb_ps_bucket *b = stb_bucket_create3(a->p);
+ free(a);
+ return EncodeBucket(b);
+ }
+ return ps;
+ }
+ }
+ return ps;
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ stb_uint32 hash = stb_hashptr(value);
+ stb_uint32 s, n = hash & h->mask;
+ void **t = h->table;
+ if (t[n] != value) {
+ s = stb_rehash(hash) | 1;
+ do {
+ n = (n + s) & h->mask;
+ } while (t[n] != value);
+ }
+ t[n] = STB_DEL;
+ -- h->count;
+ ++ h->count_deletes;
+ // should we shrink down to an array?
+ if (h->count < stb_ps_array_max) {
+ int n = 1 << stb_log2_floor(stb_ps_array_max);
+ if (h->count < n) {
+ stb_ps_array *a = (stb_ps_array *) malloc(sizeof(*a) + (n-1) * sizeof(a->p[0]));
+ int i,j=0;
+ for (i=0; i < h->size; ++i)
+ if (!stb_ps_empty((size_t)t[i]))
+ a->p[j++] = t[i];
+ assert(j == h->count);
+ a->count = j;
+ free(h);
+ return EncodeArray(a);
+ }
+ }
+ if (h->count == h->shrink_threshhold) {
+ stb_ps_hash *h2 = stb_ps_makehash(h->size >> 1, h->size, t);
+ free(h);
+ return EncodeHash(h2);
+ }
+ return ps;
+ }
+ }
+ return ps; /* NOTREACHED */
+}
+
+stb_ps *stb_ps_remove_any(stb_ps *ps, void **value)
+{
+ assert(ps != NULL);
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct:
+ *value = ps;
+ return NULL;
+ case STB_ps_bucket: {
+ stb_ps_bucket *b = GetBucket(ps);
+ int count=0, slast=0, last=0;
+ assert(STB_BUCKET_SIZE == 4);
+ if (b->p[0]) { ++count; last = 0; }
+ if (b->p[1]) { ++count; slast = last; last = 1; }
+ if (b->p[2]) { ++count; slast = last; last = 2; }
+ if (b->p[3]) { ++count; slast = last; last = 3; }
+ *value = b->p[last];
+ b->p[last] = 0;
+ if (count == 2) {
+ void *leftover = b->p[slast]; // second to last
+ stb_bucket_free(b);
+ return (stb_ps *) leftover;
+ }
+ return ps;
+ }
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ *value = a->p[a->count-1];
+ if (a->count == 4)
+ return stb_ps_remove(ps, *value);
+ --a->count;
+ return ps;
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ void **t = h->table;
+ stb_uint32 n = h->any_offset;
+ while (stb_ps_empty((size_t)t[n]))
+ n = (n + 1) & h->mask;
+ *value = t[n];
+ h->any_offset = (n+1) & h->mask;
+ // check if we need to skip down to the previous type
+ if (h->count-1 < stb_ps_array_max || h->count-1 == h->shrink_threshhold)
+ return stb_ps_remove(ps, *value);
+ t[n] = STB_DEL;
+ -- h->count;
+ ++ h->count_deletes;
+ return ps;
+ }
+ }
+ return ps; /* NOTREACHED */
+}
+
+
+void ** stb_ps_getlist(stb_ps *ps, int *count)
+{
+ int i,n=0;
+ void **p = NULL;
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct:
+ if (ps == NULL) { *count = 0; return NULL; }
+ p = (void **) malloc(sizeof(*p) * 1);
+ p[0] = ps;
+ *count = 1;
+ return p;
+ case STB_ps_bucket: {
+ stb_ps_bucket *b = GetBucket(ps);
+ p = (void **) malloc(sizeof(*p) * STB_BUCKET_SIZE);
+ for (i=0; i < STB_BUCKET_SIZE; ++i)
+ if (b->p[i] != NULL)
+ p[n++] = b->p[i];
+ break;
+ }
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ p = (void **) malloc(sizeof(*p) * a->count);
+ memcpy(p, a->p, sizeof(*p) * a->count);
+ *count = a->count;
+ return p;
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ p = (void **) malloc(sizeof(*p) * h->count);
+ for (i=0; i < h->size; ++i)
+ if (!stb_ps_empty((size_t)h->table[i]))
+ p[n++] = h->table[i];
+ break;
+ }
+ }
+ *count = n;
+ return p;
+}
+
+int stb_ps_writelist(stb_ps *ps, void **list, int size )
+{
+ int i,n=0;
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct:
+ if (ps == NULL || size <= 0) return 0;
+ list[0] = ps;
+ return 1;
+ case STB_ps_bucket: {
+ stb_ps_bucket *b = GetBucket(ps);
+ for (i=0; i < STB_BUCKET_SIZE; ++i)
+ if (b->p[i] != NULL && n < size)
+ list[n++] = b->p[i];
+ return n;
+ }
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ n = stb_min(size, a->count);
+ memcpy(list, a->p, sizeof(*list) * n);
+ return n;
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ if (size <= 0) return 0;
+ for (i=0; i < h->count; ++i) {
+ if (!stb_ps_empty((size_t)h->table[i])) {
+ list[n++] = h->table[i];
+ if (n == size) break;
+ }
+ }
+ return n;
+ }
+ }
+ return 0; /* NOTREACHED */
+}
+
+int stb_ps_enum(stb_ps *ps, void *data, int (*func)(void *value, void *data))
+{
+ int i;
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct:
+ if (ps == NULL) return STB_TRUE;
+ return func(ps, data);
+ case STB_ps_bucket: {
+ stb_ps_bucket *b = GetBucket(ps);
+ for (i=0; i < STB_BUCKET_SIZE; ++i)
+ if (b->p[i] != NULL)
+ if (!func(b->p[i], data))
+ return STB_FALSE;
+ return STB_TRUE;
+ }
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ for (i=0; i < a->count; ++i)
+ if (!func(a->p[i], data))
+ return STB_FALSE;
+ return STB_TRUE;
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ for (i=0; i < h->count; ++i)
+ if (!stb_ps_empty((size_t)h->table[i]))
+ if (!func(h->table[i], data))
+ return STB_FALSE;
+ return STB_TRUE;
+ }
+ }
+ return STB_TRUE; /* NOTREACHED */
+}
+
+int stb_ps_count (stb_ps *ps)
+{
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct:
+ return ps != NULL;
+ case STB_ps_bucket: {
+ stb_ps_bucket *b = GetBucket(ps);
+ return (b->p[0] != NULL) + (b->p[1] != NULL) +
+ (b->p[2] != NULL) + (b->p[3] != NULL);
+ }
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ return a->count;
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ return h->count;
+ }
+ }
+ return 0;
+}
+
+void ** stb_ps_fastlist(stb_ps *ps, int *count)
+{
+ static void *storage;
+
+ switch (3 & (int)(size_t) ps) {
+ case STB_ps_direct:
+ if (ps == NULL) { *count = 0; return NULL; }
+ storage = ps;
+ *count = 1;
+ return &storage;
+ case STB_ps_bucket: {
+ stb_ps_bucket *b = GetBucket(ps);
+ *count = STB_BUCKET_SIZE;
+ return b->p;
+ }
+ case STB_ps_array: {
+ stb_ps_array *a = GetArray(ps);
+ *count = a->count;
+ return a->p;
+ }
+ case STB_ps_hash: {
+ stb_ps_hash *h = GetHash(ps);
+ *count = h->size;
+ return h->table;
+ }
+ }
+ return NULL; /* NOTREACHED */
+}
+
+int stb_ps_subset(stb_ps *bigger, stb_ps *smaller)
+{
+ int i, listlen;
+ void **list = stb_ps_fastlist(smaller, &listlen);
+ for(i=0; i < listlen; ++i)
+ if (stb_ps_fastlist_valid(list[i]))
+ if (!stb_ps_find(bigger, list[i]))
+ return 0;
+ return 1;
+}
+
+int stb_ps_eq(stb_ps *p0, stb_ps *p1)
+{
+ if (stb_ps_count(p0) != stb_ps_count(p1))
+ return 0;
+ return stb_ps_subset(p0, p1);
+}
+
+#undef GetBucket
+#undef GetArray
+#undef GetHash
+
+#undef EncodeBucket
+#undef EncodeArray
+#undef EncodeHash
+
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Random Numbers via Meresenne Twister or LCG
+//
+
+STB_EXTERN unsigned int stb_srandLCG(unsigned int seed);
+STB_EXTERN unsigned int stb_randLCG(void);
+STB_EXTERN double stb_frandLCG(void);
+
+STB_EXTERN void stb_srand(unsigned int seed);
+STB_EXTERN unsigned int stb_rand(void);
+STB_EXTERN double stb_frand(void);
+STB_EXTERN void stb_shuffle(void *p, size_t n, size_t sz,
+ unsigned int seed);
+STB_EXTERN void stb_reverse(void *p, size_t n, size_t sz);
+
+STB_EXTERN unsigned int stb_randLCG_explicit(unsigned int seed);
+
+#define stb_rand_define(x,y) \
+ \
+ unsigned int x(void) \
+ { \
+ static unsigned int stb__rand = y; \
+ stb__rand = stb__rand * 2147001325 + 715136305; /* BCPL */ \
+ return 0x31415926 ^ ((stb__rand >> 16) + (stb__rand << 16)); \
+ }
+
+#ifdef STB_DEFINE
+unsigned int stb_randLCG_explicit(unsigned int seed)
+{
+ return seed * 2147001325 + 715136305;
+}
+
+static unsigned int stb__rand_seed=0;
+
+unsigned int stb_srandLCG(unsigned int seed)
+{
+ unsigned int previous = stb__rand_seed;
+ stb__rand_seed = seed;
+ return previous;
+}
+
+unsigned int stb_randLCG(void)
+{
+ stb__rand_seed = stb__rand_seed * 2147001325 + 715136305; // BCPL generator
+ // shuffle non-random bits to the middle, and xor to decorrelate with seed
+ return 0x31415926 ^ ((stb__rand_seed >> 16) + (stb__rand_seed << 16));
+}
+
+double stb_frandLCG(void)
+{
+ return stb_randLCG() / ((double) (1 << 16) * (1 << 16));
+}
+
+void stb_shuffle(void *p, size_t n, size_t sz, unsigned int seed)
+{
+ char *a;
+ unsigned int old_seed;
+ int i;
+ if (seed)
+ old_seed = stb_srandLCG(seed);
+ a = (char *) p + (n-1) * sz;
+
+ for (i=(int) n; i > 1; --i) {
+ int j = stb_randLCG() % i;
+ stb_swap(a, (char *) p + j * sz, sz);
+ a -= sz;
+ }
+ if (seed)
+ stb_srandLCG(old_seed);
+}
+
+void stb_reverse(void *p, size_t n, size_t sz)
+{
+ size_t i,j = n-1;
+ for (i=0; i < j; ++i,--j) {
+ stb_swap((char *) p + i * sz, (char *) p + j * sz, sz);
+ }
+}
+
+// public domain Mersenne Twister by Michael Brundage
+#define STB__MT_LEN 624
+
+int stb__mt_index = STB__MT_LEN*sizeof(int)+1;
+unsigned int stb__mt_buffer[STB__MT_LEN];
+
+void stb_srand(unsigned int seed)
+{
+ int i;
+ stb__mt_buffer[0]= seed & 0xffffffffUL;
+ for (i=1 ; i < STB__MT_LEN; ++i)
+ stb__mt_buffer[i] = (1812433253UL * (stb__mt_buffer[i-1] ^ (stb__mt_buffer[i-1] >> 30)) + i);
+ stb__mt_index = STB__MT_LEN*sizeof(unsigned int);
+}
+
+#define STB__MT_IA 397
+#define STB__MT_IB (STB__MT_LEN - STB__MT_IA)
+#define STB__UPPER_MASK 0x80000000
+#define STB__LOWER_MASK 0x7FFFFFFF
+#define STB__MATRIX_A 0x9908B0DF
+#define STB__TWIST(b,i,j) ((b)[i] & STB__UPPER_MASK) | ((b)[j] & STB__LOWER_MASK)
+#define STB__MAGIC(s) (((s)&1)*STB__MATRIX_A)
+
+unsigned int stb_rand()
+{
+ unsigned int * b = stb__mt_buffer;
+ int idx = stb__mt_index;
+ unsigned int s,r;
+ int i;
+
+ if (idx >= STB__MT_LEN*sizeof(unsigned int)) {
+ if (idx > STB__MT_LEN*sizeof(unsigned int))
+ stb_srand(0);
+ idx = 0;
+ i = 0;
+ for (; i < STB__MT_IB; i++) {
+ s = STB__TWIST(b, i, i+1);
+ b[i] = b[i + STB__MT_IA] ^ (s >> 1) ^ STB__MAGIC(s);
+ }
+ for (; i < STB__MT_LEN-1; i++) {
+ s = STB__TWIST(b, i, i+1);
+ b[i] = b[i - STB__MT_IB] ^ (s >> 1) ^ STB__MAGIC(s);
+ }
+
+ s = STB__TWIST(b, STB__MT_LEN-1, 0);
+ b[STB__MT_LEN-1] = b[STB__MT_IA-1] ^ (s >> 1) ^ STB__MAGIC(s);
+ }
+ stb__mt_index = idx + sizeof(unsigned int);
+
+ r = *(unsigned int *)((unsigned char *)b + idx);
+
+ r ^= (r >> 11);
+ r ^= (r << 7) & 0x9D2C5680;
+ r ^= (r << 15) & 0xEFC60000;
+ r ^= (r >> 18);
+
+ return r;
+}
+
+double stb_frand(void)
+{
+ return stb_rand() / ((double) (1 << 16) * (1 << 16));
+}
+
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_dupe
+//
+// stb_dupe is a duplicate-finding system for very, very large data
+// structures--large enough that sorting is too slow, but not so large
+// that we can't keep all the data in memory. using it works as follows:
+//
+// 1. create an stb_dupe:
+// provide a hash function
+// provide an equality function
+// provide an estimate for the size
+// optionally provide a comparison function
+//
+// 2. traverse your data, 'adding' pointers to the stb_dupe
+//
+// 3. finish and ask for duplicates
+//
+// the stb_dupe will discard its intermediate data and build
+// a collection of sorted lists of duplicates, with non-duplicate
+// entries omitted entirely
+//
+//
+// Implementation strategy:
+//
+// while collecting the N items, we keep a hash table of approximate
+// size sqrt(N). (if you tell use the N up front, the hash table is
+// just that size exactly)
+//
+// each entry in the hash table is just an stb__arr of pointers (no need
+// to use stb_ps, because we don't need to delete from these)
+//
+// for step 3, for each entry in the hash table, we apply stb_dupe to it
+// recursively. once the size gets small enough (or doesn't decrease
+// significantly), we switch to either using qsort() on the comparison
+// function, or else we just do the icky N^2 gather
+
+
+typedef struct stb_dupe stb_dupe;
+
+typedef int (*stb_compare_func)(void *a, void *b);
+typedef int (*stb_hash_func)(void *a, unsigned int seed);
+
+STB_EXTERN void stb_dupe_free(stb_dupe *sd);
+STB_EXTERN stb_dupe *stb_dupe_create(stb_hash_func hash,
+ stb_compare_func eq, int size, stb_compare_func ineq);
+STB_EXTERN void stb_dupe_add(stb_dupe *sd, void *item);
+STB_EXTERN void stb_dupe_finish(stb_dupe *sd);
+STB_EXTERN int stb_dupe_numsets(stb_dupe *sd);
+STB_EXTERN void **stb_dupe_set(stb_dupe *sd, int num);
+STB_EXTERN int stb_dupe_set_count(stb_dupe *sd, int num);
+
+struct stb_dupe
+{
+ void ***hash_table;
+ int hash_size;
+ int size_log2;
+ int population;
+
+ int hash_shift;
+ stb_hash_func hash;
+
+ stb_compare_func eq;
+ stb_compare_func ineq;
+
+ void ***dupes;
+};
+
+#ifdef STB_DEFINE
+
+int stb_dupe_numsets(stb_dupe *sd)
+{
+ assert(sd->hash_table == NULL);
+ return stb_arr_len(sd->dupes);
+}
+
+void **stb_dupe_set(stb_dupe *sd, int num)
+{
+ assert(sd->hash_table == NULL);
+ return sd->dupes[num];
+}
+
+int stb_dupe_set_count(stb_dupe *sd, int num)
+{
+ assert(sd->hash_table == NULL);
+ return stb_arr_len(sd->dupes[num]);
+}
+
+stb_dupe *stb_dupe_create(stb_hash_func hash, stb_compare_func eq, int size,
+ stb_compare_func ineq)
+{
+ int i, hsize;
+ stb_dupe *sd = (stb_dupe *) malloc(sizeof(*sd));
+
+ sd->size_log2 = 4;
+ hsize = 1 << sd->size_log2;
+ while (hsize * hsize < size) {
+ ++sd->size_log2;
+ hsize *= 2;
+ }
+
+ sd->hash = hash;
+ sd->eq = eq;
+ sd->ineq = ineq;
+ sd->hash_shift = 0;
+
+ sd->population = 0;
+ sd->hash_size = hsize;
+ sd->hash_table = (void ***) malloc(sizeof(*sd->hash_table) * hsize);
+ for (i=0; i < hsize; ++i)
+ sd->hash_table[i] = NULL;
+
+ sd->dupes = NULL;
+
+ return sd;
+}
+
+void stb_dupe_add(stb_dupe *sd, void *item)
+{
+ stb_uint32 hash = sd->hash(item, sd->hash_shift);
+ int z = hash & (sd->hash_size-1);
+ stb_arr_push(sd->hash_table[z], item);
+ ++sd->population;
+}
+
+void stb_dupe_free(stb_dupe *sd)
+{
+ int i;
+ for (i=0; i < stb_arr_len(sd->dupes); ++i)
+ if (sd->dupes[i])
+ stb_arr_free(sd->dupes[i]);
+ stb_arr_free(sd->dupes);
+ free(sd);
+}
+
+static stb_compare_func stb__compare;
+
+static int stb__dupe_compare(const void *a, const void *b)
+{
+ void *p = *(void **) a;
+ void *q = *(void **) b;
+
+ return stb__compare(p,q);
+}
+
+void stb_dupe_finish(stb_dupe *sd)
+{
+ int i,j,k;
+ assert(sd->dupes == NULL);
+ for (i=0; i < sd->hash_size; ++i) {
+ void ** list = sd->hash_table[i];
+ if (list != NULL) {
+ int n = stb_arr_len(list);
+ // @TODO: measure to find good numbers instead of just making them up!
+ int thresh = (sd->ineq ? 200 : 20);
+ // if n is large enough to be worth it, and n is smaller than
+ // before (so we can guarantee we'll use a smaller hash table);
+ // and there are enough hash bits left, assuming full 32-bit hash
+ if (n > thresh && n < (sd->population >> 3) && sd->hash_shift + sd->size_log2*2 < 32) {
+
+ // recursively process this row using stb_dupe, O(N log log N)
+
+ stb_dupe *d = stb_dupe_create(sd->hash, sd->eq, n, sd->ineq);
+ d->hash_shift = stb_randLCG_explicit(sd->hash_shift);
+ for (j=0; j < n; ++j)
+ stb_dupe_add(d, list[j]);
+ stb_arr_free(sd->hash_table[i]);
+ stb_dupe_finish(d);
+ for (j=0; j < stb_arr_len(d->dupes); ++j) {
+ stb_arr_push(sd->dupes, d->dupes[j]);
+ d->dupes[j] = NULL; // take over ownership
+ }
+ stb_dupe_free(d);
+
+ } else if (sd->ineq) {
+
+ // process this row using qsort(), O(N log N)
+ stb__compare = sd->ineq;
+ qsort(list, n, sizeof(list[0]), stb__dupe_compare);
+
+ // find equal subsequences of the list
+ for (j=0; j < n-1; ) {
+ // find a subsequence from j..k
+ for (k=j; k < n; ++k)
+ // only use ineq so eq can be left undefined
+ if (sd->ineq(list[j], list[k]))
+ break;
+ // k is the first one not in the subsequence
+ if (k-j > 1) {
+ void **mylist = NULL;
+ stb_arr_setlen(mylist, k-j);
+ memcpy(mylist, list+j, sizeof(list[j]) * (k-j));
+ stb_arr_push(sd->dupes, mylist);
+ }
+ j = k;
+ }
+ stb_arr_free(sd->hash_table[i]);
+ } else {
+
+ // process this row using eq(), O(N^2)
+ for (j=0; j < n; ++j) {
+ if (list[j] != NULL) {
+ void **output = NULL;
+ for (k=j+1; k < n; ++k) {
+ if (sd->eq(list[j], list[k])) {
+ if (output == NULL)
+ stb_arr_push(output, list[j]);
+ stb_arr_push(output, list[k]);
+ list[k] = NULL;
+ }
+ }
+ list[j] = NULL;
+ if (output)
+ stb_arr_push(sd->dupes, output);
+ }
+ }
+ stb_arr_free(sd->hash_table[i]);
+ }
+ }
+ }
+ free(sd->hash_table);
+ sd->hash_table = NULL;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// templatized Sort routine
+//
+// This is an attempt to implement a templated sorting algorithm.
+// To use it, you have to explicitly instantiate it as a _function_,
+// then you call that function. This allows the comparison to be inlined,
+// giving the sort similar performance to C++ sorts.
+//
+// It implements quicksort with three-way-median partitioning (generally
+// well-behaved), with a final insertion sort pass.
+//
+// When you define the compare expression, you should assume you have
+// elements of your array pointed to by 'a' and 'b', and perform the comparison
+// on those. OR you can use one or more statements; first say '0;', then
+// write whatever code you want, and compute the result into a variable 'c'.
+
+#define stb_declare_sort(FUNCNAME, TYPE) \
+ void FUNCNAME(TYPE *p, int n)
+#define stb_define_sort(FUNCNAME,TYPE,COMPARE) \
+ stb__define_sort( void, FUNCNAME,TYPE,COMPARE)
+#define stb_define_sort_static(FUNCNAME,TYPE,COMPARE) \
+ stb__define_sort(static void, FUNCNAME,TYPE,COMPARE)
+
+#define stb__define_sort(MODE, FUNCNAME, TYPE, COMPARE) \
+ \
+static void STB_(FUNCNAME,_ins_sort)(TYPE *p, int n) \
+{ \
+ int i,j; \
+ for (i=1; i < n; ++i) { \
+ TYPE t = p[i], *a = &t; \
+ j = i; \
+ while (j > 0) { \
+ TYPE *b = &p[j-1]; \
+ int c = COMPARE; \
+ if (!c) break; \
+ p[j] = p[j-1]; \
+ --j; \
+ } \
+ if (i != j) \
+ p[j] = t; \
+ } \
+} \
+ \
+static void STB_(FUNCNAME,_quicksort)(TYPE *p, int n) \
+{ \
+ /* threshold for transitioning to insertion sort */ \
+ while (n > 12) { \
+ TYPE *a,*b,t; \
+ int c01,c12,c,m,i,j; \
+ \
+ /* compute median of three */ \
+ m = n >> 1; \
+ a = &p[0]; \
+ b = &p[m]; \
+ c = COMPARE; \
+ c01 = c; \
+ a = &p[m]; \
+ b = &p[n-1]; \
+ c = COMPARE; \
+ c12 = c; \
+ /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ \
+ if (c01 != c12) { \
+ /* otherwise, we'll need to swap something else to middle */ \
+ int z; \
+ a = &p[0]; \
+ b = &p[n-1]; \
+ c = COMPARE; \
+ /* 0>mid && midn => n; 0 0 */ \
+ /* 0n: 0>n => 0; 0 n */ \
+ z = (c == c12) ? 0 : n-1; \
+ t = p[z]; \
+ p[z] = p[m]; \
+ p[m] = t; \
+ } \
+ /* now p[m] is the median-of-three */ \
+ /* swap it to the beginning so it won't move around */ \
+ t = p[0]; \
+ p[0] = p[m]; \
+ p[m] = t; \
+ \
+ /* partition loop */ \
+ i=1; \
+ j=n-1; \
+ for(;;) { \
+ /* handling of equality is crucial here */ \
+ /* for sentinels & efficiency with duplicates */ \
+ b = &p[0]; \
+ for (;;++i) { \
+ a=&p[i]; \
+ c = COMPARE; \
+ if (!c) break; \
+ } \
+ a = &p[0]; \
+ for (;;--j) { \
+ b=&p[j]; \
+ c = COMPARE; \
+ if (!c) break; \
+ } \
+ /* make sure we haven't crossed */ \
+ if (i >= j) break; \
+ t = p[i]; \
+ p[i] = p[j]; \
+ p[j] = t; \
+ \
+ ++i; \
+ --j; \
+ } \
+ /* recurse on smaller side, iterate on larger */ \
+ if (j < (n-i)) { \
+ STB_(FUNCNAME,_quicksort)(p,j); \
+ p = p+i; \
+ n = n-i; \
+ } else { \
+ STB_(FUNCNAME,_quicksort)(p+i, n-i); \
+ n = j; \
+ } \
+ } \
+} \
+ \
+MODE FUNCNAME(TYPE *p, int n) \
+{ \
+ STB_(FUNCNAME, _quicksort)(p, n); \
+ STB_(FUNCNAME, _ins_sort)(p, n); \
+} \
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_bitset an array of booleans indexed by integers
+//
+
+typedef stb_uint32 stb_bitset;
+
+STB_EXTERN stb_bitset *stb_bitset_new(int value, int len);
+
+#define stb_bitset_clearall(arr,len) (memset(arr, 0, 4 * (len)))
+#define stb_bitset_setall(arr,len) (memset(arr, 255, 4 * (len)))
+
+#define stb_bitset_setbit(arr,n) ((arr)[(n) >> 5] |= (1 << (n & 31)))
+#define stb_bitset_clearbit(arr,n) ((arr)[(n) >> 5] &= ~(1 << (n & 31)))
+#define stb_bitset_testbit(arr,n) ((arr)[(n) >> 5] & (1 << (n & 31)))
+
+STB_EXTERN stb_bitset *stb_bitset_union(stb_bitset *p0, stb_bitset *p1, int len);
+
+STB_EXTERN int *stb_bitset_getlist(stb_bitset *out, int start, int end);
+
+STB_EXTERN int stb_bitset_eq(stb_bitset *p0, stb_bitset *p1, int len);
+STB_EXTERN int stb_bitset_disjoint(stb_bitset *p0, stb_bitset *p1, int len);
+STB_EXTERN int stb_bitset_disjoint_0(stb_bitset *p0, stb_bitset *p1, int len);
+STB_EXTERN int stb_bitset_subset(stb_bitset *bigger, stb_bitset *smaller, int len);
+STB_EXTERN int stb_bitset_unioneq_changed(stb_bitset *p0, stb_bitset *p1, int len);
+
+#ifdef STB_DEFINE
+int stb_bitset_eq(stb_bitset *p0, stb_bitset *p1, int len)
+{
+ int i;
+ for (i=0; i < len; ++i)
+ if (p0[i] != p1[i]) return 0;
+ return 1;
+}
+
+int stb_bitset_disjoint(stb_bitset *p0, stb_bitset *p1, int len)
+{
+ int i;
+ for (i=0; i < len; ++i)
+ if (p0[i] & p1[i]) return 0;
+ return 1;
+}
+
+int stb_bitset_disjoint_0(stb_bitset *p0, stb_bitset *p1, int len)
+{
+ int i;
+ for (i=0; i < len; ++i)
+ if ((p0[i] | p1[i]) != 0xffffffff) return 0;
+ return 1;
+}
+
+int stb_bitset_subset(stb_bitset *bigger, stb_bitset *smaller, int len)
+{
+ int i;
+ for (i=0; i < len; ++i)
+ if ((bigger[i] & smaller[i]) != smaller[i]) return 0;
+ return 1;
+}
+
+stb_bitset *stb_bitset_union(stb_bitset *p0, stb_bitset *p1, int len)
+{
+ int i;
+ stb_bitset *d = (stb_bitset *) malloc(sizeof(*d) * len);
+ for (i=0; i < len; ++i) d[i] = p0[i] | p1[i];
+ return d;
+}
+
+int stb_bitset_unioneq_changed(stb_bitset *p0, stb_bitset *p1, int len)
+{
+ int i, changed=0;
+ for (i=0; i < len; ++i) {
+ stb_bitset d = p0[i] | p1[i];
+ if (d != p0[i]) {
+ p0[i] = d;
+ changed = 1;
+ }
+ }
+ return changed;
+}
+
+stb_bitset *stb_bitset_new(int value, int len)
+{
+ int i;
+ stb_bitset *d = (stb_bitset *) malloc(sizeof(*d) * len);
+ if (value) value = 0xffffffff;
+ for (i=0; i < len; ++i) d[i] = value;
+ return d;
+}
+
+int *stb_bitset_getlist(stb_bitset *out, int start, int end)
+{
+ int *list = NULL;
+ int i;
+ for (i=start; i < end; ++i)
+ if (stb_bitset_testbit(out, i))
+ stb_arr_push(list, i);
+ return list;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_wordwrap quality word-wrapping for fixed-width fonts
+//
+
+STB_EXTERN int stb_wordwrap(int *pairs, int pair_max, int count, char *str);
+STB_EXTERN int *stb_wordwrapalloc(int count, char *str);
+
+#ifdef STB_DEFINE
+
+int stb_wordwrap(int *pairs, int pair_max, int count, char *str)
+{
+ int n=0,i=0, start=0,nonwhite=0;
+ if (pairs == NULL) pair_max = 0x7ffffff0;
+ else pair_max *= 2;
+ // parse
+ for(;;) {
+ int s=i; // first whitespace char; last nonwhite+1
+ int w; // word start
+ // accept whitespace
+ while (isspace(str[i])) {
+ if (str[i] == '\n' || str[i] == '\r') {
+ if (str[i] + str[i+1] == '\n' + '\r') ++i;
+ if (n >= pair_max) return -1;
+ if (pairs) pairs[n] = start, pairs[n+1] = s-start;
+ n += 2;
+ nonwhite=0;
+ start = i+1;
+ s = start;
+ }
+ ++i;
+ }
+ if (i >= start+count) {
+ // we've gone off the end using whitespace
+ if (nonwhite) {
+ if (n >= pair_max) return -1;
+ if (pairs) pairs[n] = start, pairs[n+1] = s-start;
+ n += 2;
+ start = s = i;
+ nonwhite=0;
+ } else {
+ // output all the whitespace
+ while (i >= start+count) {
+ if (n >= pair_max) return -1;
+ if (pairs) pairs[n] = start, pairs[n+1] = count;
+ n += 2;
+ start += count;
+ }
+ s = start;
+ }
+ }
+
+ if (str[i] == 0) break;
+ // now scan out a word and see if it fits
+ w = i;
+ while (str[i] && !isspace(str[i])) {
+ ++i;
+ }
+ // wrapped?
+ if (i > start + count) {
+ // huge?
+ if (i-s <= count) {
+ if (n >= pair_max) return -1;
+ if (pairs) pairs[n] = start, pairs[n+1] = s-start;
+ n += 2;
+ start = w;
+ } else {
+ // This word is longer than one line. If we wrap it onto N lines
+ // there are leftover chars. do those chars fit on the cur line?
+ // But if we have leading whitespace, we force it to start here.
+ if ((w-start) + ((i-w) % count) <= count || !nonwhite) {
+ // output a full line
+ if (n >= pair_max) return -1;
+ if (pairs) pairs[n] = start, pairs[n+1] = count;
+ n += 2;
+ start += count;
+ w = start;
+ } else {
+ // output a partial line, trimming trailing whitespace
+ if (s != start) {
+ if (n >= pair_max) return -1;
+ if (pairs) pairs[n] = start, pairs[n+1] = s-start;
+ n += 2;
+ start = w;
+ }
+ }
+ // now output full lines as needed
+ while (start + count <= i) {
+ if (n >= pair_max) return -1;
+ if (pairs) pairs[n] = start, pairs[n+1] = count;
+ n += 2;
+ start += count;
+ }
+ }
+ }
+ nonwhite=1;
+ }
+ if (start < i) {
+ if (n >= pair_max) return -1;
+ if (pairs) pairs[n] = start, pairs[n+1] = i-start;
+ n += 2;
+ }
+ return n>>1;
+}
+
+int *stb_wordwrapalloc(int count, char *str)
+{
+ int n = stb_wordwrap(NULL,0,count,str);
+ int *z = NULL;
+ stb_arr_setlen(z, n*2);
+ stb_wordwrap(z, n, count, str);
+ return z;
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// stb_match: wildcards and regexping
+//
+
+STB_EXTERN int stb_wildmatch (char *expr, char *candidate);
+STB_EXTERN int stb_wildmatchi(char *expr, char *candidate);
+STB_EXTERN int stb_wildfind (char *expr, char *candidate);
+STB_EXTERN int stb_wildfindi (char *expr, char *candidate);
+
+STB_EXTERN int stb_regex(char *regex, char *candidate);
+
+typedef struct stb_matcher stb_matcher;
+
+STB_EXTERN stb_matcher *stb_regex_matcher(char *regex);
+STB_EXTERN int stb_matcher_match(stb_matcher *m, char *str);
+STB_EXTERN int stb_matcher_find(stb_matcher *m, char *str);
+STB_EXTERN void stb_matcher_free(stb_matcher *f);
+
+STB_EXTERN stb_matcher *stb_lex_matcher(void);
+STB_EXTERN int stb_lex_item(stb_matcher *m, const char *str, int result);
+STB_EXTERN int stb_lex_item_wild(stb_matcher *matcher, const char *regex, int result);
+STB_EXTERN int stb_lex(stb_matcher *m, char *str, int *len);
+
+
+
+#ifdef STB_DEFINE
+
+static int stb__match_qstring(char *candidate, char *qstring, int qlen, int insensitive)
+{
+ int i;
+ if (insensitive) {
+ for (i=0; i < qlen; ++i)
+ if (qstring[i] == '?') {
+ if (!candidate[i]) return 0;
+ } else
+ if (tolower(qstring[i]) != tolower(candidate[i]))
+ return 0;
+ } else {
+ for (i=0; i < qlen; ++i)
+ if (qstring[i] == '?') {
+ if (!candidate[i]) return 0;
+ } else
+ if (qstring[i] != candidate[i])
+ return 0;
+ }
+ return 1;
+}
+
+static int stb__find_qstring(char *candidate, char *qstring, int qlen, int insensitive)
+{
+ char c;
+
+ int offset=0;
+ while (*qstring == '?') {
+ ++qstring;
+ --qlen;
+ ++candidate;
+ if (qlen == 0) return 0;
+ if (*candidate == 0) return -1;
+ }
+
+ c = *qstring++;
+ --qlen;
+ if (insensitive) c = tolower(c);
+
+ while (candidate[offset]) {
+ if (c == (insensitive ? tolower(candidate[offset]) : candidate[offset]))
+ if (stb__match_qstring(candidate+offset+1, qstring, qlen, insensitive))
+ return offset;
+ ++offset;
+ }
+
+ return -1;
+}
+
+int stb__wildmatch_raw2(char *expr, char *candidate, int search, int insensitive)
+{
+ int where=0;
+ int start = -1;
+
+ if (!search) {
+ // parse to first '*'
+ if (*expr != '*')
+ start = 0;
+ while (*expr != '*') {
+ if (!*expr)
+ return *candidate == 0 ? 0 : -1;
+ if (*expr == '?') {
+ if (!*candidate) return -1;
+ } else {
+ if (insensitive) {
+ if (tolower(*candidate) != tolower(*expr))
+ return -1;
+ } else
+ if (*candidate != *expr)
+ return -1;
+ }
+ ++candidate, ++expr, ++where;
+ }
+ } else {
+ // 0-length search string
+ if (!*expr)
+ return 0;
+ }
+
+ assert(search || *expr == '*');
+ if (!search)
+ ++expr;
+
+ // implicit '*' at this point
+
+ while (*expr) {
+ int o=0;
+ // combine redundant * characters
+ while (expr[0] == '*') ++expr;
+
+ // ok, at this point, expr[-1] == '*',
+ // and expr[0] != '*'
+
+ if (!expr[0]) return start >= 0 ? start : 0;
+
+ // now find next '*'
+ o = 0;
+ while (expr[o] != '*') {
+ if (expr[o] == 0)
+ break;
+ ++o;
+ }
+ // if no '*', scan to end, then match at end
+ if (expr[o] == 0 && !search) {
+ int z;
+ for (z=0; z < o; ++z)
+ if (candidate[z] == 0)
+ return -1;
+ while (candidate[z])
+ ++z;
+ // ok, now check if they match
+ if (stb__match_qstring(candidate+z-o, expr, o, insensitive))
+ return start >= 0 ? start : 0;
+ return -1;
+ } else {
+ // if yes '*', then do stb__find_qmatch on the intervening chars
+ int n = stb__find_qstring(candidate, expr, o, insensitive);
+ if (n < 0)
+ return -1;
+ if (start < 0)
+ start = where + n;
+ expr += o;
+ candidate += n+o;
+ }
+
+ if (*expr == 0) {
+ assert(search);
+ return start;
+ }
+
+ assert(*expr == '*');
+ ++expr;
+ }
+
+ return start >= 0 ? start : 0;
+}
+
+int stb__wildmatch_raw(char *expr, char *candidate, int search, int insensitive)
+{
+ char buffer[256];
+ // handle multiple search strings
+ char *s = strchr(expr, ';');
+ char *last = expr;
+ while (s) {
+ int z;
+ // need to allow for non-writeable strings... assume they're small
+ if (s - last < 256) {
+ stb_strncpy(buffer, last, (int) (s-last+1));
+ buffer[s-last] = 0;
+ z = stb__wildmatch_raw2(buffer, candidate, search, insensitive);
+ } else {
+ *s = 0;
+ z = stb__wildmatch_raw2(last, candidate, search, insensitive);
+ *s = ';';
+ }
+ if (z >= 0) return z;
+ last = s+1;
+ s = strchr(last, ';');
+ }
+ return stb__wildmatch_raw2(last, candidate, search, insensitive);
+}
+
+int stb_wildmatch(char *expr, char *candidate)
+{
+ return stb__wildmatch_raw(expr, candidate, 0,0) >= 0;
+}
+
+int stb_wildmatchi(char *expr, char *candidate)
+{
+ return stb__wildmatch_raw(expr, candidate, 0,1) >= 0;
+}
+
+int stb_wildfind(char *expr, char *candidate)
+{
+ return stb__wildmatch_raw(expr, candidate, 1,0);
+}
+
+int stb_wildfindi(char *expr, char *candidate)
+{
+ return stb__wildmatch_raw(expr, candidate, 1,1);
+}
+
+typedef struct
+{
+ stb_int16 transition[256];
+} stb_dfa;
+
+// an NFA node represents a state you're in; it then has
+// an arbitrary number of edges dangling off of it
+// note this isn't utf8-y
+typedef struct
+{
+ stb_int16 match; // character/set to match
+ stb_uint16 node; // output node to go to
+} stb_nfa_edge;
+
+typedef struct
+{
+ stb_int16 goal; // does reaching this win the prize?
+ stb_uint8 active; // is this in the active list
+ stb_nfa_edge *out;
+ stb_uint16 *eps; // list of epsilon closures
+} stb_nfa_node;
+
+#define STB__DFA_UNDEF -1
+#define STB__DFA_GOAL -2
+#define STB__DFA_END -3
+#define STB__DFA_MGOAL -4
+#define STB__DFA_VALID 0
+
+#define STB__NFA_STOP_GOAL -1
+
+// compiled regexp
+struct stb_matcher
+{
+ stb_uint16 start_node;
+ stb_int16 dfa_start;
+ stb_uint32 *charset;
+ int num_charset;
+ int match_start;
+ stb_nfa_node *nodes;
+ int does_lex;
+
+ // dfa matcher
+ stb_dfa * dfa;
+ stb_uint32 * dfa_mapping;
+ stb_int16 * dfa_result;
+ int num_words_per_dfa;
+};
+
+static int stb__add_node(stb_matcher *matcher)
+{
+ stb_nfa_node z;
+ z.active = 0;
+ z.eps = 0;
+ z.goal = 0;
+ z.out = 0;
+ stb_arr_push(matcher->nodes, z);
+ return stb_arr_len(matcher->nodes)-1;
+}
+
+static void stb__add_epsilon(stb_matcher *matcher, int from, int to)
+{
+ assert(from != to);
+ if (matcher->nodes[from].eps == NULL)
+ stb_arr_malloc((void **) &matcher->nodes[from].eps, matcher);
+ stb_arr_push(matcher->nodes[from].eps, to);
+}
+
+static void stb__add_edge(stb_matcher *matcher, int from, int to, int type)
+{
+ stb_nfa_edge z = { (stb_int16)type, (stb_uint16)to };
+ if (matcher->nodes[from].out == NULL)
+ stb_arr_malloc((void **) &matcher->nodes[from].out, matcher);
+ stb_arr_push(matcher->nodes[from].out, z);
+}
+
+static char *stb__reg_parse_alt(stb_matcher *m, int s, char *r, stb_uint16 *e);
+static char *stb__reg_parse(stb_matcher *matcher, int start, char *regex, stb_uint16 *end)
+{
+ int n;
+ int last_start = -1;
+ stb_uint16 last_end = start;
+
+ while (*regex) {
+ switch (*regex) {
+ case '(':
+ last_start = last_end;
+ regex = stb__reg_parse_alt(matcher, last_end, regex+1, &last_end);
+ if (regex == NULL || *regex != ')')
+ return NULL;
+ ++regex;
+ break;
+
+ case '|':
+ case ')':
+ *end = last_end;
+ return regex;
+
+ case '?':
+ if (last_start < 0) return NULL;
+ stb__add_epsilon(matcher, last_start, last_end);
+ ++regex;
+ break;
+
+ case '*':
+ if (last_start < 0) return NULL;
+ stb__add_epsilon(matcher, last_start, last_end);
+
+ // fall through
+
+ case '+':
+ if (last_start < 0) return NULL;
+ stb__add_epsilon(matcher, last_end, last_start);
+ // prevent links back to last_end from chaining to last_start
+ n = stb__add_node(matcher);
+ stb__add_epsilon(matcher, last_end, n);
+ last_end = n;
+ ++regex;
+ break;
+
+ case '{': // not supported!
+ // @TODO: given {n,m}, clone last_start to last_end m times,
+ // and include epsilons from start to first m-n blocks
+ return NULL;
+
+ case '\\':
+ ++regex;
+ if (!*regex) return NULL;
+
+ // fallthrough
+ default: // match exactly this character
+ n = stb__add_node(matcher);
+ stb__add_edge(matcher, last_end, n, *regex);
+ last_start = last_end;
+ last_end = n;
+ ++regex;
+ break;
+
+ case '$':
+ n = stb__add_node(matcher);
+ stb__add_edge(matcher, last_end, n, '\n');
+ last_start = last_end;
+ last_end = n;
+ ++regex;
+ break;
+
+ case '.':
+ n = stb__add_node(matcher);
+ stb__add_edge(matcher, last_end, n, -1);
+ last_start = last_end;
+ last_end = n;
+ ++regex;
+ break;
+
+ case '[': {
+ stb_uint8 flags[256];
+ int invert = 0,z;
+ ++regex;
+ if (matcher->num_charset == 0) {
+ matcher->charset = (stb_uint *) stb_malloc(matcher, sizeof(*matcher->charset) * 256);
+ memset(matcher->charset, 0, sizeof(*matcher->charset) * 256);
+ }
+
+ memset(flags,0,sizeof(flags));
+
+ // leading ^ is special
+ if (*regex == '^')
+ ++regex, invert = 1;
+
+ // leading ] is special
+ if (*regex == ']') {
+ flags[(int) ']'] = 1;
+ ++regex;
+ }
+ while (*regex != ']') {
+ stb_uint a;
+ if (!*regex) return NULL;
+ a = *regex++;
+ if (regex[0] == '-' && regex[1] != ']') {
+ stb_uint i,b = regex[1];
+ regex += 2;
+ if (b == 0) return NULL;
+ if (a > b) return NULL;
+ for (i=a; i <= b; ++i)
+ flags[i] = 1;
+ } else
+ flags[a] = 1;
+ }
+ ++regex;
+ if (invert) {
+ int i;
+ for (i=0; i < 256; ++i)
+ flags[i] = 1-flags[i];
+ }
+
+ // now check if any existing charset matches
+ for (z=0; z < matcher->num_charset; ++z) {
+ int i, k[2] = { 0, 1 << z};
+ for (i=0; i < 256; ++i) {
+ unsigned int f = k[flags[i]];
+ if ((matcher->charset[i] & k[1]) != f)
+ break;
+ }
+ if (i == 256) break;
+ }
+
+ if (z == matcher->num_charset) {
+ int i;
+ ++matcher->num_charset;
+ if (matcher->num_charset > 32) {
+ assert(0); /* NOTREACHED */
+ return NULL; // too many charsets, oops
+ }
+ for (i=0; i < 256; ++i)
+ if (flags[i])
+ matcher->charset[i] |= (1 << z);
+ }
+
+ n = stb__add_node(matcher);
+ stb__add_edge(matcher, last_end, n, -2 - z);
+ last_start = last_end;
+ last_end = n;
+ break;
+ }
+ }
+ }
+ *end = last_end;
+ return regex;
+}
+
+static char *stb__reg_parse_alt(stb_matcher *matcher, int start, char *regex, stb_uint16 *end)
+{
+ stb_uint16 last_end = start;
+ stb_uint16 main_end;
+
+ int head, tail;
+
+ head = stb__add_node(matcher);
+ stb__add_epsilon(matcher, start, head);
+
+ regex = stb__reg_parse(matcher, head, regex, &last_end);
+ if (regex == NULL) return NULL;
+ if (*regex == 0 || *regex == ')') {
+ *end = last_end;
+ return regex;
+ }
+
+ main_end = last_end;
+ tail = stb__add_node(matcher);
+
+ stb__add_epsilon(matcher, last_end, tail);
+
+ // start alternatives from the same starting node; use epsilon
+ // transitions to combine their endings
+ while(*regex && *regex != ')') {
+ assert(*regex == '|');
+ head = stb__add_node(matcher);
+ stb__add_epsilon(matcher, start, head);
+ regex = stb__reg_parse(matcher, head, regex+1, &last_end);
+ if (regex == NULL)
+ return NULL;
+ stb__add_epsilon(matcher, last_end, tail);
+ }
+
+ *end = tail;
+ return regex;
+}
+
+static char *stb__wild_parse(stb_matcher *matcher, int start, char *str, stb_uint16 *end)
+{
+ int n;
+ stb_uint16 last_end;
+
+ last_end = stb__add_node(matcher);
+ stb__add_epsilon(matcher, start, last_end);
+
+ while (*str) {
+ switch (*str) {
+ // fallthrough
+ default: // match exactly this character
+ n = stb__add_node(matcher);
+ if (toupper(*str) == tolower(*str)) {
+ stb__add_edge(matcher, last_end, n, *str);
+ } else {
+ stb__add_edge(matcher, last_end, n, tolower(*str));
+ stb__add_edge(matcher, last_end, n, toupper(*str));
+ }
+ last_end = n;
+ ++str;
+ break;
+
+ case '?':
+ n = stb__add_node(matcher);
+ stb__add_edge(matcher, last_end, n, -1);
+ last_end = n;
+ ++str;
+ break;
+
+ case '*':
+ n = stb__add_node(matcher);
+ stb__add_edge(matcher, last_end, n, -1);
+ stb__add_epsilon(matcher, last_end, n);
+ stb__add_epsilon(matcher, n, last_end);
+ last_end = n;
+ ++str;
+ break;
+ }
+ }
+
+ // now require end of string to match
+ n = stb__add_node(matcher);
+ stb__add_edge(matcher, last_end, n, 0);
+ last_end = n;
+
+ *end = last_end;
+ return str;
+}
+
+static int stb__opt(stb_matcher *m, int n)
+{
+ for(;;) {
+ stb_nfa_node *p = &m->nodes[n];
+ if (p->goal) return n;
+ if (stb_arr_len(p->out)) return n;
+ if (stb_arr_len(p->eps) != 1) return n;
+ n = p->eps[0];
+ }
+}
+
+static void stb__optimize(stb_matcher *m)
+{
+ // if the target of any edge is a node with exactly
+ // one out-epsilon, shorten it
+ int i,j;
+ for (i=0; i < stb_arr_len(m->nodes); ++i) {
+ stb_nfa_node *p = &m->nodes[i];
+ for (j=0; j < stb_arr_len(p->out); ++j)
+ p->out[j].node = stb__opt(m,p->out[j].node);
+ for (j=0; j < stb_arr_len(p->eps); ++j)
+ p->eps[j] = stb__opt(m,p->eps[j] );
+ }
+ m->start_node = stb__opt(m,m->start_node);
+}
+
+void stb_matcher_free(stb_matcher *f)
+{
+ stb_free(f);
+}
+
+static stb_matcher *stb__alloc_matcher(void)
+{
+ stb_matcher *matcher = (stb_matcher *) stb_malloc(0,sizeof(*matcher));
+
+ matcher->start_node = 0;
+ stb_arr_malloc((void **) &matcher->nodes, matcher);
+ matcher->num_charset = 0;
+ matcher->match_start = 0;
+ matcher->does_lex = 0;
+
+ matcher->dfa_start = STB__DFA_UNDEF;
+ stb_arr_malloc((void **) &matcher->dfa, matcher);
+ stb_arr_malloc((void **) &matcher->dfa_mapping, matcher);
+ stb_arr_malloc((void **) &matcher->dfa_result, matcher);
+
+ stb__add_node(matcher);
+
+ return matcher;
+}
+
+static void stb__lex_reset(stb_matcher *matcher)
+{
+ // flush cached dfa data
+ stb_arr_setlen(matcher->dfa, 0);
+ stb_arr_setlen(matcher->dfa_mapping, 0);
+ stb_arr_setlen(matcher->dfa_result, 0);
+ matcher->dfa_start = STB__DFA_UNDEF;
+}
+
+stb_matcher *stb_regex_matcher(char *regex)
+{
+ char *z;
+ stb_uint16 end;
+ stb_matcher *matcher = stb__alloc_matcher();
+ if (*regex == '^') {
+ matcher->match_start = 1;
+ ++regex;
+ }
+
+ z = stb__reg_parse_alt(matcher, matcher->start_node, regex, &end);
+
+ if (!z || *z) {
+ stb_free(matcher);
+ return NULL;
+ }
+
+ ((matcher->nodes)[(int) end]).goal = STB__NFA_STOP_GOAL;
+
+ return matcher;
+}
+
+stb_matcher *stb_lex_matcher(void)
+{
+ stb_matcher *matcher = stb__alloc_matcher();
+
+ matcher->match_start = 1;
+ matcher->does_lex = 1;
+
+ return matcher;
+}
+
+int stb_lex_item(stb_matcher *matcher, const char *regex, int result)
+{
+ char *z;
+ stb_uint16 end;
+
+ z = stb__reg_parse_alt(matcher, matcher->start_node, (char*) regex, &end);
+
+ if (z == NULL)
+ return 0;
+
+ stb__lex_reset(matcher);
+
+ matcher->nodes[(int) end].goal = result;
+ return 1;
+}
+
+int stb_lex_item_wild(stb_matcher *matcher, const char *regex, int result)
+{
+ char *z;
+ stb_uint16 end;
+
+ z = stb__wild_parse(matcher, matcher->start_node, (char*) regex, &end);
+
+ if (z == NULL)
+ return 0;
+
+ stb__lex_reset(matcher);
+
+ matcher->nodes[(int) end].goal = result;
+ return 1;
+}
+
+static void stb__clear(stb_matcher *m, stb_uint16 *list)
+{
+ int i;
+ for (i=0; i < stb_arr_len(list); ++i)
+ m->nodes[(int) list[i]].active = 0;
+}
+
+static int stb__clear_goalcheck(stb_matcher *m, stb_uint16 *list)
+{
+ int i, t=0;
+ for (i=0; i < stb_arr_len(list); ++i) {
+ t += m->nodes[(int) list[i]].goal;
+ m->nodes[(int) list[i]].active = 0;
+ }
+ return t;
+}
+
+static stb_uint16 * stb__add_if_inactive(stb_matcher *m, stb_uint16 *list, int n)
+{
+ if (!m->nodes[n].active) {
+ stb_arr_push(list, n);
+ m->nodes[n].active = 1;
+ }
+ return list;
+}
+
+static stb_uint16 * stb__eps_closure(stb_matcher *m, stb_uint16 *list)
+{
+ int i,n = stb_arr_len(list);
+
+ for(i=0; i < n; ++i) {
+ stb_uint16 *e = m->nodes[(int) list[i]].eps;
+ if (e) {
+ int j,k = stb_arr_len(e);
+ for (j=0; j < k; ++j)
+ list = stb__add_if_inactive(m, list, e[j]);
+ n = stb_arr_len(list);
+ }
+ }
+
+ return list;
+}
+
+int stb_matcher_match(stb_matcher *m, char *str)
+{
+ int result = 0;
+ int i,j,y,z;
+ stb_uint16 *previous = NULL;
+ stb_uint16 *current = NULL;
+ stb_uint16 *temp;
+
+ stb_arr_setsize(previous, 4);
+ stb_arr_setsize(current, 4);
+
+ previous = stb__add_if_inactive(m, previous, m->start_node);
+ previous = stb__eps_closure(m,previous);
+ stb__clear(m, previous);
+
+ while (*str && stb_arr_len(previous)) {
+ y = stb_arr_len(previous);
+ for (i=0; i < y; ++i) {
+ stb_nfa_node *n = &m->nodes[(int) previous[i]];
+ z = stb_arr_len(n->out);
+ for (j=0; j < z; ++j) {
+ if (n->out[j].match >= 0) {
+ if (n->out[j].match == *str)
+ current = stb__add_if_inactive(m, current, n->out[j].node);
+ } else if (n->out[j].match == -1) {
+ if (*str != '\n')
+ current = stb__add_if_inactive(m, current, n->out[j].node);
+ } else if (n->out[j].match < -1) {
+ int z = -n->out[j].match - 2;
+ if (m->charset[(stb_uint8) *str] & (1 << z))
+ current = stb__add_if_inactive(m, current, n->out[j].node);
+ }
+ }
+ }
+ stb_arr_setlen(previous, 0);
+
+ temp = previous;
+ previous = current;
+ current = temp;
+
+ previous = stb__eps_closure(m,previous);
+ stb__clear(m, previous);
+
+ ++str;
+ }
+
+ // transition to pick up a '$' at the end
+ y = stb_arr_len(previous);
+ for (i=0; i < y; ++i)
+ m->nodes[(int) previous[i]].active = 1;
+
+ for (i=0; i < y; ++i) {
+ stb_nfa_node *n = &m->nodes[(int) previous[i]];
+ z = stb_arr_len(n->out);
+ for (j=0; j < z; ++j) {
+ if (n->out[j].match == '\n')
+ current = stb__add_if_inactive(m, current, n->out[j].node);
+ }
+ }
+
+ previous = stb__eps_closure(m,previous);
+ stb__clear(m, previous);
+
+ y = stb_arr_len(previous);
+ for (i=0; i < y; ++i)
+ if (m->nodes[(int) previous[i]].goal)
+ result = 1;
+
+ stb_arr_free(previous);
+ stb_arr_free(current);
+
+ return result && *str == 0;
+}
+
+stb_int16 stb__get_dfa_node(stb_matcher *m, stb_uint16 *list)
+{
+ stb_uint16 node;
+ stb_uint32 data[8], *state, *newstate;
+ int i,j,n;
+
+ state = (stb_uint32 *) stb_temp(data, m->num_words_per_dfa * 4);
+ memset(state, 0, m->num_words_per_dfa*4);
+
+ n = stb_arr_len(list);
+ for (i=0; i < n; ++i) {
+ int x = list[i];
+ state[x >> 5] |= 1 << (x & 31);
+ }
+
+ // @TODO use a hash table
+ n = stb_arr_len(m->dfa_mapping);
+ i=j=0;
+ for(; j < n; ++i, j += m->num_words_per_dfa) {
+ // @TODO special case for <= 32
+ if (!memcmp(state, m->dfa_mapping + j, m->num_words_per_dfa*4)) {
+ node = i;
+ goto done;
+ }
+ }
+
+ assert(stb_arr_len(m->dfa) == i);
+ node = i;
+
+ newstate = stb_arr_addn(m->dfa_mapping, m->num_words_per_dfa);
+ memcpy(newstate, state, m->num_words_per_dfa*4);
+
+ // set all transitions to 'unknown'
+ stb_arr_add(m->dfa);
+ memset(m->dfa[i].transition, -1, sizeof(m->dfa[i].transition));
+
+ if (m->does_lex) {
+ int result = -1;
+ n = stb_arr_len(list);
+ for (i=0; i < n; ++i) {
+ if (m->nodes[(int) list[i]].goal > result)
+ result = m->nodes[(int) list[i]].goal;
+ }
+
+ stb_arr_push(m->dfa_result, result);
+ }
+
+done:
+ stb_tempfree(data, state);
+ return node;
+}
+
+static int stb__matcher_dfa(stb_matcher *m, char *str_c, int *len)
+{
+ stb_uint8 *str = (stb_uint8 *) str_c;
+ stb_int16 node,prevnode;
+ stb_dfa *trans;
+ int match_length = 0;
+ stb_int16 match_result=0;
+
+ if (m->dfa_start == STB__DFA_UNDEF) {
+ stb_uint16 *list;
+
+ m->num_words_per_dfa = (stb_arr_len(m->nodes)+31) >> 5;
+ stb__optimize(m);
+
+ list = stb__add_if_inactive(m, NULL, m->start_node);
+ list = stb__eps_closure(m,list);
+ if (m->does_lex) {
+ m->dfa_start = stb__get_dfa_node(m,list);
+ stb__clear(m, list);
+ // DON'T allow start state to be a goal state!
+ // this allows people to specify regexes that can match 0
+ // characters without them actually matching (also we don't
+ // check _before_ advancing anyway
+ if (m->dfa_start <= STB__DFA_MGOAL)
+ m->dfa_start = -(m->dfa_start - STB__DFA_MGOAL);
+ } else {
+ if (stb__clear_goalcheck(m, list))
+ m->dfa_start = STB__DFA_GOAL;
+ else
+ m->dfa_start = stb__get_dfa_node(m,list);
+ }
+ stb_arr_free(list);
+ }
+
+ prevnode = STB__DFA_UNDEF;
+ node = m->dfa_start;
+ trans = m->dfa;
+
+ if (m->dfa_start == STB__DFA_GOAL)
+ return 1;
+
+ for(;;) {
+ assert(node >= STB__DFA_VALID);
+
+ // fast inner DFA loop; especially if STB__DFA_VALID is 0
+
+ do {
+ prevnode = node;
+ node = trans[node].transition[*str++];
+ } while (node >= STB__DFA_VALID);
+
+ assert(node >= STB__DFA_MGOAL - stb_arr_len(m->dfa));
+ assert(node < stb_arr_len(m->dfa));
+
+ // special case for lex: need _longest_ match, so notice goal
+ // state without stopping
+ if (node <= STB__DFA_MGOAL) {
+ match_length = (int) (str - (stb_uint8 *) str_c);
+ node = -(node - STB__DFA_MGOAL);
+ match_result = node;
+ continue;
+ }
+
+ // slow NFA->DFA conversion
+
+ // or we hit the goal or the end of the string, but those
+ // can only happen once per search...
+
+ if (node == STB__DFA_UNDEF) {
+ // build a list -- @TODO special case <= 32 states
+ // heck, use a more compact data structure for <= 16 and <= 8 ?!
+
+ // @TODO keep states/newstates around instead of reallocating them
+ stb_uint16 *states = NULL;
+ stb_uint16 *newstates = NULL;
+ int i,j,y,z;
+ stb_uint32 *flags = &m->dfa_mapping[prevnode * m->num_words_per_dfa];
+ assert(prevnode != STB__DFA_UNDEF);
+ stb_arr_setsize(states, 4);
+ stb_arr_setsize(newstates,4);
+ for (j=0; j < m->num_words_per_dfa; ++j) {
+ for (i=0; i < 32; ++i) {
+ if (*flags & (1 << i))
+ stb_arr_push(states, j*32+i);
+ }
+ ++flags;
+ }
+ // states is now the states we were in in the previous node;
+ // so now we can compute what node it transitions to on str[-1]
+
+ y = stb_arr_len(states);
+ for (i=0; i < y; ++i) {
+ stb_nfa_node *n = &m->nodes[(int) states[i]];
+ z = stb_arr_len(n->out);
+ for (j=0; j < z; ++j) {
+ if (n->out[j].match >= 0) {
+ if (n->out[j].match == str[-1] || (str[-1] == 0 && n->out[j].match == '\n'))
+ newstates = stb__add_if_inactive(m, newstates, n->out[j].node);
+ } else if (n->out[j].match == -1) {
+ if (str[-1] != '\n' && str[-1])
+ newstates = stb__add_if_inactive(m, newstates, n->out[j].node);
+ } else if (n->out[j].match < -1) {
+ int z = -n->out[j].match - 2;
+ if (m->charset[str[-1]] & (1 << z))
+ newstates = stb__add_if_inactive(m, newstates, n->out[j].node);
+ }
+ }
+ }
+ // AND add in the start state!
+ if (!m->match_start || (str[-1] == '\n' && !m->does_lex))
+ newstates = stb__add_if_inactive(m, newstates, m->start_node);
+ // AND epsilon close it
+ newstates = stb__eps_closure(m, newstates);
+ // if it's a goal state, then that's all there is to it
+ if (stb__clear_goalcheck(m, newstates)) {
+ if (m->does_lex) {
+ match_length = (int) (str - (stb_uint8 *) str_c);
+ node = stb__get_dfa_node(m,newstates);
+ match_result = node;
+ node = -node + STB__DFA_MGOAL;
+ trans = m->dfa; // could have gotten realloc()ed
+ } else
+ node = STB__DFA_GOAL;
+ } else if (str[-1] == 0 || stb_arr_len(newstates) == 0) {
+ node = STB__DFA_END;
+ } else {
+ node = stb__get_dfa_node(m,newstates);
+ trans = m->dfa; // could have gotten realloc()ed
+ }
+ trans[prevnode].transition[str[-1]] = node;
+ if (node <= STB__DFA_MGOAL)
+ node = -(node - STB__DFA_MGOAL);
+ stb_arr_free(newstates);
+ stb_arr_free(states);
+ }
+
+ if (node == STB__DFA_GOAL) {
+ return 1;
+ }
+ if (node == STB__DFA_END) {
+ if (m->does_lex) {
+ if (match_result) {
+ if (len) *len = match_length;
+ return m->dfa_result[(int) match_result];
+ }
+ }
+ return 0;
+ }
+
+ assert(node != STB__DFA_UNDEF);
+ }
+}
+
+int stb_matcher_find(stb_matcher *m, char *str)
+{
+ assert(m->does_lex == 0);
+ return stb__matcher_dfa(m, str, NULL);
+}
+
+int stb_lex(stb_matcher *m, char *str, int *len)
+{
+ assert(m->does_lex);
+ return stb__matcher_dfa(m, str, len);
+}
+
+#ifdef STB_PERFECT_HASH
+int stb_regex(char *regex, char *str)
+{
+ static stb_perfect p;
+ static stb_matcher ** matchers;
+ static char ** regexps;
+ static char ** regexp_cache;
+ static unsigned short *mapping;
+ int z = stb_perfect_hash(&p, (int)(size_t) regex);
+ if (z >= 0) {
+ if (strcmp(regex, regexp_cache[(int) mapping[z]])) {
+ int i = mapping[z];
+ stb_matcher_free(matchers[i]);
+ free(regexp_cache[i]);
+ regexps[i] = regex;
+ regexp_cache[i] = stb_p_strdup(regex);
+ matchers[i] = stb_regex_matcher(regex);
+ }
+ } else {
+ int i,n;
+ if (regex == NULL) {
+ for (i=0; i < stb_arr_len(matchers); ++i) {
+ stb_matcher_free(matchers[i]);
+ free(regexp_cache[i]);
+ }
+ stb_arr_free(matchers);
+ stb_arr_free(regexps);
+ stb_arr_free(regexp_cache);
+ stb_perfect_destroy(&p);
+ free(mapping); mapping = NULL;
+ return -1;
+ }
+ stb_arr_push(regexps, regex);
+ stb_arr_push(regexp_cache, stb_p_strdup(regex));
+ stb_arr_push(matchers, stb_regex_matcher(regex));
+ stb_perfect_destroy(&p);
+ n = stb_perfect_create(&p, (unsigned int *) (char **) regexps, stb_arr_len(regexps));
+ mapping = (unsigned short *) realloc(mapping, n * sizeof(*mapping));
+ for (i=0; i < stb_arr_len(regexps); ++i)
+ mapping[stb_perfect_hash(&p, (int)(size_t) regexps[i])] = i;
+ z = stb_perfect_hash(&p, (int)(size_t) regex);
+ }
+ return stb_matcher_find(matchers[(int) mapping[z]], str);
+}
+#endif
+#endif // STB_DEFINE
+
+
+#if 0
+//////////////////////////////////////////////////////////////////////////////
+//
+// C source-code introspection
+//
+
+// runtime structure
+typedef struct
+{
+ char *name;
+ char *type; // base type
+ char *comment; // content of comment field
+ int size; // size of base type
+ int offset; // field offset
+ int arrcount[8]; // array sizes; -1 = pointer indirection; 0 = end of list
+} stb_info_field;
+
+typedef struct
+{
+ char *structname;
+ int size;
+ int num_fields;
+ stb_info_field *fields;
+} stb_info_struct;
+
+extern stb_info_struct stb_introspect_output[];
+
+//
+
+STB_EXTERN void stb_introspect_precompiled(stb_info_struct *compiled);
+STB_EXTERN void stb__introspect(char *path, char *file);
+
+#define stb_introspect_ship() stb__introspect(NULL, NULL, stb__introspect_output)
+
+#ifdef STB_SHIP
+#define stb_introspect() stb_introspect_ship()
+#define stb_introspect_path(p) stb_introspect_ship()
+#else
+// bootstrapping: define stb_introspect() (or 'path') the first time
+#define stb_introspect() stb__introspect(NULL, __FILE__, NULL)
+#define stb_introspect_auto() stb__introspect(NULL, __FILE__, stb__introspect_output)
+
+#define stb_introspect_path(p) stb__introspect(p, __FILE__, NULL)
+#define stb_introspect_path(p) stb__introspect(p, __FILE__, NULL)
+#endif
+
+#ifdef STB_DEFINE
+
+#ifndef STB_INTROSPECT_CPP
+ #ifdef __cplusplus
+ #define STB_INTROSPECT_CPP 1
+ #else
+ #define STB_INTROSPECT_CPP 0
+ #endif
+#endif
+
+void stb_introspect_precompiled(stb_info_struct *compiled)
+{
+
+}
+
+
+static void stb__introspect_filename(char *buffer, char *path)
+{
+ #if STB_INTROSPECT_CPP
+ stb_p_sprintf(buffer stb_p_size(9999), "%s/stb_introspect.cpp", path);
+ #else
+ stb_p_sprintf(buffer stb_p_size(9999), "%s/stb_introspect.c", path);
+ #endif
+}
+
+static void stb__introspect_compute(char *path, char *file)
+{
+ int i;
+ char ** include_list = NULL;
+ char ** introspect_list = NULL;
+ FILE *f;
+ f = stb_p_fopen(file, "w");
+ if (!f) return;
+
+ fputs("// if you get compiler errors, change the following 0 to a 1:\n", f);
+ fputs("#define STB_INTROSPECT_INVALID 0\n\n", f);
+ fputs("// this will force the code to compile, and force the introspector\n", f);
+ fputs("// to run and then exit, allowing you to recompile\n\n\n", f);
+ fputs("#include \"stb.h\"\n\n",f );
+ fputs("#if STB_INTROSPECT_INVALID\n", f);
+ fputs(" stb_info_struct stb__introspect_output[] = { (void *) 1 }\n", f);
+ fputs("#else\n\n", f);
+ for (i=0; i < stb_arr_len(include_list); ++i)
+ fprintf(f, " #include \"%s\"\n", include_list[i]);
+
+ fputs(" stb_info_struct stb__introspect_output[] =\n{\n", f);
+ for (i=0; i < stb_arr_len(introspect_list); ++i)
+ fprintf(f, " stb_introspect_%s,\n", introspect_list[i]);
+ fputs(" };\n", f);
+ fputs("#endif\n", f);
+ fclose(f);
+}
+
+static stb_info_struct *stb__introspect_info;
+
+#ifndef STB_SHIP
+
+#endif
+
+void stb__introspect(char *path, char *file, stb_info_struct *compiled)
+{
+ static int first=1;
+ if (!first) return;
+ first=0;
+
+ stb__introspect_info = compiled;
+
+ #ifndef STB_SHIP
+ if (path || file) {
+ int bail_flag = compiled && compiled[0].structname == (void *) 1;
+ int needs_building = bail_flag;
+ struct stb__stat st;
+ char buffer[1024], buffer2[1024];
+ if (!path) {
+ stb_splitpath(buffer, file, STB_PATH);
+ path = buffer;
+ }
+ // bail if the source path doesn't exist
+ if (!stb_fexists(path)) return;
+
+ stb__introspect_filename(buffer2, path);
+
+ // get source/include files timestamps, compare to output-file timestamp;
+ // if mismatched, regenerate
+
+ if (stb__stat(buffer2, &st))
+ needs_building = STB_TRUE;
+
+ {
+ // find any file that contains an introspection command and is newer
+ // if needs_building is already true, we don't need to do this test,
+ // but we still need these arrays, so go ahead and get them
+ char **all[3];
+ all[0] = stb_readdir_files_mask(path, "*.h");
+ all[1] = stb_readdir_files_mask(path, "*.c");
+ all[2] = stb_readdir_files_mask(path, "*.cpp");
+ int i,j;
+ if (needs_building) {
+ for (j=0; j < 3; ++j) {
+ for (i=0; i < stb_arr_len(all[j]); ++i) {
+ struct stb__stat st2;
+ if (!stb__stat(all[j][i], &st2)) {
+ if (st.st_mtime < st2.st_mtime) {
+ char *z = stb_filec(all[j][i], NULL);
+ int found=STB_FALSE;
+ while (y) {
+ y = strstr(y, "//si");
+ if (y && isspace(y[4])) {
+ found = STB_TRUE;
+ break;
+ }
+ }
+ needs_building = STB_TRUE;
+ goto done;
+ }
+ }
+ }
+ }
+ done:;
+ }
+ char *z = stb_filec(all[i], NULL), *y = z;
+ int found=STB_FALSE;
+ while (y) {
+ y = strstr(y, "//si");
+ if (y && isspace(y[4])) {
+ found = STB_TRUE;
+ break;
+ }
+ }
+ if (found)
+ stb_arr_push(introspect_h, stb_p_strdup(all[i]));
+ free(z);
+ }
+ }
+ stb_readdir_free(all);
+ if (!needs_building) {
+ for (i=0; i < stb_arr_len(introspect_h); ++i) {
+ struct stb__stat st2;
+ if (!stb__stat(introspect_h[i], &st2))
+ if (st.st_mtime < st2.st_mtime)
+ needs_building = STB_TRUE;
+ }
+ }
+
+ if (needs_building) {
+ stb__introspect_compute(path, buffer2);
+ }
+ }
+ }
+ #endif
+}
+#endif
+#endif
+
+#ifdef STB_INTROSPECT
+// compile-time code-generator
+#define INTROSPECT(x) int main(int argc, char **argv) { stb__introspect(__FILE__); return 0; }
+#define FILE(x)
+
+void stb__introspect(char *filename)
+{
+ char *file = stb_file(filename, NULL);
+ char *s = file, *t, **p;
+ char *out_name = "stb_introspect.c";
+ char *out_path;
+ STB_ARR(char) filelist = NULL;
+ int i,n;
+ if (!file) stb_fatal("Couldn't open %s", filename);
+
+ out_path = stb_splitpathdup(filename, STB_PATH);
+
+ // search for the macros
+ while (*s) {
+ char buffer[256];
+ while (*s && !isupper(*s)) ++s;
+ s = stb_strtok_invert(buffer, s, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ s = stb_skipwhite(s);
+ if (*s == '(') {
+ ++s;
+ t = strchr(s, ')');
+ if (t == NULL) stb_fatal("Error parsing %s", filename);
+
+ }
+ }
+}
+
+
+
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// STB-C sliding-window dictionary compression
+//
+// This uses a DEFLATE-style sliding window, but no bitwise entropy.
+// Everything is on byte boundaries, so you could then apply a byte-wise
+// entropy code, though that's nowhere near as effective.
+//
+// An STB-C stream begins with a 16-byte header:
+// 4 bytes: 0x57 0xBC 0x00 0x00
+// 8 bytes: big-endian size of decompressed data, 64-bits
+// 4 bytes: big-endian size of window (how far back decompressor may need)
+//
+// The following symbols appear in the stream (these were determined ad hoc,
+// not by analysis):
+//
+// [dict] 00000100 yyyyyyyy yyyyyyyy yyyyyyyy xxxxxxxx xxxxxxxx
+// [END] 00000101 11111010 cccccccc cccccccc cccccccc cccccccc
+// [dict] 00000110 yyyyyyyy yyyyyyyy yyyyyyyy xxxxxxxx
+// [literals] 00000111 zzzzzzzz zzzzzzzz
+// [literals] 00001zzz zzzzzzzz
+// [dict] 00010yyy yyyyyyyy yyyyyyyy xxxxxxxx xxxxxxxx
+// [dict] 00011yyy yyyyyyyy yyyyyyyy xxxxxxxx
+// [literals] 001zzzzz
+// [dict] 01yyyyyy yyyyyyyy xxxxxxxx
+// [dict] 1xxxxxxx yyyyyyyy
+//
+// xxxxxxxx: match length - 1
+// yyyyyyyy: backwards distance - 1
+// zzzzzzzz: num literals - 1
+// cccccccc: adler32 checksum of decompressed data
+// (all big-endian)
+
+
+STB_EXTERN stb_uint stb_decompress_length(stb_uchar *input);
+STB_EXTERN stb_uint stb_decompress(stb_uchar *out,stb_uchar *in,stb_uint len);
+STB_EXTERN stb_uint stb_compress (stb_uchar *out,stb_uchar *in,stb_uint len);
+STB_EXTERN void stb_compress_window(int z);
+STB_EXTERN void stb_compress_hashsize(unsigned int z);
+
+STB_EXTERN int stb_compress_tofile(char *filename, char *in, stb_uint len);
+STB_EXTERN int stb_compress_intofile(FILE *f, char *input, stb_uint len);
+STB_EXTERN char *stb_decompress_fromfile(char *filename, stb_uint *len);
+
+STB_EXTERN int stb_compress_stream_start(FILE *f);
+STB_EXTERN void stb_compress_stream_end(int close);
+STB_EXTERN void stb_write(char *data, int data_len);
+
+#ifdef STB_DEFINE
+
+stb_uint stb_decompress_length(stb_uchar *input)
+{
+ return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11];
+}
+
+//////////////////// decompressor ///////////////////////
+
+// simple implementation that just writes whole thing into big block
+
+static unsigned char *stb__barrier;
+static unsigned char *stb__barrier2;
+static unsigned char *stb__barrier3;
+static unsigned char *stb__barrier4;
+
+static stb_uchar *stb__dout;
+static void stb__match(stb_uchar *data, stb_uint length)
+{
+ // INVERSE of memmove... write each byte before copying the next...
+ assert (stb__dout + length <= stb__barrier);
+ if (stb__dout + length > stb__barrier) { stb__dout += length; return; }
+ if (data < stb__barrier4) { stb__dout = stb__barrier+1; return; }
+ while (length--) *stb__dout++ = *data++;
+}
+
+static void stb__lit(stb_uchar *data, stb_uint length)
+{
+ assert (stb__dout + length <= stb__barrier);
+ if (stb__dout + length > stb__barrier) { stb__dout += length; return; }
+ if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; }
+ memcpy(stb__dout, data, length);
+ stb__dout += length;
+}
+
+#define stb__in2(x) ((i[x] << 8) + i[(x)+1])
+#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1))
+#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1))
+
+static stb_uchar *stb_decompress_token(stb_uchar *i)
+{
+ if (*i >= 0x20) { // use fewer if's for cases that expand small
+ if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2;
+ else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3;
+ else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1);
+ } else { // more ifs for cases that expand large, since overhead is amortized
+ if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4;
+ else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5;
+ else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1);
+ else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1);
+ else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5;
+ else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6;
+ }
+ return i;
+}
+
+stb_uint stb_decompress(stb_uchar *output, stb_uchar *i, stb_uint length)
+{
+ stb_uint olen;
+ if (stb__in4(0) != 0x57bC0000) return 0;
+ if (stb__in4(4) != 0) return 0; // error! stream is > 4GB
+ olen = stb_decompress_length(i);
+ stb__barrier2 = i;
+ stb__barrier3 = i+length;
+ stb__barrier = output + olen;
+ stb__barrier4 = output;
+ i += 16;
+
+ stb__dout = output;
+ while (1) {
+ stb_uchar *old_i = i;
+ i = stb_decompress_token(i);
+ if (i == old_i) {
+ if (*i == 0x05 && i[1] == 0xfa) {
+ assert(stb__dout == output + olen);
+ if (stb__dout != output + olen) return 0;
+ if (stb_adler32(1, output, olen) != (stb_uint) stb__in4(2))
+ return 0;
+ return olen;
+ } else {
+ assert(0); /* NOTREACHED */
+ return 0;
+ }
+ }
+ assert(stb__dout <= output + olen);
+ if (stb__dout > output + olen)
+ return 0;
+ }
+}
+
+char *stb_decompress_fromfile(char *filename, unsigned int *len)
+{
+ unsigned int n;
+ char *q;
+ unsigned char *p;
+ FILE *f = stb_p_fopen(filename, "rb"); if (f == NULL) return NULL;
+ fseek(f, 0, SEEK_END);
+ n = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ p = (unsigned char * ) malloc(n); if (p == NULL) return NULL;
+ fread(p, 1, n, f);
+ fclose(f);
+ if (p == NULL) return NULL;
+ if (p[0] != 0x57 || p[1] != 0xBc || p[2] || p[3]) { free(p); return NULL; }
+ q = (char *) malloc(stb_decompress_length(p)+1);
+ if (!q) { free(p); return NULL; }
+ *len = stb_decompress((unsigned char *) q, p, n);
+ if (*len) q[*len] = 0;
+ free(p);
+ return q;
+}
+
+#if 0
+// streaming decompressor
+
+static struct
+{
+ stb__uchar *in_buffer;
+ stb__uchar *match;
+
+ stb__uint pending_literals;
+ stb__uint pending_match;
+} xx;
+
+
+
+static void stb__match(stb_uchar *data, stb_uint length)
+{
+ // INVERSE of memmove... write each byte before copying the next...
+ assert (stb__dout + length <= stb__barrier);
+ if (stb__dout + length > stb__barrier) { stb__dout += length; return; }
+ if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; }
+ while (length--) *stb__dout++ = *data++;
+}
+
+static void stb__lit(stb_uchar *data, stb_uint length)
+{
+ assert (stb__dout + length <= stb__barrier);
+ if (stb__dout + length > stb__barrier) { stb__dout += length; return; }
+ if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; }
+ memcpy(stb__dout, data, length);
+ stb__dout += length;
+}
+
+static void sx_match(stb_uchar *data, stb_uint length)
+{
+ xx.match = data;
+ xx.pending_match = length;
+}
+
+static void sx_lit(stb_uchar *data, stb_uint length)
+{
+ xx.pending_lit = length;
+}
+
+static int stb_decompress_token_state(void)
+{
+ stb__uchar *i = xx.in_buffer;
+
+ if (*i >= 0x20) { // use fewer if's for cases that expand small
+ if (*i >= 0x80) sx_match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2;
+ else if (*i >= 0x40) sx_match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3;
+ else /* *i >= 0x20 */ sx_lit(i+1, i[0] - 0x20 + 1), i += 1;
+ } else { // more ifs for cases that expand large, since overhead is amortized
+ if (*i >= 0x18) sx_match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4;
+ else if (*i >= 0x10) sx_match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5;
+ else if (*i >= 0x08) sx_lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2;
+ else if (*i == 0x07) sx_lit(i+3, stb__in2(1) + 1), i += 3;
+ else if (*i == 0x06) sx_match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5;
+ else if (*i == 0x04) sx_match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6;
+ else return 0;
+ }
+ xx.in_buffer = i;
+ return 1;
+}
+#endif
+
+
+
+//////////////////// compressor ///////////////////////
+
+static unsigned int stb_matchlen(stb_uchar *m1, stb_uchar *m2, stb_uint maxlen)
+{
+ stb_uint i;
+ for (i=0; i < maxlen; ++i)
+ if (m1[i] != m2[i]) return i;
+ return i;
+}
+
+// simple implementation that just takes the source data in a big block
+
+static stb_uchar *stb__out;
+static FILE *stb__outfile;
+static stb_uint stb__outbytes;
+
+static void stb__write(unsigned char v)
+{
+ fputc(v, stb__outfile);
+ ++stb__outbytes;
+}
+
+#define stb_out(v) (stb__out ? (void)(*stb__out++ = (stb_uchar) (v)) : stb__write((stb_uchar) (v)))
+
+static void stb_out2(stb_uint v)
+{
+ stb_out(v >> 8);
+ stb_out(v);
+}
+
+static void stb_out3(stb_uint v) { stb_out(v >> 16); stb_out(v >> 8); stb_out(v); }
+static void stb_out4(stb_uint v) { stb_out(v >> 24); stb_out(v >> 16);
+ stb_out(v >> 8 ); stb_out(v); }
+
+static void outliterals(stb_uchar *in, ptrdiff_t numlit)
+{
+ while (numlit > 65536) {
+ outliterals(in,65536);
+ in += 65536;
+ numlit -= 65536;
+ }
+
+ if (numlit == 0) ;
+ else if (numlit <= 32) stb_out (0x000020 + (stb_uint) numlit-1);
+ else if (numlit <= 2048) stb_out2(0x000800 + (stb_uint) numlit-1);
+ else /* numlit <= 65536) */ stb_out3(0x070000 + (stb_uint) numlit-1);
+
+ if (stb__out) {
+ memcpy(stb__out,in,numlit);
+ stb__out += numlit;
+ } else
+ fwrite(in, 1, numlit, stb__outfile);
+}
+
+static int stb__window = 0x40000; // 256K
+void stb_compress_window(int z)
+{
+ if (z >= 0x1000000) z = 0x1000000; // limit of implementation
+ if (z < 0x100) z = 0x100; // insanely small
+ stb__window = z;
+}
+
+static int stb_not_crap(int best, int dist)
+{
+ return ((best > 2 && dist <= 0x00100)
+ || (best > 5 && dist <= 0x04000)
+ || (best > 7 && dist <= 0x80000));
+}
+
+static stb_uint stb__hashsize = 32768;
+void stb_compress_hashsize(unsigned int y)
+{
+ unsigned int z = 1024;
+ while (z < y) z <<= 1;
+ stb__hashsize = z >> 2; // pass in bytes, store #pointers
+}
+
+// note that you can play with the hashing functions all you
+// want without needing to change the decompressor
+#define stb__hc(q,h,c) (((h) << 7) + ((h) >> 25) + q[c])
+#define stb__hc2(q,h,c,d) (((h) << 14) + ((h) >> 18) + (q[c] << 7) + q[d])
+#define stb__hc3(q,c,d,e) ((q[c] << 14) + (q[d] << 7) + q[e])
+
+static stb_uint32 stb__running_adler;
+
+static int stb_compress_chunk(stb_uchar *history,
+ stb_uchar *start,
+ stb_uchar *end,
+ int length,
+ int *pending_literals,
+ stb_uchar **chash,
+ stb_uint mask)
+{
+ int window = stb__window;
+ stb_uint match_max;
+ stb_uchar *lit_start = start - *pending_literals;
+ stb_uchar *q = start;
+
+ #define STB__SCRAMBLE(h) (((h) + ((h) >> 16)) & mask)
+
+ // stop short of the end so we don't scan off the end doing
+ // the hashing; this means we won't compress the last few bytes
+ // unless they were part of something longer
+ while (q < start+length && q+12 < end) {
+ int m;
+ stb_uint h1,h2,h3,h4, h;
+ stb_uchar *t;
+ int best = 2, dist=0;
+
+ if (q+65536 > end)
+ match_max = (stb_uint) (end-q);
+ else
+ match_max = 65536u;
+
+ #define stb__nc(b,d) ((d) <= window && ((b) > 9 || stb_not_crap(b,d)))
+
+ #define STB__TRY(t,p) /* avoid retrying a match we already tried */ \
+ if (p ? dist != (int) (q-t) : 1) \
+ if ((m = (int) stb_matchlen(t, q, match_max)) > best)\
+ if (stb__nc(m,(int) (q-(t)))) \
+ best = m, dist = (int) (q - (t))
+
+ // rather than search for all matches, only try 4 candidate locations,
+ // chosen based on 4 different hash functions of different lengths.
+ // this strategy is inspired by LZO; hashing is unrolled here using the
+ // 'hc' macro
+ h = stb__hc3(q,0, 1, 2); h1 = STB__SCRAMBLE(h);
+ t = chash[h1]; if (t) STB__TRY(t,0);
+ h = stb__hc2(q,h, 3, 4); h2 = STB__SCRAMBLE(h);
+ h = stb__hc2(q,h, 5, 6); t = chash[h2]; if (t) STB__TRY(t,1);
+ h = stb__hc2(q,h, 7, 8); h3 = STB__SCRAMBLE(h);
+ h = stb__hc2(q,h, 9,10); t = chash[h3]; if (t) STB__TRY(t,1);
+ h = stb__hc2(q,h,11,12); h4 = STB__SCRAMBLE(h);
+ t = chash[h4]; if (t) STB__TRY(t,1);
+
+ // because we use a shared hash table, can only update it
+ // _after_ we've probed all of them
+ chash[h1] = chash[h2] = chash[h3] = chash[h4] = q;
+
+ if (best > 2)
+ assert(dist > 0);
+
+ // see if our best match qualifies
+ if (best < 3) { // fast path literals
+ ++q;
+ } else if (best > 2 && best <= 0x80 && dist <= 0x100) {
+ outliterals(lit_start, q-lit_start); lit_start = (q += best);
+ stb_out(0x80 + best-1);
+ stb_out(dist-1);
+ } else if (best > 5 && best <= 0x100 && dist <= 0x4000) {
+ outliterals(lit_start, q-lit_start); lit_start = (q += best);
+ stb_out2(0x4000 + dist-1);
+ stb_out(best-1);
+ } else if (best > 7 && best <= 0x100 && dist <= 0x80000) {
+ outliterals(lit_start, q-lit_start); lit_start = (q += best);
+ stb_out3(0x180000 + dist-1);
+ stb_out(best-1);
+ } else if (best > 8 && best <= 0x10000 && dist <= 0x80000) {
+ outliterals(lit_start, q-lit_start); lit_start = (q += best);
+ stb_out3(0x100000 + dist-1);
+ stb_out2(best-1);
+ } else if (best > 9 && dist <= 0x1000000) {
+ if (best > 65536) best = 65536;
+ outliterals(lit_start, q-lit_start); lit_start = (q += best);
+ if (best <= 0x100) {
+ stb_out(0x06);
+ stb_out3(dist-1);
+ stb_out(best-1);
+ } else {
+ stb_out(0x04);
+ stb_out3(dist-1);
+ stb_out2(best-1);
+ }
+ } else { // fallback literals if no match was a balanced tradeoff
+ ++q;
+ }
+ }
+
+ // if we didn't get all the way, add the rest to literals
+ if (q-start < length)
+ q = start+length;
+
+ // the literals are everything from lit_start to q
+ *pending_literals = (int) (q - lit_start);
+
+ stb__running_adler = stb_adler32(stb__running_adler, start, (int) (q - start));
+ return (int) (q - start);
+}
+
+static int stb_compress_inner(stb_uchar *input, stb_uint length)
+{
+ int literals = 0;
+ stb_uint len,i;
+
+ stb_uchar **chash;
+ chash = (stb_uchar**) malloc(stb__hashsize * sizeof(stb_uchar*));
+ if (chash == NULL) return 0; // failure
+ for (i=0; i < stb__hashsize; ++i)
+ chash[i] = NULL;
+
+ // stream signature
+ stb_out(0x57); stb_out(0xbc);
+ stb_out2(0);
+
+ stb_out4(0); // 64-bit length requires 32-bit leading 0
+ stb_out4(length);
+ stb_out4(stb__window);
+
+ stb__running_adler = 1;
+
+ len = stb_compress_chunk(input, input, input+length, length, &literals, chash, stb__hashsize-1);
+ assert(len == length);
+
+ outliterals(input+length - literals, literals);
+
+ free(chash);
+
+ stb_out2(0x05fa); // end opcode
+
+ stb_out4(stb__running_adler);
+
+ return 1; // success
+}
+
+stb_uint stb_compress(stb_uchar *out, stb_uchar *input, stb_uint length)
+{
+ stb__out = out;
+ stb__outfile = NULL;
+
+ stb_compress_inner(input, length);
+
+ return (stb_uint) (stb__out - out);
+}
+
+int stb_compress_tofile(char *filename, char *input, unsigned int length)
+{
+ //int maxlen = length + 512 + (length >> 2); // total guess
+ //char *buffer = (char *) malloc(maxlen);
+ //int blen = stb_compress((stb_uchar*)buffer, (stb_uchar*)input, length);
+
+ stb__out = NULL;
+ stb__outfile = stb_p_fopen(filename, "wb");
+ if (!stb__outfile) return 0;
+
+ stb__outbytes = 0;
+
+ if (!stb_compress_inner((stb_uchar*)input, length))
+ return 0;
+
+ fclose(stb__outfile);
+
+ return stb__outbytes;
+}
+
+int stb_compress_intofile(FILE *f, char *input, unsigned int length)
+{
+ //int maxlen = length + 512 + (length >> 2); // total guess
+ //char *buffer = (char*)malloc(maxlen);
+ //int blen = stb_compress((stb_uchar*)buffer, (stb_uchar*)input, length);
+
+ stb__out = NULL;
+ stb__outfile = f;
+ if (!stb__outfile) return 0;
+
+ stb__outbytes = 0;
+
+ if (!stb_compress_inner((stb_uchar*)input, length))
+ return 0;
+
+ return stb__outbytes;
+}
+
+////////////////////// streaming I/O version /////////////////////
+
+
+static size_t stb_out_backpatch_id(void)
+{
+ if (stb__out)
+ return (size_t) stb__out;
+ else
+ return ftell(stb__outfile);
+}
+
+static void stb_out_backpatch(size_t id, stb_uint value)
+{
+ stb_uchar data[4] = { (stb_uchar)(value >> 24), (stb_uchar)(value >> 16), (stb_uchar)(value >> 8), (stb_uchar)(value) };
+ if (stb__out) {
+ memcpy((void *) id, data, 4);
+ } else {
+ stb_uint where = ftell(stb__outfile);
+ fseek(stb__outfile, (long) id, SEEK_SET);
+ fwrite(data, 4, 1, stb__outfile);
+ fseek(stb__outfile, where, SEEK_SET);
+ }
+}
+
+// ok, the wraparound buffer was a total failure. let's instead
+// use a copying-in-place buffer, which lets us share the code.
+// This is way less efficient but it'll do for now.
+
+static struct
+{
+ stb_uchar *buffer;
+ int size; // physical size of buffer in bytes
+
+ int valid; // amount of valid data in bytes
+ int start; // bytes of data already output
+
+ int window;
+ int fsize;
+
+ int pending_literals; // bytes not-quite output but counted in start
+ int length_id;
+
+ stb_uint total_bytes;
+
+ stb_uchar **chash;
+ stb_uint hashmask;
+} xtb;
+
+static int stb_compress_streaming_start(void)
+{
+ stb_uint i;
+ xtb.size = stb__window * 3;
+ xtb.buffer = (stb_uchar*)malloc(xtb.size);
+ if (!xtb.buffer) return 0;
+
+ xtb.chash = (stb_uchar**)malloc(sizeof(*xtb.chash) * stb__hashsize);
+ if (!xtb.chash) {
+ free(xtb.buffer);
+ return 0;
+ }
+
+ for (i=0; i < stb__hashsize; ++i)
+ xtb.chash[i] = NULL;
+
+ xtb.hashmask = stb__hashsize-1;
+
+ xtb.valid = 0;
+ xtb.start = 0;
+ xtb.window = stb__window;
+ xtb.fsize = stb__window;
+ xtb.pending_literals = 0;
+ xtb.total_bytes = 0;
+
+ // stream signature
+ stb_out(0x57); stb_out(0xbc); stb_out2(0);
+
+ stb_out4(0); // 64-bit length requires 32-bit leading 0
+
+ xtb.length_id = (int) stb_out_backpatch_id();
+ stb_out4(0); // we don't know the output length yet
+
+ stb_out4(stb__window);
+
+ stb__running_adler = 1;
+
+ return 1;
+}
+
+static int stb_compress_streaming_end(void)
+{
+ // flush out any remaining data
+ stb_compress_chunk(xtb.buffer, xtb.buffer+xtb.start, xtb.buffer+xtb.valid,
+ xtb.valid-xtb.start, &xtb.pending_literals, xtb.chash, xtb.hashmask);
+
+ // write out pending literals
+ outliterals(xtb.buffer + xtb.valid - xtb.pending_literals, xtb.pending_literals);
+
+ stb_out2(0x05fa); // end opcode
+ stb_out4(stb__running_adler);
+
+ stb_out_backpatch(xtb.length_id, xtb.total_bytes);
+
+ free(xtb.buffer);
+ free(xtb.chash);
+ return 1;
+}
+
+void stb_write(char *data, int data_len)
+{
+ stb_uint i;
+
+ // @TODO: fast path for filling the buffer and doing nothing else
+ // if (xtb.valid + data_len < xtb.size)
+
+ xtb.total_bytes += data_len;
+
+ while (data_len) {
+ // fill buffer
+ if (xtb.valid < xtb.size) {
+ int amt = xtb.size - xtb.valid;
+ if (data_len < amt) amt = data_len;
+ memcpy(xtb.buffer + xtb.valid, data, amt);
+ data_len -= amt;
+ data += amt;
+ xtb.valid += amt;
+ }
+ if (xtb.valid < xtb.size)
+ return;
+
+ // at this point, the buffer is full
+
+ // if we can process some data, go for it; make sure
+ // we leave an 'fsize's worth of data, though
+ if (xtb.start + xtb.fsize < xtb.valid) {
+ int amount = (xtb.valid - xtb.fsize) - xtb.start;
+ int n;
+ assert(amount > 0);
+ n = stb_compress_chunk(xtb.buffer, xtb.buffer + xtb.start, xtb.buffer + xtb.valid,
+ amount, &xtb.pending_literals, xtb.chash, xtb.hashmask);
+ xtb.start += n;
+ }
+
+ assert(xtb.start + xtb.fsize >= xtb.valid);
+ // at this point, our future size is too small, so we
+ // need to flush some history. we, in fact, flush exactly
+ // one window's worth of history
+
+ {
+ int flush = xtb.window;
+ assert(xtb.start >= flush);
+ assert(xtb.valid >= flush);
+
+ // if 'pending literals' extends back into the shift region,
+ // write them out
+ if (xtb.start - xtb.pending_literals < flush) {
+ outliterals(xtb.buffer + xtb.start - xtb.pending_literals, xtb.pending_literals);
+ xtb.pending_literals = 0;
+ }
+
+ // now shift the window
+ memmove(xtb.buffer, xtb.buffer + flush, xtb.valid - flush);
+ xtb.start -= flush;
+ xtb.valid -= flush;
+
+ for (i=0; i <= xtb.hashmask; ++i)
+ if (xtb.chash[i] < xtb.buffer + flush)
+ xtb.chash[i] = NULL;
+ else
+ xtb.chash[i] -= flush;
+ }
+ // and now that we've made room for more data, go back to the top
+ }
+}
+
+int stb_compress_stream_start(FILE *f)
+{
+ stb__out = NULL;
+ stb__outfile = f;
+
+ if (f == NULL)
+ return 0;
+
+ if (!stb_compress_streaming_start())
+ return 0;
+
+ return 1;
+}
+
+void stb_compress_stream_end(int close)
+{
+ stb_compress_streaming_end();
+ if (close && stb__outfile) {
+ fclose(stb__outfile);
+ }
+}
+
+#endif // STB_DEFINE
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// File abstraction... tired of not having this... we can write
+// compressors to be layers over these that auto-close their children.
+
+
+typedef struct stbfile
+{
+ int (*getbyte)(struct stbfile *); // -1 on EOF
+ unsigned int (*getdata)(struct stbfile *, void *block, unsigned int len);
+
+ int (*putbyte)(struct stbfile *, int byte);
+ unsigned int (*putdata)(struct stbfile *, void *block, unsigned int len);
+
+ unsigned int (*size)(struct stbfile *);
+
+ unsigned int (*tell)(struct stbfile *);
+ void (*backpatch)(struct stbfile *, unsigned int tell, void *block, unsigned int len);
+
+ void (*close)(struct stbfile *);
+
+ FILE *f; // file to fread/fwrite
+ unsigned char *buffer; // input/output buffer
+ unsigned char *indata, *inend; // input buffer
+ union {
+ int various;
+ void *ptr;
+ };
+} stbfile;
+
+STB_EXTERN unsigned int stb_getc(stbfile *f); // read
+STB_EXTERN int stb_putc(stbfile *f, int ch); // write
+STB_EXTERN unsigned int stb_getdata(stbfile *f, void *buffer, unsigned int len); // read
+STB_EXTERN unsigned int stb_putdata(stbfile *f, void *buffer, unsigned int len); // write
+STB_EXTERN unsigned int stb_tell(stbfile *f); // read
+STB_EXTERN unsigned int stb_size(stbfile *f); // read/write
+STB_EXTERN void stb_backpatch(stbfile *f, unsigned int tell, void *buffer, unsigned int len); // write
+
+#ifdef STB_DEFINE
+
+unsigned int stb_getc(stbfile *f) { return f->getbyte(f); }
+int stb_putc(stbfile *f, int ch) { return f->putbyte(f, ch); }
+
+unsigned int stb_getdata(stbfile *f, void *buffer, unsigned int len)
+{
+ return f->getdata(f, buffer, len);
+}
+unsigned int stb_putdata(stbfile *f, void *buffer, unsigned int len)
+{
+ return f->putdata(f, buffer, len);
+}
+void stb_close(stbfile *f)
+{
+ f->close(f);
+ free(f);
+}
+unsigned int stb_tell(stbfile *f) { return f->tell(f); }
+unsigned int stb_size(stbfile *f) { return f->size(f); }
+void stb_backpatch(stbfile *f, unsigned int tell, void *buffer, unsigned int len)
+{
+ f->backpatch(f,tell,buffer,len);
+}
+
+// FILE * implementation
+static int stb__fgetbyte(stbfile *f) { return fgetc(f->f); }
+static int stb__fputbyte(stbfile *f, int ch) { return fputc(ch, f->f)==0; }
+static unsigned int stb__fgetdata(stbfile *f, void *buffer, unsigned int len) { return (unsigned int) fread(buffer,1,len,f->f); }
+static unsigned int stb__fputdata(stbfile *f, void *buffer, unsigned int len) { return (unsigned int) fwrite(buffer,1,len,f->f); }
+static unsigned int stb__fsize(stbfile *f) { return (unsigned int) stb_filelen(f->f); }
+static unsigned int stb__ftell(stbfile *f) { return (unsigned int) ftell(f->f); }
+static void stb__fbackpatch(stbfile *f, unsigned int where, void *buffer, unsigned int len)
+{
+ fseek(f->f, where, SEEK_SET);
+ fwrite(buffer, 1, len, f->f);
+ fseek(f->f, 0, SEEK_END);
+}
+static void stb__fclose(stbfile *f) { fclose(f->f); }
+
+stbfile *stb_openf(FILE *f)
+{
+ stbfile m = { stb__fgetbyte, stb__fgetdata,
+ stb__fputbyte, stb__fputdata,
+ stb__fsize, stb__ftell, stb__fbackpatch, stb__fclose,
+ 0,0,0, };
+ stbfile *z = (stbfile *) malloc(sizeof(*z));
+ if (z) {
+ *z = m;
+ z->f = f;
+ }
+ return z;
+}
+
+static int stb__nogetbyte(stbfile *f) { assert(0); return -1; }
+static unsigned int stb__nogetdata(stbfile *f, void *buffer, unsigned int len) { assert(0); return 0; }
+static int stb__noputbyte(stbfile *f, int ch) { assert(0); return 0; }
+static unsigned int stb__noputdata(stbfile *f, void *buffer, unsigned int len) { assert(0); return 0; }
+static void stb__nobackpatch(stbfile *f, unsigned int where, void *buffer, unsigned int len) { assert(0); }
+
+static int stb__bgetbyte(stbfile *s)
+{
+ if (s->indata < s->inend)
+ return *s->indata++;
+ else
+ return -1;
+}
+
+static unsigned int stb__bgetdata(stbfile *s, void *buffer, unsigned int len)
+{
+ if (s->indata + len > s->inend)
+ len = (unsigned int) (s->inend - s->indata);
+ memcpy(buffer, s->indata, len);
+ s->indata += len;
+ return len;
+}
+static unsigned int stb__bsize(stbfile *s) { return (unsigned int) (s->inend - s->buffer); }
+static unsigned int stb__btell(stbfile *s) { return (unsigned int) (s->indata - s->buffer); }
+
+static void stb__bclose(stbfile *s)
+{
+ if (s->various)
+ free(s->buffer);
+}
+
+stbfile *stb_open_inbuffer(void *buffer, unsigned int len)
+{
+ stbfile m = { stb__bgetbyte, stb__bgetdata,
+ stb__noputbyte, stb__noputdata,
+ stb__bsize, stb__btell, stb__nobackpatch, stb__bclose };
+ stbfile *z = (stbfile *) malloc(sizeof(*z));
+ if (z) {
+ *z = m;
+ z->buffer = (unsigned char *) buffer;
+ z->indata = z->buffer;
+ z->inend = z->indata + len;
+ }
+ return z;
+}
+
+stbfile *stb_open_inbuffer_free(void *buffer, unsigned int len)
+{
+ stbfile *z = stb_open_inbuffer(buffer, len);
+ if (z)
+ z->various = 1; // free
+ return z;
+}
+
+#ifndef STB_VERSION
+// if we've been cut-and-pasted elsewhere, you get a limited
+// version of stb_open, without the 'k' flag and utf8 support
+static void stb__fclose2(stbfile *f)
+{
+ fclose(f->f);
+}
+
+stbfile *stb_open(char *filename, char *mode)
+{
+ FILE *f = stb_p_fopen(filename, mode);
+ stbfile *s;
+ if (f == NULL) return NULL;
+ s = stb_openf(f);
+ if (s)
+ s->close = stb__fclose2;
+ return s;
+}
+#else
+// the full version depends on some code in stb.h; this
+// also includes the memory buffer output format implemented with stb_arr
+static void stb__fclose2(stbfile *f)
+{
+ stb_fclose(f->f, f->various);
+}
+
+stbfile *stb_open(char *filename, char *mode)
+{
+ FILE *f = stb_fopen(filename, mode[0] == 'k' ? mode+1 : mode);
+ stbfile *s;
+ if (f == NULL) return NULL;
+ s = stb_openf(f);
+ if (s) {
+ s->close = stb__fclose2;
+ s->various = mode[0] == 'k' ? stb_keep_if_different : stb_keep_yes;
+ }
+ return s;
+}
+
+static int stb__aputbyte(stbfile *f, int ch)
+{
+ stb_arr_push(f->buffer, ch);
+ return 1;
+}
+static unsigned int stb__aputdata(stbfile *f, void *data, unsigned int len)
+{
+ memcpy(stb_arr_addn(f->buffer, (int) len), data, len);
+ return len;
+}
+static unsigned int stb__asize(stbfile *f) { return stb_arr_len(f->buffer); }
+static void stb__abackpatch(stbfile *f, unsigned int where, void *data, unsigned int len)
+{
+ memcpy(f->buffer+where, data, len);
+}
+static void stb__aclose(stbfile *f)
+{
+ *(unsigned char **) f->ptr = f->buffer;
+}
+
+stbfile *stb_open_outbuffer(unsigned char **update_on_close)
+{
+ stbfile m = { stb__nogetbyte, stb__nogetdata,
+ stb__aputbyte, stb__aputdata,
+ stb__asize, stb__asize, stb__abackpatch, stb__aclose };
+ stbfile *z = (stbfile *) malloc(sizeof(*z));
+ if (z) {
+ z->ptr = update_on_close;
+ *z = m;
+ }
+ return z;
+}
+#endif
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Arithmetic coder... based on cbloom's notes on the subject, should be
+// less code than a huffman code.
+
+typedef struct
+{
+ unsigned int range_low;
+ unsigned int range_high;
+ unsigned int code, range; // decode
+ int buffered_u8;
+ int pending_ffs;
+ stbfile *output;
+} stb_arith;
+
+STB_EXTERN void stb_arith_init_encode(stb_arith *a, stbfile *out);
+STB_EXTERN void stb_arith_init_decode(stb_arith *a, stbfile *in);
+STB_EXTERN stbfile *stb_arith_encode_close(stb_arith *a);
+STB_EXTERN stbfile *stb_arith_decode_close(stb_arith *a);
+
+STB_EXTERN void stb_arith_encode(stb_arith *a, unsigned int totalfreq, unsigned int freq, unsigned int cumfreq);
+STB_EXTERN void stb_arith_encode_log2(stb_arith *a, unsigned int totalfreq2, unsigned int freq, unsigned int cumfreq);
+STB_EXTERN unsigned int stb_arith_decode_value(stb_arith *a, unsigned int totalfreq);
+STB_EXTERN void stb_arith_decode_advance(stb_arith *a, unsigned int totalfreq, unsigned int freq, unsigned int cumfreq);
+STB_EXTERN unsigned int stb_arith_decode_value_log2(stb_arith *a, unsigned int totalfreq2);
+STB_EXTERN void stb_arith_decode_advance_log2(stb_arith *a, unsigned int totalfreq2, unsigned int freq, unsigned int cumfreq);
+
+STB_EXTERN void stb_arith_encode_byte(stb_arith *a, int byte);
+STB_EXTERN int stb_arith_decode_byte(stb_arith *a);
+
+// this is a memory-inefficient way of doing things, but it's
+// fast(?) and simple
+typedef struct
+{
+ unsigned short cumfreq;
+ unsigned short samples;
+} stb_arith_symstate_item;
+
+typedef struct
+{
+ int num_sym;
+ unsigned int pow2;
+ int countdown;
+ stb_arith_symstate_item data[1];
+} stb_arith_symstate;
+
+#ifdef STB_DEFINE
+void stb_arith_init_encode(stb_arith *a, stbfile *out)
+{
+ a->range_low = 0;
+ a->range_high = 0xffffffff;
+ a->pending_ffs = -1; // means no buffered character currently, to speed up normal case
+ a->output = out;
+}
+
+static void stb__arith_carry(stb_arith *a)
+{
+ int i;
+ assert(a->pending_ffs != -1); // can't carry with no data
+ stb_putc(a->output, a->buffered_u8);
+ for (i=0; i < a->pending_ffs; ++i)
+ stb_putc(a->output, 0);
+}
+
+static void stb__arith_putbyte(stb_arith *a, int byte)
+{
+ if (a->pending_ffs) {
+ if (a->pending_ffs == -1) { // means no buffered data; encoded for fast path efficiency
+ if (byte == 0xff)
+ stb_putc(a->output, byte); // just write it immediately
+ else {
+ a->buffered_u8 = byte;
+ a->pending_ffs = 0;
+ }
+ } else if (byte == 0xff) {
+ ++a->pending_ffs;
+ } else {
+ int i;
+ stb_putc(a->output, a->buffered_u8);
+ for (i=0; i < a->pending_ffs; ++i)
+ stb_putc(a->output, 0xff);
+ }
+ } else if (byte == 0xff) {
+ ++a->pending_ffs;
+ } else {
+ // fast path
+ stb_putc(a->output, a->buffered_u8);
+ a->buffered_u8 = byte;
+ }
+}
+
+static void stb__arith_flush(stb_arith *a)
+{
+ if (a->pending_ffs >= 0) {
+ int i;
+ stb_putc(a->output, a->buffered_u8);
+ for (i=0; i < a->pending_ffs; ++i)
+ stb_putc(a->output, 0xff);
+ }
+}
+
+static void stb__renorm_encoder(stb_arith *a)
+{
+ stb__arith_putbyte(a, a->range_low >> 24);
+ a->range_low <<= 8;
+ a->range_high = (a->range_high << 8) | 0xff;
+}
+
+static void stb__renorm_decoder(stb_arith *a)
+{
+ int c = stb_getc(a->output);
+ a->code = (a->code << 8) + (c >= 0 ? c : 0); // if EOF, insert 0
+}
+
+void stb_arith_encode(stb_arith *a, unsigned int totalfreq, unsigned int freq, unsigned int cumfreq)
+{
+ unsigned int range = a->range_high - a->range_low;
+ unsigned int old = a->range_low;
+ range /= totalfreq;
+ a->range_low += range * cumfreq;
+ a->range_high = a->range_low + range*freq;
+ if (a->range_low < old)
+ stb__arith_carry(a);
+ while (a->range_high - a->range_low < 0x1000000)
+ stb__renorm_encoder(a);
+}
+
+void stb_arith_encode_log2(stb_arith *a, unsigned int totalfreq2, unsigned int freq, unsigned int cumfreq)
+{
+ unsigned int range = a->range_high - a->range_low;
+ unsigned int old = a->range_low;
+ range >>= totalfreq2;
+ a->range_low += range * cumfreq;
+ a->range_high = a->range_low + range*freq;
+ if (a->range_low < old)
+ stb__arith_carry(a);
+ while (a->range_high - a->range_low < 0x1000000)
+ stb__renorm_encoder(a);
+}
+
+unsigned int stb_arith_decode_value(stb_arith *a, unsigned int totalfreq)
+{
+ unsigned int freqsize = a->range / totalfreq;
+ unsigned int z = a->code / freqsize;
+ return z >= totalfreq ? totalfreq-1 : z;
+}
+
+void stb_arith_decode_advance(stb_arith *a, unsigned int totalfreq, unsigned int freq, unsigned int cumfreq)
+{
+ unsigned int freqsize = a->range / totalfreq; // @OPTIMIZE, share with above divide somehow?
+ a->code -= freqsize * cumfreq;
+ a->range = freqsize * freq;
+ while (a->range < 0x1000000)
+ stb__renorm_decoder(a);
+}
+
+unsigned int stb_arith_decode_value_log2(stb_arith *a, unsigned int totalfreq2)
+{
+ unsigned int freqsize = a->range >> totalfreq2;
+ unsigned int z = a->code / freqsize;
+ return z >= (1U<range >> totalfreq2;
+ a->code -= freqsize * cumfreq;
+ a->range = freqsize * freq;
+ while (a->range < 0x1000000)
+ stb__renorm_decoder(a);
+}
+
+stbfile *stb_arith_encode_close(stb_arith *a)
+{
+ // put exactly as many bytes as we'll read, so we can turn on/off arithmetic coding in a stream
+ stb__arith_putbyte(a, a->range_low >> 24);
+ stb__arith_putbyte(a, a->range_low >> 16);
+ stb__arith_putbyte(a, a->range_low >> 8);
+ stb__arith_putbyte(a, a->range_low >> 0);
+ stb__arith_flush(a);
+ return a->output;
+}
+
+stbfile *stb_arith_decode_close(stb_arith *a)
+{
+ return a->output;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Threads
+//
+
+#ifndef _WIN32
+#ifdef STB_THREADS
+#error "threads not implemented except for Windows"
+#endif
+#endif
+
+// call this function to free any global variables for memory testing
+STB_EXTERN void stb_thread_cleanup(void);
+
+typedef void * (*stb_thread_func)(void *);
+
+// do not rely on these types, this is an implementation detail.
+// compare against STB_THREAD_NULL and ST_SEMAPHORE_NULL
+typedef void *stb_thread;
+typedef void *stb_semaphore;
+typedef void *stb_mutex;
+typedef struct stb__sync *stb_sync;
+
+#define STB_SEMAPHORE_NULL NULL
+#define STB_THREAD_NULL NULL
+#define STB_MUTEX_NULL NULL
+#define STB_SYNC_NULL NULL
+
+// get the number of processors (limited to those in the affinity mask for this process).
+STB_EXTERN int stb_processor_count(void);
+// force to run on a single core -- needed for RDTSC to work, e.g. for iprof
+STB_EXTERN void stb_force_uniprocessor(void);
+
+// stb_work functions: queue up work to be done by some worker threads
+
+// set number of threads to serve the queue; you can change this on the fly,
+// but if you decrease it, it won't decrease until things currently on the
+// queue are finished
+STB_EXTERN void stb_work_numthreads(int n);
+// set maximum number of units in the queue; you can only set this BEFORE running any work functions
+STB_EXTERN int stb_work_maxunits(int n);
+// enqueue some work to be done (can do this from any thread, or even from a piece of work);
+// return value of f is stored in *return_code if non-NULL
+STB_EXTERN int stb_work(stb_thread_func f, void *d, volatile void **return_code);
+// as above, but stb_sync_reach is called on 'rel' after work is complete
+STB_EXTERN int stb_work_reach(stb_thread_func f, void *d, volatile void **return_code, stb_sync rel);
+
+
+// necessary to call this when using volatile to order writes/reads
+STB_EXTERN void stb_barrier(void);
+
+// support for independent queues with their own threads
+
+typedef struct stb__workqueue stb_workqueue;
+
+STB_EXTERN stb_workqueue*stb_workq_new(int numthreads, int max_units);
+STB_EXTERN stb_workqueue*stb_workq_new_flags(int numthreads, int max_units, int no_add_mutex, int no_remove_mutex);
+STB_EXTERN void stb_workq_delete(stb_workqueue *q);
+STB_EXTERN void stb_workq_numthreads(stb_workqueue *q, int n);
+STB_EXTERN int stb_workq(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code);
+STB_EXTERN int stb_workq_reach(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel);
+STB_EXTERN int stb_workq_length(stb_workqueue *q);
+
+STB_EXTERN stb_thread stb_create_thread (stb_thread_func f, void *d);
+STB_EXTERN stb_thread stb_create_thread2(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel);
+STB_EXTERN void stb_destroy_thread(stb_thread t);
+
+STB_EXTERN stb_semaphore stb_sem_new(int max_val);
+STB_EXTERN stb_semaphore stb_sem_new_extra(int max_val, int start_val);
+STB_EXTERN void stb_sem_delete (stb_semaphore s);
+STB_EXTERN void stb_sem_waitfor(stb_semaphore s);
+STB_EXTERN void stb_sem_release(stb_semaphore s);
+
+STB_EXTERN stb_mutex stb_mutex_new(void);
+STB_EXTERN void stb_mutex_delete(stb_mutex m);
+STB_EXTERN void stb_mutex_begin(stb_mutex m);
+STB_EXTERN void stb_mutex_end(stb_mutex m);
+
+STB_EXTERN stb_sync stb_sync_new(void);
+STB_EXTERN void stb_sync_delete(stb_sync s);
+STB_EXTERN int stb_sync_set_target(stb_sync s, int count);
+STB_EXTERN void stb_sync_reach_and_wait(stb_sync s); // wait for 'target' reachers
+STB_EXTERN int stb_sync_reach(stb_sync s);
+
+typedef struct stb__threadqueue stb_threadqueue;
+#define STB_THREADQ_DYNAMIC 0
+STB_EXTERN stb_threadqueue *stb_threadq_new(int item_size, int num_items, int many_add, int many_remove);
+STB_EXTERN void stb_threadq_delete(stb_threadqueue *tq);
+STB_EXTERN int stb_threadq_get(stb_threadqueue *tq, void *output);
+STB_EXTERN void stb_threadq_get_block(stb_threadqueue *tq, void *output);
+STB_EXTERN int stb_threadq_add(stb_threadqueue *tq, void *input);
+// can return FALSE if STB_THREADQ_DYNAMIC and attempt to grow fails
+STB_EXTERN int stb_threadq_add_block(stb_threadqueue *tq, void *input);
+
+#ifdef STB_THREADS
+#ifdef STB_DEFINE
+
+typedef struct
+{
+ stb_thread_func f;
+ void *d;
+ volatile void **return_val;
+ stb_semaphore sem;
+} stb__thread;
+
+// this is initialized along all possible paths to create threads, therefore
+// it's always initialized before any other threads are create, therefore
+// it's free of races AS LONG AS you only create threads through stb_*
+static stb_mutex stb__threadmutex, stb__workmutex;
+
+static void stb__threadmutex_init(void)
+{
+ if (stb__threadmutex == STB_SEMAPHORE_NULL) {
+ stb__threadmutex = stb_mutex_new();
+ stb__workmutex = stb_mutex_new();
+ }
+}
+
+#ifdef STB_THREAD_TEST
+volatile float stb__t1=1, stb__t2;
+
+static void stb__wait(int n)
+{
+ float z = 0;
+ int i;
+ for (i=0; i < n; ++i)
+ z += 1 / (stb__t1+i);
+ stb__t2 = z;
+}
+#else
+#define stb__wait(x)
+#endif
+
+#ifdef _WIN32
+
+// avoid including windows.h -- note that our definitions aren't
+// exactly the same (we don't define the security descriptor struct)
+// so if you want to include windows.h, make sure you do it first.
+#include
+
+#ifndef _WINDOWS_ // check windows.h guard
+#define STB__IMPORT STB_EXTERN __declspec(dllimport)
+#define STB__DW unsigned long
+
+STB__IMPORT int __stdcall TerminateThread(void *, STB__DW);
+STB__IMPORT void * __stdcall CreateSemaphoreA(void *sec, long,long,char*);
+STB__IMPORT int __stdcall CloseHandle(void *);
+STB__IMPORT STB__DW __stdcall WaitForSingleObject(void *, STB__DW);
+STB__IMPORT int __stdcall ReleaseSemaphore(void *, long, long *);
+STB__IMPORT void __stdcall Sleep(STB__DW);
+#endif
+
+// necessary to call this when using volatile to order writes/reads
+void stb_barrier(void)
+{
+ #ifdef MemoryBarrier
+ MemoryBarrier();
+ #else
+ long temp;
+ __asm xchg temp,eax;
+ #endif
+}
+
+static void stb__thread_run(void *t)
+{
+ void *res;
+ stb__thread info = * (stb__thread *) t;
+ free(t);
+ res = info.f(info.d);
+ if (info.return_val)
+ *info.return_val = res;
+ if (info.sem != STB_SEMAPHORE_NULL)
+ stb_sem_release(info.sem);
+}
+
+static stb_thread stb_create_thread_raw(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel)
+{
+#ifdef _MT
+#if defined(STB_FASTMALLOC) && !defined(STB_FASTMALLOC_ITS_OKAY_I_ONLY_MALLOC_IN_ONE_THREAD)
+ stb_fatal("Error! Cannot use STB_FASTMALLOC with threads.\n");
+ return STB_THREAD_NULL;
+#else
+ unsigned long id;
+ stb__thread *data = (stb__thread *) malloc(sizeof(*data));
+ if (!data) return NULL;
+ stb__threadmutex_init();
+ data->f = f;
+ data->d = d;
+ data->return_val = return_code;
+ data->sem = rel;
+ id = _beginthread(stb__thread_run, 0, data);
+ if (id == -1) return NULL;
+ return (void *) id;
+#endif
+#else
+#ifdef STB_NO_STB_STRINGS
+ stb_fatal("Invalid compilation");
+#else
+ stb_fatal("Must compile mult-threaded to use stb_thread/stb_work.");
+#endif
+ return NULL;
+#endif
+}
+
+// trivial win32 wrappers
+void stb_destroy_thread(stb_thread t) { TerminateThread(t,0); }
+stb_semaphore stb_sem_new(int maxv) {return CreateSemaphoreA(NULL,0,maxv,NULL); }
+stb_semaphore stb_sem_new_extra(int maxv,int start){return CreateSemaphoreA(NULL,start,maxv,NULL); }
+void stb_sem_delete(stb_semaphore s) { if (s != NULL) CloseHandle(s); }
+void stb_sem_waitfor(stb_semaphore s) { WaitForSingleObject(s, 0xffffffff); } // INFINITE
+void stb_sem_release(stb_semaphore s) { ReleaseSemaphore(s,1,NULL); }
+static void stb__thread_sleep(int ms) { Sleep(ms); }
+
+#ifndef _WINDOWS_
+STB__IMPORT int __stdcall GetProcessAffinityMask(void *, STB__DW *, STB__DW *);
+STB__IMPORT void * __stdcall GetCurrentProcess(void);
+STB__IMPORT int __stdcall SetProcessAffinityMask(void *, STB__DW);
+#endif
+
+int stb_processor_count(void)
+{
+ unsigned long proc,sys;
+ GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys);
+ return stb_bitcount(proc);
+}
+
+void stb_force_uniprocessor(void)
+{
+ unsigned long proc,sys;
+ GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys);
+ if (stb_bitcount(proc) > 1) {
+ int z;
+ for (z=0; z < 32; ++z)
+ if (proc & (1 << z))
+ break;
+ if (z < 32) {
+ proc = 1 << z;
+ SetProcessAffinityMask(GetCurrentProcess(), proc);
+ }
+ }
+}
+
+#ifdef _WINDOWS_
+#define STB_MUTEX_NATIVE
+void *stb_mutex_new(void)
+{
+ CRITICAL_SECTION *p = (CRITICAL_SECTION *) malloc(sizeof(*p));
+ if (p)
+#if _WIN32_WINNT >= 0x0500
+ InitializeCriticalSectionAndSpinCount(p, 500);
+#else
+ InitializeCriticalSection(p);
+#endif
+ return p;
+}
+
+void stb_mutex_delete(void *p)
+{
+ if (p) {
+ DeleteCriticalSection((CRITICAL_SECTION *) p);
+ free(p);
+ }
+}
+
+void stb_mutex_begin(void *p)
+{
+ stb__wait(500);
+ if (p)
+ EnterCriticalSection((CRITICAL_SECTION *) p);
+}
+
+void stb_mutex_end(void *p)
+{
+ if (p)
+ LeaveCriticalSection((CRITICAL_SECTION *) p);
+ stb__wait(500);
+}
+#endif // _WINDOWS_
+
+#if 0
+// for future reference,
+// InterlockedCompareExchange for x86:
+ int cas64_mp(void * dest, void * xcmp, void * xxchg) {
+ __asm
+ {
+ mov esi, [xxchg] ; exchange
+ mov ebx, [esi + 0]
+ mov ecx, [esi + 4]
+
+ mov esi, [xcmp] ; comparand
+ mov eax, [esi + 0]
+ mov edx, [esi + 4]
+
+ mov edi, [dest] ; destination
+ lock cmpxchg8b [edi]
+ jz yyyy;
+
+ mov [esi + 0], eax;
+ mov [esi + 4], edx;
+
+yyyy:
+ xor eax, eax;
+ setz al;
+ };
+
+inline unsigned __int64 _InterlockedCompareExchange64(volatile unsigned __int64 *dest
+ ,unsigned __int64 exchange
+ ,unsigned __int64 comperand)
+{
+ //value returned in eax::edx
+ __asm {
+ lea esi,comperand;
+ lea edi,exchange;
+
+ mov eax,[esi];
+ mov edx,4[esi];
+ mov ebx,[edi];
+ mov ecx,4[edi];
+ mov esi,dest;
+ lock CMPXCHG8B [esi];
+ }
+#endif // #if 0
+
+#endif // _WIN32
+
+stb_thread stb_create_thread2(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel)
+{
+ return stb_create_thread_raw(f,d,return_code,rel);
+}
+
+stb_thread stb_create_thread(stb_thread_func f, void *d)
+{
+ return stb_create_thread2(f,d,NULL,STB_SEMAPHORE_NULL);
+}
+
+// mutex implemented by wrapping semaphore
+#ifndef STB_MUTEX_NATIVE
+stb_mutex stb_mutex_new(void) { return stb_sem_new_extra(1,1); }
+void stb_mutex_delete(stb_mutex m) { stb_sem_delete (m); }
+void stb_mutex_begin(stb_mutex m) { stb__wait(500); if (m) stb_sem_waitfor(m); }
+void stb_mutex_end(stb_mutex m) { if (m) stb_sem_release(m); stb__wait(500); }
+#endif
+
+// thread merge operation
+struct stb__sync
+{
+ int target; // target number of threads to hit it
+ int sofar; // total threads that hit it
+ int waiting; // total threads waiting
+
+ stb_mutex start; // mutex to prevent starting again before finishing previous
+ stb_mutex mutex; // mutex while tweaking state
+ stb_semaphore release; // semaphore wake up waiting threads
+ // we have to wake them up one at a time, rather than using a single release
+ // call, because win32 semaphores don't let you dynamically change the max count!
+};
+
+stb_sync stb_sync_new(void)
+{
+ stb_sync s = (stb_sync) malloc(sizeof(*s));
+ if (!s) return s;
+
+ s->target = s->sofar = s->waiting = 0;
+ s->mutex = stb_mutex_new();
+ s->start = stb_mutex_new();
+ s->release = stb_sem_new(1);
+ if (s->mutex == STB_MUTEX_NULL || s->release == STB_SEMAPHORE_NULL || s->start == STB_MUTEX_NULL) {
+ stb_mutex_delete(s->mutex);
+ stb_mutex_delete(s->mutex);
+ stb_sem_delete(s->release);
+ free(s);
+ return NULL;
+ }
+ return s;
+}
+
+void stb_sync_delete(stb_sync s)
+{
+ if (s->waiting) {
+ // it's bad to delete while there are threads waiting!
+ // shall we wait for them to reach, or just bail? just bail
+ assert(0);
+ }
+ stb_mutex_delete(s->mutex);
+ stb_mutex_delete(s->release);
+ free(s);
+}
+
+int stb_sync_set_target(stb_sync s, int count)
+{
+ // don't allow setting a target until the last one is fully released;
+ // note that this can lead to inefficient pipelining, and maybe we'd
+ // be better off ping-ponging between two internal syncs?
+ // I tried seeing how often this happened using TryEnterCriticalSection
+ // and could _never_ get it to happen in imv(stb), even with more threads
+ // than processors. So who knows!
+ stb_mutex_begin(s->start);
+
+ // this mutex is pointless, since it's not valid for threads
+ // to call reach() before anyone calls set_target() anyway
+ stb_mutex_begin(s->mutex);
+
+ assert(s->target == 0); // enforced by start mutex
+ s->target = count;
+ s->sofar = 0;
+ s->waiting = 0;
+ stb_mutex_end(s->mutex);
+ return STB_TRUE;
+}
+
+void stb__sync_release(stb_sync s)
+{
+ if (s->waiting)
+ stb_sem_release(s->release);
+ else {
+ s->target = 0;
+ stb_mutex_end(s->start);
+ }
+}
+
+int stb_sync_reach(stb_sync s)
+{
+ int n;
+ stb_mutex_begin(s->mutex);
+ assert(s->sofar < s->target);
+ n = ++s->sofar; // record this value to avoid possible race if we did 'return s->sofar';
+ if (s->sofar == s->target)
+ stb__sync_release(s);
+ stb_mutex_end(s->mutex);
+ return n;
+}
+
+void stb_sync_reach_and_wait(stb_sync s)
+{
+ stb_mutex_begin(s->mutex);
+ assert(s->sofar < s->target);
+ ++s->sofar;
+ if (s->sofar == s->target) {
+ stb__sync_release(s);
+ stb_mutex_end(s->mutex);
+ } else {
+ ++s->waiting; // we're waiting, so one more waiter
+ stb_mutex_end(s->mutex); // release the mutex to other threads
+
+ stb_sem_waitfor(s->release); // wait for merge completion
+
+ stb_mutex_begin(s->mutex); // on merge completion, grab the mutex
+ --s->waiting; // we're done waiting
+ stb__sync_release(s); // restart the next waiter
+ stb_mutex_end(s->mutex); // and now we're done
+ // this ends the same as the first case, but it's a lot
+ // clearer to understand without sharing the code
+ }
+}
+
+struct stb__threadqueue
+{
+ stb_mutex add, remove;
+ stb_semaphore nonempty, nonfull;
+ int head_blockers; // number of threads blocking--used to know whether to release(avail)
+ int tail_blockers;
+ int head, tail, array_size, growable;
+ int item_size;
+ char *data;
+};
+
+static int stb__tq_wrap(volatile stb_threadqueue *z, int p)
+{
+ if (p == z->array_size)
+ return p - z->array_size;
+ else
+ return p;
+}
+
+int stb__threadq_get_raw(stb_threadqueue *tq2, void *output, int block)
+{
+ volatile stb_threadqueue *tq = (volatile stb_threadqueue *) tq2;
+ if (tq->head == tq->tail && !block) return 0;
+
+ stb_mutex_begin(tq->remove);
+
+ while (tq->head == tq->tail) {
+ if (!block) {
+ stb_mutex_end(tq->remove);
+ return 0;
+ }
+ ++tq->head_blockers;
+ stb_mutex_end(tq->remove);
+
+ stb_sem_waitfor(tq->nonempty);
+
+ stb_mutex_begin(tq->remove);
+ --tq->head_blockers;
+ }
+
+ memcpy(output, tq->data + tq->head*tq->item_size, tq->item_size);
+ stb_barrier();
+ tq->head = stb__tq_wrap(tq, tq->head+1);
+
+ stb_sem_release(tq->nonfull);
+ if (tq->head_blockers) // can't check if actually non-empty due to race?
+ stb_sem_release(tq->nonempty); // if there are other blockers, wake one
+
+ stb_mutex_end(tq->remove);
+ return STB_TRUE;
+}
+
+int stb__threadq_grow(volatile stb_threadqueue *tq)
+{
+ int n;
+ char *p;
+ assert(tq->remove != STB_MUTEX_NULL); // must have this to allow growth!
+ stb_mutex_begin(tq->remove);
+
+ n = tq->array_size * 2;
+ p = (char *) realloc(tq->data, n * tq->item_size);
+ if (p == NULL) {
+ stb_mutex_end(tq->remove);
+ stb_mutex_end(tq->add);
+ return STB_FALSE;
+ }
+ if (tq->tail < tq->head) {
+ memcpy(p + tq->array_size * tq->item_size, p, tq->tail * tq->item_size);
+ tq->tail += tq->array_size;
+ }
+ tq->data = p;
+ tq->array_size = n;
+
+ stb_mutex_end(tq->remove);
+ return STB_TRUE;
+}
+
+int stb__threadq_add_raw(stb_threadqueue *tq2, void *input, int block)
+{
+ int tail,pos;
+ volatile stb_threadqueue *tq = (volatile stb_threadqueue *) tq2;
+ stb_mutex_begin(tq->add);
+ for(;;) {
+ pos = tq->tail;
+ tail = stb__tq_wrap(tq, pos+1);
+ if (tail != tq->head) break;
+
+ // full
+ if (tq->growable) {
+ if (!stb__threadq_grow(tq)) {
+ stb_mutex_end(tq->add);
+ return STB_FALSE; // out of memory
+ }
+ } else if (!block) {
+ stb_mutex_end(tq->add);
+ return STB_FALSE;
+ } else {
+ ++tq->tail_blockers;
+ stb_mutex_end(tq->add);
+
+ stb_sem_waitfor(tq->nonfull);
+
+ stb_mutex_begin(tq->add);
+ --tq->tail_blockers;
+ }
+ }
+ memcpy(tq->data + tq->item_size * pos, input, tq->item_size);
+ stb_barrier();
+ tq->tail = tail;
+ stb_sem_release(tq->nonempty);
+ if (tq->tail_blockers) // can't check if actually non-full due to race?
+ stb_sem_release(tq->nonfull);
+ stb_mutex_end(tq->add);
+ return STB_TRUE;
+}
+
+int stb_threadq_length(stb_threadqueue *tq2)
+{
+ int a,b,n;
+ volatile stb_threadqueue *tq = (volatile stb_threadqueue *) tq2;
+ stb_mutex_begin(tq->add);
+ a = tq->head;
+ b = tq->tail;
+ n = tq->array_size;
+ stb_mutex_end(tq->add);
+ if (a > b) b += n;
+ return b-a;
+}
+
+int stb_threadq_get(stb_threadqueue *tq, void *output)
+{
+ return stb__threadq_get_raw(tq, output, STB_FALSE);
+}
+
+void stb_threadq_get_block(stb_threadqueue *tq, void *output)
+{
+ stb__threadq_get_raw(tq, output, STB_TRUE);
+}
+
+int stb_threadq_add(stb_threadqueue *tq, void *input)
+{
+ return stb__threadq_add_raw(tq, input, STB_FALSE);
+}
+
+int stb_threadq_add_block(stb_threadqueue *tq, void *input)
+{
+ return stb__threadq_add_raw(tq, input, STB_TRUE);
+}
+
+void stb_threadq_delete(stb_threadqueue *tq)
+{
+ if (tq) {
+ free(tq->data);
+ stb_mutex_delete(tq->add);
+ stb_mutex_delete(tq->remove);
+ stb_sem_delete(tq->nonempty);
+ stb_sem_delete(tq->nonfull);
+ free(tq);
+ }
+}
+
+#define STB_THREADQUEUE_DYNAMIC 0
+stb_threadqueue *stb_threadq_new(int item_size, int num_items, int many_add, int many_remove)
+{
+ int error=0;
+ stb_threadqueue *tq = (stb_threadqueue *) malloc(sizeof(*tq));
+ if (tq == NULL) return NULL;
+
+ if (num_items == STB_THREADQUEUE_DYNAMIC) {
+ tq->growable = STB_TRUE;
+ num_items = 32;
+ } else
+ tq->growable = STB_FALSE;
+
+ tq->item_size = item_size;
+ tq->array_size = num_items+1;
+
+ tq->add = tq->remove = STB_MUTEX_NULL;
+ tq->nonempty = tq->nonfull = STB_SEMAPHORE_NULL;
+ tq->data = NULL;
+ if (many_add)
+ { tq->add = stb_mutex_new(); if (tq->add == STB_MUTEX_NULL) goto error; }
+ if (many_remove || tq->growable)
+ { tq->remove = stb_mutex_new(); if (tq->remove == STB_MUTEX_NULL) goto error; }
+ tq->nonempty = stb_sem_new(1); if (tq->nonempty == STB_SEMAPHORE_NULL) goto error;
+ tq->nonfull = stb_sem_new(1); if (tq->nonfull == STB_SEMAPHORE_NULL) goto error;
+ tq->data = (char *) malloc(tq->item_size * tq->array_size);
+ if (tq->data == NULL) goto error;
+
+ tq->head = tq->tail = 0;
+ tq->head_blockers = tq->tail_blockers = 0;
+
+ return tq;
+
+error:
+ stb_threadq_delete(tq);
+ return NULL;
+}
+
+typedef struct
+{
+ stb_thread_func f;
+ void *d;
+ volatile void **retval;
+ stb_sync sync;
+} stb__workinfo;
+
+//static volatile stb__workinfo *stb__work;
+
+struct stb__workqueue
+{
+ int numthreads;
+ stb_threadqueue *tq;
+};
+
+static stb_workqueue *stb__work_global;
+
+static void *stb__thread_workloop(void *p)
+{
+ volatile stb_workqueue *q = (volatile stb_workqueue *) p;
+ for(;;) {
+ void *z;
+ stb__workinfo w;
+ stb_threadq_get_block(q->tq, &w);
+ if (w.f == NULL) // null work is a signal to end the thread
+ return NULL;
+ z = w.f(w.d);
+ if (w.retval) { stb_barrier(); *w.retval = z; }
+ if (w.sync != STB_SYNC_NULL) stb_sync_reach(w.sync);
+ }
+}
+
+stb_workqueue *stb_workq_new(int num_threads, int max_units)
+{
+ return stb_workq_new_flags(num_threads, max_units, 0,0);
+}
+
+stb_workqueue *stb_workq_new_flags(int numthreads, int max_units, int no_add_mutex, int no_remove_mutex)
+{
+ stb_workqueue *q = (stb_workqueue *) malloc(sizeof(*q));
+ if (q == NULL) return NULL;
+ q->tq = stb_threadq_new(sizeof(stb__workinfo), max_units, !no_add_mutex, !no_remove_mutex);
+ if (q->tq == NULL) { free(q); return NULL; }
+ q->numthreads = 0;
+ stb_workq_numthreads(q, numthreads);
+ return q;
+}
+
+void stb_workq_delete(stb_workqueue *q)
+{
+ while (stb_workq_length(q) != 0)
+ stb__thread_sleep(1);
+ stb_threadq_delete(q->tq);
+ free(q);
+}
+
+static int stb__work_maxitems = STB_THREADQUEUE_DYNAMIC;
+
+static void stb_work_init(int num_threads)
+{
+ if (stb__work_global == NULL) {
+ stb__threadmutex_init();
+ stb_mutex_begin(stb__workmutex);
+ stb_barrier();
+ if (*(stb_workqueue * volatile *) &stb__work_global == NULL)
+ stb__work_global = stb_workq_new(num_threads, stb__work_maxitems);
+ stb_mutex_end(stb__workmutex);
+ }
+}
+
+static int stb__work_raw(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel)
+{
+ stb__workinfo w;
+ if (q == NULL) {
+ stb_work_init(1);
+ q = stb__work_global;
+ }
+ w.f = f;
+ w.d = d;
+ w.retval = return_code;
+ w.sync = rel;
+ return stb_threadq_add(q->tq, &w);
+}
+
+int stb_workq_length(stb_workqueue *q)
+{
+ return stb_threadq_length(q->tq);
+}
+
+int stb_workq(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code)
+{
+ if (f == NULL) return 0;
+ return stb_workq_reach(q, f, d, return_code, NULL);
+}
+
+int stb_workq_reach(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel)
+{
+ if (f == NULL) return 0;
+ return stb__work_raw(q, f, d, return_code, rel);
+}
+
+static void stb__workq_numthreads(stb_workqueue *q, int n)
+{
+ while (q->numthreads < n) {
+ stb_create_thread(stb__thread_workloop, q);
+ ++q->numthreads;
+ }
+ while (q->numthreads > n) {
+ stb__work_raw(q, NULL, NULL, NULL, NULL);
+ --q->numthreads;
+ }
+}
+
+void stb_workq_numthreads(stb_workqueue *q, int n)
+{
+ stb_mutex_begin(stb__threadmutex);
+ stb__workq_numthreads(q,n);
+ stb_mutex_end(stb__threadmutex);
+}
+
+int stb_work_maxunits(int n)
+{
+ if (stb__work_global == NULL) {
+ stb__work_maxitems = n;
+ stb_work_init(1);
+ }
+ return stb__work_maxitems;
+}
+
+int stb_work(stb_thread_func f, void *d, volatile void **return_code)
+{
+ return stb_workq(stb__work_global, f,d,return_code);
+}
+
+int stb_work_reach(stb_thread_func f, void *d, volatile void **return_code, stb_sync rel)
+{
+ return stb_workq_reach(stb__work_global, f,d,return_code,rel);
+}
+
+void stb_work_numthreads(int n)
+{
+ if (stb__work_global == NULL)
+ stb_work_init(n);
+ else
+ stb_workq_numthreads(stb__work_global, n);
+}
+#endif // STB_DEFINE
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Background disk I/O
+//
+//
+
+#define STB_BGIO_READ_ALL (-1)
+STB_EXTERN int stb_bgio_read (char *filename, int offset, int len, stb_uchar **result, int *olen);
+STB_EXTERN int stb_bgio_readf (FILE *f , int offset, int len, stb_uchar **result, int *olen);
+STB_EXTERN int stb_bgio_read_to (char *filename, int offset, int len, stb_uchar *buffer, int *olen);
+STB_EXTERN int stb_bgio_readf_to(FILE *f , int offset, int len, stb_uchar *buffer, int *olen);
+
+typedef struct
+{
+ int have_data;
+ int is_valid;
+ int is_dir;
+ time_t filetime;
+ stb_int64 filesize;
+} stb_bgstat;
+
+STB_EXTERN int stb_bgio_stat (char *filename, stb_bgstat *result);
+
+#ifdef STB_DEFINE
+
+static stb_workqueue *stb__diskio;
+static stb_mutex stb__diskio_mutex;
+
+void stb_thread_cleanup(void)
+{
+ if (stb__work_global) stb_workq_delete(stb__work_global); stb__work_global = NULL;
+ if (stb__threadmutex) stb_mutex_delete(stb__threadmutex); stb__threadmutex = NULL;
+ if (stb__workmutex) stb_mutex_delete(stb__workmutex); stb__workmutex = NULL;
+ if (stb__diskio) stb_workq_delete(stb__diskio); stb__diskio = NULL;
+ if (stb__diskio_mutex)stb_mutex_delete(stb__diskio_mutex);stb__diskio_mutex= NULL;
+}
+
+
+typedef struct
+{
+ char *filename;
+ FILE *f;
+ int offset;
+ int len;
+
+ stb_bgstat *stat_out;
+ stb_uchar *output;
+ stb_uchar **result;
+ int *len_output;
+ int *flag;
+} stb__disk_command;
+
+#define STB__MAX_DISK_COMMAND 100
+static stb__disk_command stb__dc_queue[STB__MAX_DISK_COMMAND];
+static int stb__dc_offset;
+
+void stb__io_init(void)
+{
+ if (!stb__diskio) {
+ stb__threadmutex_init();
+ stb_mutex_begin(stb__threadmutex);
+ stb_barrier();
+ if (*(stb_thread * volatile *) &stb__diskio == NULL) {
+ stb__diskio_mutex = stb_mutex_new();
+ // use many threads so OS can try to schedule seeks
+ stb__diskio = stb_workq_new_flags(16,STB__MAX_DISK_COMMAND,STB_FALSE,STB_FALSE);
+ }
+ stb_mutex_end(stb__threadmutex);
+ }
+}
+
+static void * stb__io_error(stb__disk_command *dc)
+{
+ if (dc->len_output) *dc->len_output = 0;
+ if (dc->result) *dc->result = NULL;
+ if (dc->flag) *dc->flag = -1;
+ return NULL;
+}
+
+static void * stb__io_task(void *p)
+{
+ stb__disk_command *dc = (stb__disk_command *) p;
+ int len;
+ FILE *f;
+ stb_uchar *buf;
+
+ if (dc->stat_out) {
+ struct _stati64 s;
+ if (!_stati64(dc->filename, &s)) {
+ dc->stat_out->filesize = s.st_size;
+ dc->stat_out->filetime = s.st_mtime;
+ dc->stat_out->is_dir = s.st_mode & _S_IFDIR;
+ dc->stat_out->is_valid = (s.st_mode & _S_IFREG) || dc->stat_out->is_dir;
+ } else
+ dc->stat_out->is_valid = 0;
+ stb_barrier();
+ dc->stat_out->have_data = 1;
+ free(dc->filename);
+ return 0;
+ }
+ if (dc->f) {
+ #ifdef WIN32
+ f = _fdopen(_dup(_fileno(dc->f)), "rb");
+ #else
+ f = fdopen(dup(fileno(dc->f)), "rb");
+ #endif
+ if (!f)
+ return stb__io_error(dc);
+ } else {
+ f = fopen(dc->filename, "rb");
+ free(dc->filename);
+ if (!f)
+ return stb__io_error(dc);
+ }
+
+ len = dc->len;
+ if (len < 0) {
+ fseek(f, 0, SEEK_END);
+ len = ftell(f) - dc->offset;
+ }
+
+ if (fseek(f, dc->offset, SEEK_SET)) {
+ fclose(f);
+ return stb__io_error(dc);
+ }
+
+ if (dc->output)
+ buf = dc->output;
+ else {
+ buf = (stb_uchar *) malloc(len);
+ if (buf == NULL) {
+ fclose(f);
+ return stb__io_error(dc);
+ }
+ }
+
+ len = fread(buf, 1, len, f);
+ fclose(f);
+ if (dc->len_output) *dc->len_output = len;
+ if (dc->result) *dc->result = buf;
+ if (dc->flag) *dc->flag = 1;
+
+ return NULL;
+}
+
+int stb__io_add(char *fname, FILE *f, int off, int len, stb_uchar *out, stb_uchar **result, int *olen, int *flag, stb_bgstat *stat)
+{
+ int res;
+ stb__io_init();
+ // do memory allocation outside of mutex
+ if (fname) fname = stb_p_strdup(fname);
+ stb_mutex_begin(stb__diskio_mutex);
+ {
+ stb__disk_command *dc = &stb__dc_queue[stb__dc_offset];
+ dc->filename = fname;
+ dc->f = f;
+ dc->offset = off;
+ dc->len = len;
+ dc->output = out;
+ dc->result = result;
+ dc->len_output = olen;
+ dc->flag = flag;
+ dc->stat_out = stat;
+ res = stb_workq(stb__diskio, stb__io_task, dc, NULL);
+ if (res)
+ stb__dc_offset = (stb__dc_offset + 1 == STB__MAX_DISK_COMMAND ? 0 : stb__dc_offset+1);
+ }
+ stb_mutex_end(stb__diskio_mutex);
+ return res;
+}
+
+int stb_bgio_read(char *filename, int offset, int len, stb_uchar **result, int *olen)
+{
+ return stb__io_add(filename,NULL,offset,len,NULL,result,olen,NULL,NULL);
+}
+
+int stb_bgio_readf(FILE *f, int offset, int len, stb_uchar **result, int *olen)
+{
+ return stb__io_add(NULL,f,offset,len,NULL,result,olen,NULL,NULL);
+}
+
+int stb_bgio_read_to(char *filename, int offset, int len, stb_uchar *buffer, int *olen)
+{
+ return stb__io_add(filename,NULL,offset,len,buffer,NULL,olen,NULL,NULL);
+}
+
+int stb_bgio_readf_to(FILE *f, int offset, int len, stb_uchar *buffer, int *olen)
+{
+ return stb__io_add(NULL,f,offset,len,buffer,NULL,olen,NULL,NULL);
+}
+
+STB_EXTERN int stb_bgio_stat (char *filename, stb_bgstat *result)
+{
+ result->have_data = 0;
+ return stb__io_add(filename,NULL,0,0,0,NULL,0,NULL, result);
+}
+#endif
+#endif
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Fast malloc implementation
+//
+// This is a clone of TCMalloc, but without the thread support.
+// 1. large objects are allocated directly, page-aligned
+// 2. small objects are allocated in homogeonous heaps, 0 overhead
+//
+// We keep an allocation table for pages a la TCMalloc. This would
+// require 4MB for the entire address space, but we only allocate
+// the parts that are in use. The overhead from using homogenous heaps
+// everywhere is 3MB. (That is, if you allocate 1 object of each size,
+// you'll use 3MB.)
+
+#if defined(STB_DEFINE) && ((defined(_WIN32) && !defined(_M_AMD64)) || defined(STB_FASTMALLOC))
+
+#ifdef _WIN32
+ #ifndef _WINDOWS_
+ #ifndef STB__IMPORT
+ #define STB__IMPORT STB_EXTERN __declspec(dllimport)
+ #define STB__DW unsigned long
+ #endif
+ STB__IMPORT void * __stdcall VirtualAlloc(void *p, unsigned long size, unsigned long type, unsigned long protect);
+ STB__IMPORT int __stdcall VirtualFree(void *p, unsigned long size, unsigned long freetype);
+ #endif
+ #define stb__alloc_pages_raw(x) (stb_uint32) VirtualAlloc(NULL, (x), 0x3000, 0x04)
+ #define stb__dealloc_pages_raw(p) VirtualFree((void *) p, 0, 0x8000)
+#else
+ #error "Platform not currently supported"
+#endif
+
+typedef struct stb__span
+{
+ int start, len;
+ struct stb__span *next, *prev;
+ void *first_free;
+ unsigned short list; // 1..256 free; 257..511 sizeclass; 0=large block
+ short allocations; // # outstanding allocations for sizeclass
+} stb__span; // 24
+
+static stb__span **stb__span_for_page;
+static int stb__firstpage, stb__lastpage;
+static void stb__update_page_range(int first, int last)
+{
+ stb__span **sfp;
+ int i, f,l;
+ if (first >= stb__firstpage && last <= stb__lastpage) return;
+ if (stb__span_for_page == NULL) {
+ f = first;
+ l = f+stb_max(last-f, 16384);
+ l = stb_min(l, 1<<20);
+ } else if (last > stb__lastpage) {
+ f = stb__firstpage;
+ l = f + (stb__lastpage - f) * 2;
+ l = stb_clamp(last, l,1<<20);
+ } else {
+ l = stb__lastpage;
+ f = l - (l - stb__firstpage) * 2;
+ f = stb_clamp(f, 0,first);
+ }
+ sfp = (stb__span **) stb__alloc_pages_raw(sizeof(void *) * (l-f));
+ for (i=f; i < stb__firstpage; ++i) sfp[i - f] = NULL;
+ for ( ; i < stb__lastpage ; ++i) sfp[i - f] = stb__span_for_page[i - stb__firstpage];
+ for ( ; i < l ; ++i) sfp[i - f] = NULL;
+ if (stb__span_for_page) stb__dealloc_pages_raw(stb__span_for_page);
+ stb__firstpage = f;
+ stb__lastpage = l;
+ stb__span_for_page = sfp;
+}
+
+static stb__span *stb__span_free=NULL;
+static stb__span *stb__span_first, *stb__span_end;
+static stb__span *stb__span_alloc(void)
+{
+ stb__span *s = stb__span_free;
+ if (s)
+ stb__span_free = s->next;
+ else {
+ if (!stb__span_first) {
+ stb__span_first = (stb__span *) stb__alloc_pages_raw(65536);
+ if (stb__span_first == NULL) return NULL;
+ stb__span_end = stb__span_first + (65536 / sizeof(stb__span));
+ }
+ s = stb__span_first++;
+ if (stb__span_first == stb__span_end) stb__span_first = NULL;
+ }
+ return s;
+}
+
+static stb__span *stb__spanlist[512];
+
+static void stb__spanlist_unlink(stb__span *s)
+{
+ if (s->prev)
+ s->prev->next = s->next;
+ else {
+ int n = s->list;
+ assert(stb__spanlist[n] == s);
+ stb__spanlist[n] = s->next;
+ }
+ if (s->next)
+ s->next->prev = s->prev;
+ s->next = s->prev = NULL;
+ s->list = 0;
+}
+
+static void stb__spanlist_add(int n, stb__span *s)
+{
+ s->list = n;
+ s->next = stb__spanlist[n];
+ s->prev = NULL;
+ stb__spanlist[n] = s;
+ if (s->next) s->next->prev = s;
+}
+
+#define stb__page_shift 12
+#define stb__page_size (1 << stb__page_shift)
+#define stb__page_number(x) ((x) >> stb__page_shift)
+#define stb__page_address(x) ((x) << stb__page_shift)
+
+static void stb__set_span_for_page(stb__span *s)
+{
+ int i;
+ for (i=0; i < s->len; ++i)
+ stb__span_for_page[s->start + i - stb__firstpage] = s;
+}
+
+static stb__span *stb__coalesce(stb__span *a, stb__span *b)
+{
+ assert(a->start + a->len == b->start);
+ if (a->list) stb__spanlist_unlink(a);
+ if (b->list) stb__spanlist_unlink(b);
+ a->len += b->len;
+ b->len = 0;
+ b->next = stb__span_free;
+ stb__span_free = b;
+ stb__set_span_for_page(a);
+ return a;
+}
+
+static void stb__free_span(stb__span *s)
+{
+ stb__span *n = NULL;
+ if (s->start > stb__firstpage) {
+ n = stb__span_for_page[s->start-1 - stb__firstpage];
+ if (n && n->allocations == -2 && n->start + n->len == s->start) s = stb__coalesce(n,s);
+ }
+ if (s->start + s->len < stb__lastpage) {
+ n = stb__span_for_page[s->start + s->len - stb__firstpage];
+ if (n && n->allocations == -2 && s->start + s->len == n->start) s = stb__coalesce(s,n);
+ }
+ s->allocations = -2;
+ stb__spanlist_add(s->len > 256 ? 256 : s->len, s);
+}
+
+static stb__span *stb__alloc_pages(int num)
+{
+ stb__span *s = stb__span_alloc();
+ int p;
+ if (!s) return NULL;
+ p = stb__alloc_pages_raw(num << stb__page_shift);
+ if (p == 0) { s->next = stb__span_free; stb__span_free = s; return 0; }
+ assert(stb__page_address(stb__page_number(p)) == p);
+ p = stb__page_number(p);
+ stb__update_page_range(p, p+num);
+ s->start = p;
+ s->len = num;
+ s->next = NULL;
+ s->prev = NULL;
+ stb__set_span_for_page(s);
+ return s;
+}
+
+static stb__span *stb__alloc_span(int pagecount)
+{
+ int i;
+ stb__span *p = NULL;
+ for(i=pagecount; i < 256; ++i)
+ if (stb__spanlist[i]) {
+ p = stb__spanlist[i];
+ break;
+ }
+ if (!p) {
+ p = stb__spanlist[256];
+ while (p && p->len < pagecount)
+ p = p->next;
+ }
+ if (!p) {
+ p = stb__alloc_pages(pagecount < 16 ? 16 : pagecount);
+ if (p == NULL) return 0;
+ } else
+ stb__spanlist_unlink(p);
+
+ if (p->len > pagecount) {
+ stb__span *q = stb__span_alloc();
+ if (q) {
+ q->start = p->start + pagecount;
+ q->len = p->len - pagecount;
+ p->len = pagecount;
+ for (i=0; i < q->len; ++i)
+ stb__span_for_page[q->start+i - stb__firstpage] = q;
+ stb__spanlist_add(q->len > 256 ? 256 : q->len, q);
+ }
+ }
+ return p;
+}
+
+#define STB__MAX_SMALL_SIZE 32768
+#define STB__MAX_SIZE_CLASSES 256
+
+static unsigned char stb__class_base[32];
+static unsigned char stb__class_shift[32];
+static unsigned char stb__pages_for_class[STB__MAX_SIZE_CLASSES];
+static int stb__size_for_class[STB__MAX_SIZE_CLASSES];
+
+stb__span *stb__get_nonempty_sizeclass(int c)
+{
+ int s = c + 256, i, size, tsize; // remap to span-list index
+ char *z;
+ void *q;
+ stb__span *p = stb__spanlist[s];
+ if (p) {
+ if (p->first_free) return p; // fast path: it's in the first one in list
+ for (p=p->next; p; p=p->next)
+ if (p->first_free) {
+ // move to front for future queries
+ stb__spanlist_unlink(p);
+ stb__spanlist_add(s, p);
+ return p;
+ }
+ }
+ // no non-empty ones, so allocate a new one
+ p = stb__alloc_span(stb__pages_for_class[c]);
+ if (!p) return NULL;
+ // create the free list up front
+ size = stb__size_for_class[c];
+ tsize = stb__pages_for_class[c] << stb__page_shift;
+ i = 0;
+ z = (char *) stb__page_address(p->start);
+ q = NULL;
+ while (i + size <= tsize) {
+ * (void **) z = q; q = z;
+ z += size;
+ i += size;
+ }
+ p->first_free = q;
+ p->allocations = 0;
+ stb__spanlist_add(s,p);
+ return p;
+}
+
+static int stb__sizeclass(size_t sz)
+{
+ int z = stb_log2_floor(sz); // -1 below to group e.g. 13,14,15,16 correctly
+ return stb__class_base[z] + ((sz-1) >> stb__class_shift[z]);
+}
+
+static void stb__init_sizeclass(void)
+{
+ int i, size, overhead;
+ int align_shift = 2; // allow 4-byte and 12-byte blocks as well, vs. TCMalloc
+ int next_class = 1;
+ int last_log = 0;
+
+ for (i = 0; i < align_shift; i++) {
+ stb__class_base [i] = next_class;
+ stb__class_shift[i] = align_shift;
+ }
+
+ for (size = 1 << align_shift; size <= STB__MAX_SMALL_SIZE; size += 1 << align_shift) {
+ i = stb_log2_floor(size);
+ if (i > last_log) {
+ if (size == 16) ++align_shift; // switch from 4-byte to 8-byte alignment
+ else if (size >= 128 && align_shift < 8) ++align_shift;
+ stb__class_base[i] = next_class - ((size-1) >> align_shift);
+ stb__class_shift[i] = align_shift;
+ last_log = i;
+ }
+ stb__size_for_class[next_class++] = size;
+ }
+
+ for (i=1; i <= STB__MAX_SMALL_SIZE; ++i)
+ assert(i <= stb__size_for_class[stb__sizeclass(i)]);
+
+ overhead = 0;
+ for (i = 1; i < next_class; i++) {
+ int s = stb__size_for_class[i];
+ size = stb__page_size;
+ while (size % s > size >> 3)
+ size += stb__page_size;
+ stb__pages_for_class[i] = (unsigned char) (size >> stb__page_shift);
+ overhead += size;
+ }
+ assert(overhead < (4 << 20)); // make sure it's under 4MB of overhead
+}
+
+#ifdef STB_DEBUG
+#define stb__smemset(a,b,c) memset((void *) a, b, c)
+#elif defined(STB_FASTMALLOC_INIT)
+#define stb__smemset(a,b,c) memset((void *) a, b, c)
+#else
+#define stb__smemset(a,b,c)
+#endif
+void *stb_smalloc(size_t sz)
+{
+ stb__span *s;
+ if (sz == 0) return NULL;
+ if (stb__size_for_class[1] == 0) stb__init_sizeclass();
+ if (sz > STB__MAX_SMALL_SIZE) {
+ s = stb__alloc_span((sz + stb__page_size - 1) >> stb__page_shift);
+ if (s == NULL) return NULL;
+ s->list = 0;
+ s->next = s->prev = NULL;
+ s->allocations = -32767;
+ stb__smemset(stb__page_address(s->start), 0xcd, (sz+3)&~3);
+ return (void *) stb__page_address(s->start);
+ } else {
+ void *p;
+ int c = stb__sizeclass(sz);
+ s = stb__spanlist[256+c];
+ if (!s || !s->first_free)
+ s = stb__get_nonempty_sizeclass(c);
+ if (s == NULL) return NULL;
+ p = s->first_free;
+ s->first_free = * (void **) p;
+ ++s->allocations;
+ stb__smemset(p,0xcd, sz);
+ return p;
+ }
+}
+
+int stb_ssize(void *p)
+{
+ stb__span *s;
+ if (p == NULL) return 0;
+ s = stb__span_for_page[stb__page_number((stb_uint) p) - stb__firstpage];
+ if (s->list >= 256) {
+ return stb__size_for_class[s->list - 256];
+ } else {
+ assert(s->list == 0);
+ return s->len << stb__page_shift;
+ }
+}
+
+void stb_sfree(void *p)
+{
+ stb__span *s;
+ if (p == NULL) return;
+ s = stb__span_for_page[stb__page_number((stb_uint) p) - stb__firstpage];
+ if (s->list >= 256) {
+ stb__smemset(p, 0xfe, stb__size_for_class[s->list-256]);
+ * (void **) p = s->first_free;
+ s->first_free = p;
+ if (--s->allocations == 0) {
+ stb__spanlist_unlink(s);
+ stb__free_span(s);
+ }
+ } else {
+ assert(s->list == 0);
+ stb__smemset(p, 0xfe, stb_ssize(p));
+ stb__free_span(s);
+ }
+}
+
+void *stb_srealloc(void *p, size_t sz)
+{
+ size_t cur_size;
+ if (p == NULL) return stb_smalloc(sz);
+ if (sz == 0) { stb_sfree(p); return NULL; }
+ cur_size = stb_ssize(p);
+ if (sz > cur_size || sz <= (cur_size >> 1)) {
+ void *q;
+ if (sz > cur_size && sz < (cur_size << 1)) sz = cur_size << 1;
+ q = stb_smalloc(sz); if (q == NULL) return NULL;
+ memcpy(q, p, sz < cur_size ? sz : cur_size);
+ stb_sfree(p);
+ return q;
+ }
+ return p;
+}
+
+void *stb_scalloc(size_t n, size_t sz)
+{
+ void *p;
+ if (n == 0 || sz == 0) return NULL;
+ if (stb_log2_ceil(n) + stb_log2_ceil(n) >= 32) return NULL;
+ p = stb_smalloc(n*sz);
+ if (p) memset(p, 0, n*sz);
+ return p;
+}
+
+char *stb_sstrdup(char *s)
+{
+ int n = strlen(s);
+ char *p = (char *) stb_smalloc(n+1);
+ if (p) stb_p_strcpy_s(p,n+1,s);
+ return p;
+}
+#endif // STB_DEFINE
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Source code constants
+//
+// This is a trivial system to let you specify constants in source code,
+// then while running you can change the constants.
+//
+// Note that you can't wrap the #defines, because we need to know their
+// names. So we provide a pre-wrapped version without 'STB_' for convenience;
+// to request it, #define STB_CONVENIENT_H, yielding:
+// KI -- integer
+// KU -- unsigned integer
+// KF -- float
+// KD -- double
+// KS -- string constant
+//
+// Defaults to functioning in debug build, not in release builds.
+// To force on, define STB_ALWAYS_H
+
+#ifdef STB_CONVENIENT_H
+#define KI(x) STB_I(x)
+#define KU(x) STB_UI(x)
+#define KF(x) STB_F(x)
+#define KD(x) STB_D(x)
+#define KS(x) STB_S(x)
+#endif
+
+STB_EXTERN void stb_source_path(char *str);
+#ifdef STB_DEFINE
+char *stb__source_path;
+void stb_source_path(char *path)
+{
+ stb__source_path = path;
+}
+
+char *stb__get_sourcefile_path(char *file)
+{
+ static char filebuf[512];
+ if (stb__source_path) {
+ stb_p_sprintf(filebuf stb_p_size(sizeof(filebuf)), "%s/%s", stb__source_path, file);
+ if (stb_fexists(filebuf)) return filebuf;
+ }
+
+ if (stb_fexists(file)) return file;
+
+ stb_p_sprintf(filebuf stb_p_size(sizeof(filebuf)), "../%s", file);
+ if (!stb_fexists(filebuf)) return filebuf;
+
+ return file;
+}
+#endif
+
+#define STB_F(x) ((float) STB_H(x))
+#define STB_UI(x) ((unsigned int) STB_I(x))
+
+#if !defined(STB_DEBUG) && !defined(STB_ALWAYS_H)
+#define STB_D(x) ((double) (x))
+#define STB_I(x) ((int) (x))
+#define STB_S(x) ((char *) (x))
+#else
+#define STB_D(x) stb__double_constant(__FILE__, __LINE__-1, (x))
+#define STB_I(x) stb__int_constant(__FILE__, __LINE__-1, (x))
+#define STB_S(x) stb__string_constant(__FILE__, __LINE__-1, (x))
+
+STB_EXTERN double stb__double_constant(char *file, int line, double x);
+STB_EXTERN int stb__int_constant(char *file, int line, int x);
+STB_EXTERN char * stb__string_constant(char *file, int line, char *str);
+
+#ifdef STB_DEFINE
+
+enum
+{
+ STB__CTYPE_int,
+ STB__CTYPE_uint,
+ STB__CTYPE_float,
+ STB__CTYPE_double,
+ STB__CTYPE_string,
+};
+
+typedef struct
+{
+ int line;
+ int type;
+ union {
+ int ival;
+ double dval;
+ char *sval;
+ };
+} stb__Entry;
+
+typedef struct
+{
+ stb__Entry *entries;
+ char *filename;
+ time_t timestamp;
+ char **file_data;
+ int file_len;
+ unsigned short *line_index;
+} stb__FileEntry;
+
+static void stb__constant_parse(stb__FileEntry *f, int i)
+{
+ char *s;
+ int n;
+ if (!stb_arr_valid(f->entries, i)) return;
+ n = f->entries[i].line;
+ if (n >= f->file_len) return;
+ s = f->file_data[n];
+ switch (f->entries[i].type) {
+ case STB__CTYPE_float:
+ while (*s) {
+ if (!strncmp(s, "STB_D(", 6)) { s+=6; goto matched_float; }
+ if (!strncmp(s, "STB_F(", 6)) { s+=6; goto matched_float; }
+ if (!strncmp(s, "KD(", 3)) { s+=3; goto matched_float; }
+ if (!strncmp(s, "KF(", 3)) { s+=3; goto matched_float; }
+ ++s;
+ }
+ break;
+ matched_float:
+ f->entries[i].dval = strtod(s, NULL);
+ break;
+ case STB__CTYPE_int:
+ while (*s) {
+ if (!strncmp(s, "STB_I(", 6)) { s+=6; goto matched_int; }
+ if (!strncmp(s, "STB_UI(", 7)) { s+=7; goto matched_int; }
+ if (!strncmp(s, "KI(", 3)) { s+=3; goto matched_int; }
+ if (!strncmp(s, "KU(", 3)) { s+=3; goto matched_int; }
+ ++s;
+ }
+ break;
+ matched_int: {
+ int neg=0;
+ s = stb_skipwhite(s);
+ while (*s == '-') { neg = !neg; s = stb_skipwhite(s+1); } // handle '- - 5', pointlessly
+ if (s[0] == '0' && tolower(s[1]) == 'x')
+ f->entries[i].ival = strtol(s, NULL, 16);
+ else if (s[0] == '0')
+ f->entries[i].ival = strtol(s, NULL, 8);
+ else
+ f->entries[i].ival = strtol(s, NULL, 10);
+ if (neg) f->entries[i].ival = -f->entries[i].ival;
+ break;
+ }
+ case STB__CTYPE_string:
+ // @TODO
+ break;
+ }
+}
+
+static stb_sdict *stb__constant_file_hash;
+
+stb__Entry *stb__constant_get_entry(char *filename, int line, int type)
+{
+ int i;
+ stb__FileEntry *f;
+ if (stb__constant_file_hash == NULL)
+ stb__constant_file_hash = stb_sdict_new(STB_TRUE);
+ f = (stb__FileEntry*) stb_sdict_get(stb__constant_file_hash, filename);
+ if (f == NULL) {
+ char *s = stb__get_sourcefile_path(filename);
+ if (s == NULL || !stb_fexists(s)) return 0;
+ f = (stb__FileEntry *) malloc(sizeof(*f));
+ f->timestamp = stb_ftimestamp(s);
+ f->file_data = stb_stringfile(s, &f->file_len);
+ f->filename = stb_p_strdup(s); // cache the full path
+ f->entries = NULL;
+ f->line_index = 0;
+ stb_arr_setlen(f->line_index, f->file_len);
+ memset(f->line_index, 0xff, stb_arr_storage(f->line_index));
+ } else {
+ time_t t = stb_ftimestamp(f->filename);
+ if (f->timestamp != t) {
+ f->timestamp = t;
+ free(f->file_data);
+ f->file_data = stb_stringfile(f->filename, &f->file_len);
+ stb_arr_setlen(f->line_index, f->file_len);
+ for (i=0; i < stb_arr_len(f->entries); ++i)
+ stb__constant_parse(f, i);
+ }
+ }
+
+ if (line >= f->file_len) return 0;
+
+ if (f->line_index[line] >= stb_arr_len(f->entries)) {
+ // need a new entry
+ int n = stb_arr_len(f->entries);
+ stb__Entry e;
+ e.line = line;
+ if (line < f->file_len)
+ f->line_index[line] = n;
+ e.type = type;
+ stb_arr_push(f->entries, e);
+ stb__constant_parse(f, n);
+ }
+ return f->entries + f->line_index[line];
+}
+
+double stb__double_constant(char *file, int line, double x)
+{
+ stb__Entry *e = stb__constant_get_entry(file, line, STB__CTYPE_float);
+ if (!e) return x;
+ return e->dval;
+}
+
+int stb__int_constant(char *file, int line, int x)
+{
+ stb__Entry *e = stb__constant_get_entry(file, line, STB__CTYPE_int);
+ if (!e) return x;
+ return e->ival;
+}
+
+char * stb__string_constant(char *file, int line, char *x)
+{
+ stb__Entry *e = stb__constant_get_entry(file, line, STB__CTYPE_string);
+ if (!e) return x;
+ return e->sval;
+}
+
+#endif // STB_DEFINE
+#endif // !STB_DEBUG && !STB_ALWAYS_H
+
+#undef STB_EXTERN
+#endif // STB_INCLUDE_STB_H
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+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 OR COPYRIGHT HOLDERS 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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+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.
+------------------------------------------------------------------------------
+*/
diff --git a/third-party/stb/deprecated/stb_image.c b/third-party/stb/deprecated/stb_image.c
new file mode 100644
index 0000000..de0d935
--- /dev/null
+++ b/third-party/stb/deprecated/stb_image.c
@@ -0,0 +1,4678 @@
+/* stb_image - v1.35 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c
+ when you control the images you're loading
+ no warranty implied; use at your own risk
+
+ QUICK NOTES:
+ Primarily of interest to game developers and other people who can
+ avoid problematic images and only need the trivial interface
+
+ JPEG baseline (no JPEG progressive)
+ PNG 8-bit-per-channel only
+
+ TGA (not sure what subset, if a subset)
+ BMP non-1bpp, non-RLE
+ PSD (composited view only, no extra channels)
+
+ GIF (*comp always reports as 4-channel)
+ HDR (radiance rgbE format)
+ PIC (Softimage PIC)
+
+ - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
+ - decode from arbitrary I/O callbacks
+ - overridable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD)
+
+ Latest revisions:
+ 1.35 (2014-05-27) warnings, bugfixes, TGA optimization, etc
+ 1.34 (unknown ) warning fix
+ 1.33 (2011-07-14) minor fixes suggested by Dave Moore
+ 1.32 (2011-07-13) info support for all filetypes (SpartanJ)
+ 1.31 (2011-06-19) a few more leak fixes, bug in PNG handling (SpartanJ)
+ 1.30 (2011-06-11) added ability to load files via io callbacks (Ben Wenger)
+ 1.29 (2010-08-16) various warning fixes from Aurelien Pocheville
+ 1.28 (2010-08-01) fix bug in GIF palette transparency (SpartanJ)
+
+ See end of file for full revision history.
+
+ TODO:
+ stbi_info support for BMP,PSD,HDR,PIC
+
+
+ ============================ Contributors =========================
+
+ Image formats Bug fixes & warning fixes
+ Sean Barrett (jpeg, png, bmp) Marc LeBlanc
+ Nicolas Schulz (hdr, psd) Christpher Lloyd
+ Jonathan Dummer (tga) Dave Moore
+ Jean-Marc Lienher (gif) Won Chun
+ Tom Seddon (pic) the Horde3D community
+ Thatcher Ulrich (psd) Janez Zemva
+ Jonathan Blow
+ Laurent Gomila
+ Extensions, features Aruelien Pocheville
+ Jetro Lauha (stbi_info) Ryamond Barbiero
+ James "moose2000" Brown (iPhone PNG) David Woo
+ Ben "Disch" Wenger (io callbacks) Roy Eltham
+ Martin "SpartanJ" Golini Luke Graham
+ Thomas Ruf
+ John Bartholomew
+ Optimizations & bugfixes Ken Hamada
+ Fabian "ryg" Giesen Cort Stratton
+ Arseny Kapoulkine Blazej Dariusz Roszkowski
+ Thibault Reuille
+ If your name should be here but Paul Du Bois
+ isn't let Sean know. Guillaume George
+
+*/
+
+#ifndef STBI_INCLUDE_STB_IMAGE_H
+#define STBI_INCLUDE_STB_IMAGE_H
+
+// To get a header file for this, either cut and paste the header,
+// or create stb_image.h, #define STBI_HEADER_FILE_ONLY, and
+// then include stb_image.c from it.
+
+//// begin header file ////////////////////////////////////////////////////
+//
+// Limitations:
+// - no jpeg progressive support
+// - non-HDR formats support 8-bit samples only (jpeg, png)
+// - no delayed line count (jpeg) -- IJG doesn't support either
+// - no 1-bit BMP
+// - GIF always returns *comp=4
+//
+// Basic usage (see HDR discussion below):
+// int x,y,n;
+// unsigned char *data = stbi_load(filename, &x, &y, &n, 0);
+// // ... process data if not NULL ...
+// // ... x = width, y = height, n = # 8-bit components per pixel ...
+// // ... replace '0' with '1'..'4' to force that many components per pixel
+// // ... but 'n' will always be the number that it would have been if you said 0
+// stbi_image_free(data)
+//
+// Standard parameters:
+// int *x -- outputs image width in pixels
+// int *y -- outputs image height in pixels
+// int *comp -- outputs # of image components in image file
+// int req_comp -- if non-zero, # of image components requested in result
+//
+// The return value from an image loader is an 'unsigned char *' which points
+// to the pixel data. The pixel data consists of *y scanlines of *x pixels,
+// with each pixel consisting of N interleaved 8-bit components; the first
+// pixel pointed to is top-left-most in the image. There is no padding between
+// image scanlines or between pixels, regardless of format. The number of
+// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise.
+// If req_comp is non-zero, *comp has the number of components that _would_
+// have been output otherwise. E.g. if you set req_comp to 4, you will always
+// get RGBA output, but you can check *comp to easily see if it's opaque.
+//
+// An output image with N components has the following components interleaved
+// in this order in each pixel:
+//
+// N=#comp components
+// 1 grey
+// 2 grey, alpha
+// 3 red, green, blue
+// 4 red, green, blue, alpha
+//
+// If image loading fails for any reason, the return value will be NULL,
+// and *x, *y, *comp will be unchanged. The function stbi_failure_reason()
+// can be queried for an extremely brief, end-user unfriendly explanation
+// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid
+// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly
+// more user-friendly ones.
+//
+// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.
+//
+// ===========================================================================
+//
+// iPhone PNG support:
+//
+// By default we convert iphone-formatted PNGs back to RGB; nominally they
+// would silently load as BGR, except the existing code should have just
+// failed on such iPhone PNGs. But you can disable this conversion by
+// by calling stbi_convert_iphone_png_to_rgb(0), in which case
+// you will always just get the native iphone "format" through.
+//
+// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per
+// pixel to remove any premultiplied alpha *only* if the image file explicitly
+// says there's premultiplied data (currently only happens in iPhone images,
+// and only if iPhone convert-to-rgb processing is on).
+//
+// ===========================================================================
+//
+// HDR image support (disable by defining STBI_NO_HDR)
+//
+// stb_image now supports loading HDR images in general, and currently
+// the Radiance .HDR file format, although the support is provided
+// generically. You can still load any file through the existing interface;
+// if you attempt to load an HDR file, it will be automatically remapped to
+// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;
+// both of these constants can be reconfigured through this interface:
+//
+// stbi_hdr_to_ldr_gamma(2.2f);
+// stbi_hdr_to_ldr_scale(1.0f);
+//
+// (note, do not use _inverse_ constants; stbi_image will invert them
+// appropriately).
+//
+// Additionally, there is a new, parallel interface for loading files as
+// (linear) floats to preserve the full dynamic range:
+//
+// float *data = stbi_loadf(filename, &x, &y, &n, 0);
+//
+// If you load LDR images through this interface, those images will
+// be promoted to floating point values, run through the inverse of
+// constants corresponding to the above:
+//
+// stbi_ldr_to_hdr_scale(1.0f);
+// stbi_ldr_to_hdr_gamma(2.2f);
+//
+// Finally, given a filename (or an open file or memory block--see header
+// file for details) containing image data, you can query for the "most
+// appropriate" interface to use (that is, whether the image is HDR or
+// not), using:
+//
+// stbi_is_hdr(char *filename);
+//
+// ===========================================================================
+//
+// I/O callbacks
+//
+// I/O callbacks allow you to read from arbitrary sources, like packaged
+// files or some other source. Data read from callbacks are processed
+// through a small internal buffer (currently 128 bytes) to try to reduce
+// overhead.
+//
+// The three functions you must define are "read" (reads some bytes of data),
+// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end).
+
+
+#ifndef STBI_NO_STDIO
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen()
+#pragma warning(push)
+#pragma warning(disable:4996) // suppress even more warnings about fopen()
+#endif
+#include
+#endif // STBI_NO_STDIO
+
+#define STBI_VERSION 1
+
+enum
+{
+ STBI_default = 0, // only used for req_comp
+
+ STBI_grey = 1,
+ STBI_grey_alpha = 2,
+ STBI_rgb = 3,
+ STBI_rgb_alpha = 4
+};
+
+typedef unsigned char stbi_uc;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// PRIMARY API - works on images of any type
+//
+
+//
+// load image by filename, open file, or memory buffer
+//
+
+extern stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
+
+#ifndef STBI_NO_STDIO
+extern stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp);
+extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
+// for stbi_load_from_file, file pointer is left pointing immediately after image
+#endif
+
+typedef struct
+{
+ int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read
+ void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
+ int (*eof) (void *user); // returns nonzero if we are at end of file/data
+} stbi_io_callbacks;
+
+extern stbi_uc *stbi_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp);
+
+#ifndef STBI_NO_HDR
+ extern float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
+
+ #ifndef STBI_NO_STDIO
+ extern float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp);
+ extern float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
+ #endif
+
+ extern float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp);
+
+ extern void stbi_hdr_to_ldr_gamma(float gamma);
+ extern void stbi_hdr_to_ldr_scale(float scale);
+
+ extern void stbi_ldr_to_hdr_gamma(float gamma);
+ extern void stbi_ldr_to_hdr_scale(float scale);
+#endif // STBI_NO_HDR
+
+// stbi_is_hdr is always defined
+extern int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);
+extern int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
+#ifndef STBI_NO_STDIO
+extern int stbi_is_hdr (char const *filename);
+extern int stbi_is_hdr_from_file(FILE *f);
+#endif // STBI_NO_STDIO
+
+
+// get a VERY brief reason for failure
+// NOT THREADSAFE
+extern const char *stbi_failure_reason (void);
+
+// free the loaded image -- this is just free()
+extern void stbi_image_free (void *retval_from_stbi_load);
+
+// get image dimensions & components without fully decoding
+extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
+extern int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);
+
+#ifndef STBI_NO_STDIO
+extern int stbi_info (char const *filename, int *x, int *y, int *comp);
+extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp);
+
+#endif
+
+
+
+// for image formats that explicitly notate that they have premultiplied alpha,
+// we just return the colors as stored in the file. set this flag to force
+// unpremultiplication. results are undefined if the unpremultiply overflow.
+extern void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
+
+// indicate whether we should process iphone images back to canonical format,
+// or just pass them through "as-is"
+extern void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
+
+
+// ZLIB client - used by PNG, available for other purposes
+
+extern char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
+extern char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);
+extern char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
+extern int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+extern char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
+extern int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+
+// define faster low-level operations (typically SIMD support)
+#ifdef STBI_SIMD
+typedef void (*stbi_idct_8x8)(stbi_uc *out, int out_stride, short data[64], unsigned short *dequantize);
+// compute an integer IDCT on "input"
+// input[x] = data[x] * dequantize[x]
+// write results to 'out': 64 samples, each run of 8 spaced by 'out_stride'
+// CLAMP results to 0..255
+typedef void (*stbi_YCbCr_to_RGB_run)(stbi_uc *output, stbi_uc const *y, stbi_uc const *cb, stbi_uc const *cr, int count, int step);
+// compute a conversion from YCbCr to RGB
+// 'count' pixels
+// write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B
+// y: Y input channel
+// cb: Cb input channel; scale/biased to be 0..255
+// cr: Cr input channel; scale/biased to be 0..255
+
+extern void stbi_install_idct(stbi_idct_8x8 func);
+extern void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func);
+#endif // STBI_SIMD
+
+
+#ifdef __cplusplus
+}
+#endif
+
+//
+//
+//// end header file /////////////////////////////////////////////////////
+#endif // STBI_INCLUDE_STB_IMAGE_H
+
+#ifndef STBI_HEADER_FILE_ONLY
+
+#ifndef STBI_NO_HDR
+#include // ldexp
+#include // strcmp, strtok
+#endif
+
+#ifndef STBI_NO_STDIO
+#include
+#endif
+#include
+#include
+#include
+#include
+#include // ptrdiff_t on osx
+
+#ifndef _MSC_VER
+ #ifdef __cplusplus
+ #define stbi_inline inline
+ #else
+ #define stbi_inline
+ #endif
+#else
+ #define stbi_inline __forceinline
+#endif
+
+
+#ifdef _MSC_VER
+typedef unsigned char stbi__uint8;
+typedef unsigned short stbi__uint16;
+typedef signed short stbi__int16;
+typedef unsigned int stbi__uint32;
+typedef signed int stbi__int32;
+#else
+#include
+typedef uint8_t stbi__uint8;
+typedef uint16_t stbi__uint16;
+typedef int16_t stbi__int16;
+typedef uint32_t stbi__uint32;
+typedef int32_t stbi__int32;
+#endif
+
+// should produce compiler error if size is wrong
+typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];
+
+#ifdef _MSC_VER
+#define STBI_NOTUSED(v) (void)(v)
+#else
+#define STBI_NOTUSED(v) (void)sizeof(v)
+#endif
+
+#ifdef _MSC_VER
+#define STBI_HAS_LROTL
+#endif
+
+#ifdef STBI_HAS_LROTL
+ #define stbi_lrot(x,y) _lrotl(x,y)
+#else
+ #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y))))
+#endif
+
+///////////////////////////////////////////////
+//
+// stbi struct and start_xxx functions
+
+// stbi structure is our basic context used by all images, so it
+// contains all the IO context, plus some basic image information
+typedef struct
+{
+ stbi__uint32 img_x, img_y;
+ int img_n, img_out_n;
+
+ stbi_io_callbacks io;
+ void *io_user_data;
+
+ int read_from_callbacks;
+ int buflen;
+ stbi__uint8 buffer_start[128];
+
+ stbi__uint8 *img_buffer, *img_buffer_end;
+ stbi__uint8 *img_buffer_original;
+} stbi;
+
+
+static void refill_buffer(stbi *s);
+
+// initialize a memory-decode context
+static void start_mem(stbi *s, stbi__uint8 const *buffer, int len)
+{
+ s->io.read = NULL;
+ s->read_from_callbacks = 0;
+ s->img_buffer = s->img_buffer_original = (stbi__uint8 *) buffer;
+ s->img_buffer_end = (stbi__uint8 *) buffer+len;
+}
+
+// initialize a callback-based context
+static void start_callbacks(stbi *s, stbi_io_callbacks *c, void *user)
+{
+ s->io = *c;
+ s->io_user_data = user;
+ s->buflen = sizeof(s->buffer_start);
+ s->read_from_callbacks = 1;
+ s->img_buffer_original = s->buffer_start;
+ refill_buffer(s);
+}
+
+#ifndef STBI_NO_STDIO
+
+static int stdio_read(void *user, char *data, int size)
+{
+ return (int) fread(data,1,size,(FILE*) user);
+}
+
+static void stdio_skip(void *user, int n)
+{
+ fseek((FILE*) user, n, SEEK_CUR);
+}
+
+static int stdio_eof(void *user)
+{
+ return feof((FILE*) user);
+}
+
+static stbi_io_callbacks stbi_stdio_callbacks =
+{
+ stdio_read,
+ stdio_skip,
+ stdio_eof,
+};
+
+static void start_file(stbi *s, FILE *f)
+{
+ start_callbacks(s, &stbi_stdio_callbacks, (void *) f);
+}
+
+//static void stop_file(stbi *s) { }
+
+#endif // !STBI_NO_STDIO
+
+static void stbi_rewind(stbi *s)
+{
+ // conceptually rewind SHOULD rewind to the beginning of the stream,
+ // but we just rewind to the beginning of the initial buffer, because
+ // we only use it after doing 'test', which only ever looks at at most 92 bytes
+ s->img_buffer = s->img_buffer_original;
+}
+
+static int stbi_jpeg_test(stbi *s);
+static stbi_uc *stbi_jpeg_load(stbi *s, int *x, int *y, int *comp, int req_comp);
+static int stbi_jpeg_info(stbi *s, int *x, int *y, int *comp);
+static int stbi_png_test(stbi *s);
+static stbi_uc *stbi_png_load(stbi *s, int *x, int *y, int *comp, int req_comp);
+static int stbi_png_info(stbi *s, int *x, int *y, int *comp);
+static int stbi_bmp_test(stbi *s);
+static stbi_uc *stbi_bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp);
+static int stbi_tga_test(stbi *s);
+static stbi_uc *stbi_tga_load(stbi *s, int *x, int *y, int *comp, int req_comp);
+static int stbi_tga_info(stbi *s, int *x, int *y, int *comp);
+static int stbi_psd_test(stbi *s);
+static stbi_uc *stbi_psd_load(stbi *s, int *x, int *y, int *comp, int req_comp);
+#ifndef STBI_NO_HDR
+static int stbi_hdr_test(stbi *s);
+static float *stbi_hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp);
+#endif
+static int stbi_pic_test(stbi *s);
+static stbi_uc *stbi_pic_load(stbi *s, int *x, int *y, int *comp, int req_comp);
+static int stbi_gif_test(stbi *s);
+static stbi_uc *stbi_gif_load(stbi *s, int *x, int *y, int *comp, int req_comp);
+static int stbi_gif_info(stbi *s, int *x, int *y, int *comp);
+
+
+// this is not threadsafe
+static const char *failure_reason;
+
+const char *stbi_failure_reason(void)
+{
+ return failure_reason;
+}
+
+static int e(const char *str)
+{
+ failure_reason = str;
+ return 0;
+}
+
+// e - error
+// epf - error returning pointer to float
+// epuc - error returning pointer to unsigned char
+
+#ifdef STBI_NO_FAILURE_STRINGS
+ #define e(x,y) 0
+#elif defined(STBI_FAILURE_USERMSG)
+ #define e(x,y) e(y)
+#else
+ #define e(x,y) e(x)
+#endif
+
+#define epf(x,y) ((float *) (e(x,y)?NULL:NULL))
+#define epuc(x,y) ((unsigned char *) (e(x,y)?NULL:NULL))
+
+void stbi_image_free(void *retval_from_stbi_load)
+{
+ free(retval_from_stbi_load);
+}
+
+#ifndef STBI_NO_HDR
+static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
+static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp);
+#endif
+
+static unsigned char *stbi_load_main(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ if (stbi_jpeg_test(s)) return stbi_jpeg_load(s,x,y,comp,req_comp);
+ if (stbi_png_test(s)) return stbi_png_load(s,x,y,comp,req_comp);
+ if (stbi_bmp_test(s)) return stbi_bmp_load(s,x,y,comp,req_comp);
+ if (stbi_gif_test(s)) return stbi_gif_load(s,x,y,comp,req_comp);
+ if (stbi_psd_test(s)) return stbi_psd_load(s,x,y,comp,req_comp);
+ if (stbi_pic_test(s)) return stbi_pic_load(s,x,y,comp,req_comp);
+
+ #ifndef STBI_NO_HDR
+ if (stbi_hdr_test(s)) {
+ float *hdr = stbi_hdr_load(s, x,y,comp,req_comp);
+ return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);
+ }
+ #endif
+
+ // test tga last because it's a crappy test!
+ if (stbi_tga_test(s))
+ return stbi_tga_load(s,x,y,comp,req_comp);
+ return epuc("unknown image type", "Image not of any known type, or corrupt");
+}
+
+#ifndef STBI_NO_STDIO
+unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+ FILE *f = fopen(filename, "rb");
+ unsigned char *result;
+ if (!f) return epuc("can't fopen", "Unable to open file");
+ result = stbi_load_from_file(f,x,y,comp,req_comp);
+ fclose(f);
+ return result;
+}
+
+unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char *result;
+ stbi s;
+ start_file(&s,f);
+ result = stbi_load_main(&s,x,y,comp,req_comp);
+ if (result) {
+ // need to 'unget' all the characters in the IO buffer
+ fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
+ }
+ return result;
+}
+#endif //!STBI_NO_STDIO
+
+unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+ stbi s;
+ start_mem(&s,buffer,len);
+ return stbi_load_main(&s,x,y,comp,req_comp);
+}
+
+unsigned char *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+ stbi s;
+ start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi_load_main(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_HDR
+
+float *stbi_loadf_main(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char *data;
+ #ifndef STBI_NO_HDR
+ if (stbi_hdr_test(s))
+ return stbi_hdr_load(s,x,y,comp,req_comp);
+ #endif
+ data = stbi_load_main(s, x, y, comp, req_comp);
+ if (data)
+ return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);
+ return epf("unknown image type", "Image not of any known type, or corrupt");
+}
+
+float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+ stbi s;
+ start_mem(&s,buffer,len);
+ return stbi_loadf_main(&s,x,y,comp,req_comp);
+}
+
+float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+ stbi s;
+ start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi_loadf_main(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_STDIO
+float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+ FILE *f = fopen(filename, "rb");
+ float *result;
+ if (!f) return epf("can't fopen", "Unable to open file");
+ result = stbi_loadf_from_file(f,x,y,comp,req_comp);
+ fclose(f);
+ return result;
+}
+
+float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+ stbi s;
+ start_file(&s,f);
+ return stbi_loadf_main(&s,x,y,comp,req_comp);
+}
+#endif // !STBI_NO_STDIO
+
+#endif // !STBI_NO_HDR
+
+// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is
+// defined, for API simplicity; if STBI_NO_HDR is defined, it always
+// reports false!
+
+int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)
+{
+ #ifndef STBI_NO_HDR
+ stbi s;
+ start_mem(&s,buffer,len);
+ return stbi_hdr_test(&s);
+ #else
+ STBI_NOTUSED(buffer);
+ STBI_NOTUSED(len);
+ return 0;
+ #endif
+}
+
+#ifndef STBI_NO_STDIO
+extern int stbi_is_hdr (char const *filename)
+{
+ FILE *f = fopen(filename, "rb");
+ int result=0;
+ if (f) {
+ result = stbi_is_hdr_from_file(f);
+ fclose(f);
+ }
+ return result;
+}
+
+extern int stbi_is_hdr_from_file(FILE *f)
+{
+ #ifndef STBI_NO_HDR
+ stbi s;
+ start_file(&s,f);
+ return stbi_hdr_test(&s);
+ #else
+ return 0;
+ #endif
+}
+#endif // !STBI_NO_STDIO
+
+extern int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)
+{
+ #ifndef STBI_NO_HDR
+ stbi s;
+ start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi_hdr_test(&s);
+ #else
+ return 0;
+ #endif
+}
+
+#ifndef STBI_NO_HDR
+static float h2l_gamma_i=1.0f/2.2f, h2l_scale_i=1.0f;
+static float l2h_gamma=2.2f, l2h_scale=1.0f;
+
+void stbi_hdr_to_ldr_gamma(float gamma) { h2l_gamma_i = 1/gamma; }
+void stbi_hdr_to_ldr_scale(float scale) { h2l_scale_i = 1/scale; }
+
+void stbi_ldr_to_hdr_gamma(float gamma) { l2h_gamma = gamma; }
+void stbi_ldr_to_hdr_scale(float scale) { l2h_scale = scale; }
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Common code used by all image loaders
+//
+
+enum
+{
+ SCAN_load=0,
+ SCAN_type,
+ SCAN_header
+};
+
+static void refill_buffer(stbi *s)
+{
+ int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);
+ if (n == 0) {
+ // at end of file, treat same as if from memory, but need to handle case
+ // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file
+ s->read_from_callbacks = 0;
+ s->img_buffer = s->buffer_start;
+ s->img_buffer_end = s->buffer_start+1;
+ *s->img_buffer = 0;
+ } else {
+ s->img_buffer = s->buffer_start;
+ s->img_buffer_end = s->buffer_start + n;
+ }
+}
+
+stbi_inline static int get8(stbi *s)
+{
+ if (s->img_buffer < s->img_buffer_end)
+ return *s->img_buffer++;
+ if (s->read_from_callbacks) {
+ refill_buffer(s);
+ return *s->img_buffer++;
+ }
+ return 0;
+}
+
+stbi_inline static int at_eof(stbi *s)
+{
+ if (s->io.read) {
+ if (!(s->io.eof)(s->io_user_data)) return 0;
+ // if feof() is true, check if buffer = end
+ // special case: we've only got the special 0 character at the end
+ if (s->read_from_callbacks == 0) return 1;
+ }
+
+ return s->img_buffer >= s->img_buffer_end;
+}
+
+stbi_inline static stbi__uint8 get8u(stbi *s)
+{
+ return (stbi__uint8) get8(s);
+}
+
+static void skip(stbi *s, int n)
+{
+ if (s->io.read) {
+ int blen = (int) (s->img_buffer_end - s->img_buffer);
+ if (blen < n) {
+ s->img_buffer = s->img_buffer_end;
+ (s->io.skip)(s->io_user_data, n - blen);
+ return;
+ }
+ }
+ s->img_buffer += n;
+}
+
+static int getn(stbi *s, stbi_uc *buffer, int n)
+{
+ if (s->io.read) {
+ int blen = (int) (s->img_buffer_end - s->img_buffer);
+ if (blen < n) {
+ int res, count;
+
+ memcpy(buffer, s->img_buffer, blen);
+
+ count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);
+ res = (count == (n-blen));
+ s->img_buffer = s->img_buffer_end;
+ return res;
+ }
+ }
+
+ if (s->img_buffer+n <= s->img_buffer_end) {
+ memcpy(buffer, s->img_buffer, n);
+ s->img_buffer += n;
+ return 1;
+ } else
+ return 0;
+}
+
+static int get16(stbi *s)
+{
+ int z = get8(s);
+ return (z << 8) + get8(s);
+}
+
+static stbi__uint32 get32(stbi *s)
+{
+ stbi__uint32 z = get16(s);
+ return (z << 16) + get16(s);
+}
+
+static int get16le(stbi *s)
+{
+ int z = get8(s);
+ return z + (get8(s) << 8);
+}
+
+static stbi__uint32 get32le(stbi *s)
+{
+ stbi__uint32 z = get16le(s);
+ return z + (get16le(s) << 16);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// generic converter from built-in img_n to req_comp
+// individual types do this automatically as much as possible (e.g. jpeg
+// does all cases internally since it needs to colorspace convert anyway,
+// and it never has alpha, so very few cases ). png can automatically
+// interleave an alpha=255 channel, but falls back to this for other cases
+//
+// assume data buffer is malloced, so malloc a new one and free that one
+// only failure mode is malloc failing
+
+static stbi__uint8 compute_y(int r, int g, int b)
+{
+ return (stbi__uint8) (((r*77) + (g*150) + (29*b)) >> 8);
+}
+
+static unsigned char *convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)
+{
+ int i,j;
+ unsigned char *good;
+
+ if (req_comp == img_n) return data;
+ assert(req_comp >= 1 && req_comp <= 4);
+
+ good = (unsigned char *) malloc(req_comp * x * y);
+ if (good == NULL) {
+ free(data);
+ return epuc("outofmem", "Out of memory");
+ }
+
+ for (j=0; j < (int) y; ++j) {
+ unsigned char *src = data + j * x * img_n ;
+ unsigned char *dest = good + j * x * req_comp;
+
+ #define COMBO(a,b) ((a)*8+(b))
+ #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
+ // convert source image with img_n components to one with req_comp components;
+ // avoid switch per pixel, so use switch per scanline and massive macros
+ switch (COMBO(img_n, req_comp)) {
+ CASE(1,2) dest[0]=src[0], dest[1]=255; break;
+ CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break;
+ CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break;
+ CASE(2,1) dest[0]=src[0]; break;
+ CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break;
+ CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break;
+ CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break;
+ CASE(3,1) dest[0]=compute_y(src[0],src[1],src[2]); break;
+ CASE(3,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = 255; break;
+ CASE(4,1) dest[0]=compute_y(src[0],src[1],src[2]); break;
+ CASE(4,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break;
+ CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break;
+ default: assert(0);
+ }
+ #undef CASE
+ }
+
+ free(data);
+ return good;
+}
+
+#ifndef STBI_NO_HDR
+static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
+{
+ int i,k,n;
+ float *output = (float *) malloc(x * y * comp * sizeof(float));
+ if (output == NULL) { free(data); return epf("outofmem", "Out of memory"); }
+ // compute number of non-alpha components
+ if (comp & 1) n = comp; else n = comp-1;
+ for (i=0; i < x*y; ++i) {
+ for (k=0; k < n; ++k) {
+ output[i*comp + k] = (float) pow(data[i*comp+k]/255.0f, l2h_gamma) * l2h_scale;
+ }
+ if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f;
+ }
+ free(data);
+ return output;
+}
+
+#define float2int(x) ((int) (x))
+static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp)
+{
+ int i,k,n;
+ stbi_uc *output = (stbi_uc *) malloc(x * y * comp);
+ if (output == NULL) { free(data); return epuc("outofmem", "Out of memory"); }
+ // compute number of non-alpha components
+ if (comp & 1) n = comp; else n = comp-1;
+ for (i=0; i < x*y; ++i) {
+ for (k=0; k < n; ++k) {
+ float z = (float) pow(data[i*comp+k]*h2l_scale_i, h2l_gamma_i) * 255 + 0.5f;
+ if (z < 0) z = 0;
+ if (z > 255) z = 255;
+ output[i*comp + k] = (stbi__uint8) float2int(z);
+ }
+ if (k < comp) {
+ float z = data[i*comp+k] * 255 + 0.5f;
+ if (z < 0) z = 0;
+ if (z > 255) z = 255;
+ output[i*comp + k] = (stbi__uint8) float2int(z);
+ }
+ }
+ free(data);
+ return output;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation)
+//
+// simple implementation
+// - channel subsampling of at most 2 in each dimension
+// - doesn't support delayed output of y-dimension
+// - simple interface (only one output format: 8-bit interleaved RGB)
+// - doesn't try to recover corrupt jpegs
+// - doesn't allow partial loading, loading multiple at once
+// - still fast on x86 (copying globals into locals doesn't help x86)
+// - allocates lots of intermediate memory (full size of all components)
+// - non-interleaved case requires this anyway
+// - allows good upsampling (see next)
+// high-quality
+// - upsampled channels are bilinearly interpolated, even across blocks
+// - quality integer IDCT derived from IJG's 'slow'
+// performance
+// - fast huffman; reasonable integer IDCT
+// - uses a lot of intermediate memory, could cache poorly
+// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4
+// stb_jpeg: 1.34 seconds (MSVC6, default release build)
+// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro)
+// IJL11.dll: 1.08 seconds (compiled by intel)
+// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG)
+// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro)
+
+// huffman decoding acceleration
+#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache
+
+typedef struct
+{
+ stbi__uint8 fast[1 << FAST_BITS];
+ // weirdly, repacking this into AoS is a 10% speed loss, instead of a win
+ stbi__uint16 code[256];
+ stbi__uint8 values[256];
+ stbi__uint8 size[257];
+ unsigned int maxcode[18];
+ int delta[17]; // old 'firstsymbol' - old 'firstcode'
+} huffman;
+
+typedef struct
+{
+ #ifdef STBI_SIMD
+ unsigned short dequant2[4][64];
+ #endif
+ stbi *s;
+ huffman huff_dc[4];
+ huffman huff_ac[4];
+ stbi__uint8 dequant[4][64];
+
+// sizes for components, interleaved MCUs
+ int img_h_max, img_v_max;
+ int img_mcu_x, img_mcu_y;
+ int img_mcu_w, img_mcu_h;
+
+// definition of jpeg image component
+ struct
+ {
+ int id;
+ int h,v;
+ int tq;
+ int hd,ha;
+ int dc_pred;
+
+ int x,y,w2,h2;
+ stbi__uint8 *data;
+ void *raw_data;
+ stbi__uint8 *linebuf;
+ } img_comp[4];
+
+ stbi__uint32 code_buffer; // jpeg entropy-coded buffer
+ int code_bits; // number of valid bits
+ unsigned char marker; // marker seen while filling entropy buffer
+ int nomore; // flag if we saw a marker so must stop
+
+ int scan_n, order[4];
+ int restart_interval, todo;
+} jpeg;
+
+static int build_huffman(huffman *h, int *count)
+{
+ int i,j,k=0,code;
+ // build size list for each symbol (from JPEG spec)
+ for (i=0; i < 16; ++i)
+ for (j=0; j < count[i]; ++j)
+ h->size[k++] = (stbi__uint8) (i+1);
+ h->size[k] = 0;
+
+ // compute actual symbols (from jpeg spec)
+ code = 0;
+ k = 0;
+ for(j=1; j <= 16; ++j) {
+ // compute delta to add to code to compute symbol id
+ h->delta[j] = k - code;
+ if (h->size[k] == j) {
+ while (h->size[k] == j)
+ h->code[k++] = (stbi__uint16) (code++);
+ if (code-1 >= (1 << j)) return e("bad code lengths","Corrupt JPEG");
+ }
+ // compute largest code + 1 for this size, preshifted as needed later
+ h->maxcode[j] = code << (16-j);
+ code <<= 1;
+ }
+ h->maxcode[j] = 0xffffffff;
+
+ // build non-spec acceleration table; 255 is flag for not-accelerated
+ memset(h->fast, 255, 1 << FAST_BITS);
+ for (i=0; i < k; ++i) {
+ int s = h->size[i];
+ if (s <= FAST_BITS) {
+ int c = h->code[i] << (FAST_BITS-s);
+ int m = 1 << (FAST_BITS-s);
+ for (j=0; j < m; ++j) {
+ h->fast[c+j] = (stbi__uint8) i;
+ }
+ }
+ }
+ return 1;
+}
+
+static void grow_buffer_unsafe(jpeg *j)
+{
+ do {
+ int b = j->nomore ? 0 : get8(j->s);
+ if (b == 0xff) {
+ int c = get8(j->s);
+ if (c != 0) {
+ j->marker = (unsigned char) c;
+ j->nomore = 1;
+ return;
+ }
+ }
+ j->code_buffer |= b << (24 - j->code_bits);
+ j->code_bits += 8;
+ } while (j->code_bits <= 24);
+}
+
+// (1 << n) - 1
+static stbi__uint32 bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};
+
+// decode a jpeg huffman value from the bitstream
+stbi_inline static int decode(jpeg *j, huffman *h)
+{
+ unsigned int temp;
+ int c,k;
+
+ if (j->code_bits < 16) grow_buffer_unsafe(j);
+
+ // look at the top FAST_BITS and determine what symbol ID it is,
+ // if the code is <= FAST_BITS
+ c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+ k = h->fast[c];
+ if (k < 255) {
+ int s = h->size[k];
+ if (s > j->code_bits)
+ return -1;
+ j->code_buffer <<= s;
+ j->code_bits -= s;
+ return h->values[k];
+ }
+
+ // naive test is to shift the code_buffer down so k bits are
+ // valid, then test against maxcode. To speed this up, we've
+ // preshifted maxcode left so that it has (16-k) 0s at the
+ // end; in other words, regardless of the number of bits, it
+ // wants to be compared against something shifted to have 16;
+ // that way we don't need to shift inside the loop.
+ temp = j->code_buffer >> 16;
+ for (k=FAST_BITS+1 ; ; ++k)
+ if (temp < h->maxcode[k])
+ break;
+ if (k == 17) {
+ // error! code not found
+ j->code_bits -= 16;
+ return -1;
+ }
+
+ if (k > j->code_bits)
+ return -1;
+
+ // convert the huffman code to the symbol id
+ c = ((j->code_buffer >> (32 - k)) & bmask[k]) + h->delta[k];
+ assert((((j->code_buffer) >> (32 - h->size[c])) & bmask[h->size[c]]) == h->code[c]);
+
+ // convert the id to a symbol
+ j->code_bits -= k;
+ j->code_buffer <<= k;
+ return h->values[c];
+}
+
+// combined JPEG 'receive' and JPEG 'extend', since baseline
+// always extends everything it receives.
+stbi_inline static int extend_receive(jpeg *j, int n)
+{
+ unsigned int m = 1 << (n-1);
+ unsigned int k;
+ if (j->code_bits < n) grow_buffer_unsafe(j);
+
+ #if 1
+ k = stbi_lrot(j->code_buffer, n);
+ j->code_buffer = k & ~bmask[n];
+ k &= bmask[n];
+ j->code_bits -= n;
+ #else
+ k = (j->code_buffer >> (32 - n)) & bmask[n];
+ j->code_bits -= n;
+ j->code_buffer <<= n;
+ #endif
+ // the following test is probably a random branch that won't
+ // predict well. I tried to table accelerate it but failed.
+ // maybe it's compiling as a conditional move?
+ if (k < m)
+ return (-1 << n) + k + 1;
+ else
+ return k;
+}
+
+// given a value that's at position X in the zigzag stream,
+// where does it appear in the 8x8 matrix coded as row-major?
+static stbi__uint8 dezigzag[64+15] =
+{
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+ // let corrupt input sample past end
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63
+};
+
+// decode one 64-entry block--
+static int decode_block(jpeg *j, short data[64], huffman *hdc, huffman *hac, int b)
+{
+ int diff,dc,k;
+ int t = decode(j, hdc);
+ if (t < 0) return e("bad huffman code","Corrupt JPEG");
+
+ // 0 all the ac values now so we can do it 32-bits at a time
+ memset(data,0,64*sizeof(data[0]));
+
+ diff = t ? extend_receive(j, t) : 0;
+ dc = j->img_comp[b].dc_pred + diff;
+ j->img_comp[b].dc_pred = dc;
+ data[0] = (short) dc;
+
+ // decode AC components, see JPEG spec
+ k = 1;
+ do {
+ int r,s;
+ int rs = decode(j, hac);
+ if (rs < 0) return e("bad huffman code","Corrupt JPEG");
+ s = rs & 15;
+ r = rs >> 4;
+ if (s == 0) {
+ if (rs != 0xf0) break; // end block
+ k += 16;
+ } else {
+ k += r;
+ // decode into unzigzag'd location
+ data[dezigzag[k++]] = (short) extend_receive(j,s);
+ }
+ } while (k < 64);
+ return 1;
+}
+
+// take a -128..127 value and clamp it and convert to 0..255
+stbi_inline static stbi__uint8 clamp(int x)
+{
+ // trick to use a single test to catch both cases
+ if ((unsigned int) x > 255) {
+ if (x < 0) return 0;
+ if (x > 255) return 255;
+ }
+ return (stbi__uint8) x;
+}
+
+#define f2f(x) (int) (((x) * 4096 + 0.5))
+#define fsh(x) ((x) << 12)
+
+// derived from jidctint -- DCT_ISLOW
+#define IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \
+ int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \
+ p2 = s2; \
+ p3 = s6; \
+ p1 = (p2+p3) * f2f(0.5411961f); \
+ t2 = p1 + p3*f2f(-1.847759065f); \
+ t3 = p1 + p2*f2f( 0.765366865f); \
+ p2 = s0; \
+ p3 = s4; \
+ t0 = fsh(p2+p3); \
+ t1 = fsh(p2-p3); \
+ x0 = t0+t3; \
+ x3 = t0-t3; \
+ x1 = t1+t2; \
+ x2 = t1-t2; \
+ t0 = s7; \
+ t1 = s5; \
+ t2 = s3; \
+ t3 = s1; \
+ p3 = t0+t2; \
+ p4 = t1+t3; \
+ p1 = t0+t3; \
+ p2 = t1+t2; \
+ p5 = (p3+p4)*f2f( 1.175875602f); \
+ t0 = t0*f2f( 0.298631336f); \
+ t1 = t1*f2f( 2.053119869f); \
+ t2 = t2*f2f( 3.072711026f); \
+ t3 = t3*f2f( 1.501321110f); \
+ p1 = p5 + p1*f2f(-0.899976223f); \
+ p2 = p5 + p2*f2f(-2.562915447f); \
+ p3 = p3*f2f(-1.961570560f); \
+ p4 = p4*f2f(-0.390180644f); \
+ t3 += p1+p4; \
+ t2 += p2+p3; \
+ t1 += p2+p4; \
+ t0 += p1+p3;
+
+#ifdef STBI_SIMD
+typedef unsigned short stbi_dequantize_t;
+#else
+typedef stbi__uint8 stbi_dequantize_t;
+#endif
+
+// .344 seconds on 3*anemones.jpg
+static void idct_block(stbi__uint8 *out, int out_stride, short data[64], stbi_dequantize_t *dequantize)
+{
+ int i,val[64],*v=val;
+ stbi_dequantize_t *dq = dequantize;
+ stbi__uint8 *o;
+ short *d = data;
+
+ // columns
+ for (i=0; i < 8; ++i,++d,++dq, ++v) {
+ // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
+ if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
+ && d[40]==0 && d[48]==0 && d[56]==0) {
+ // no shortcut 0 seconds
+ // (1|2|3|4|5|6|7)==0 0 seconds
+ // all separate -0.047 seconds
+ // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds
+ int dcterm = d[0] * dq[0] << 2;
+ v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
+ } else {
+ IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24],
+ d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56])
+ // constants scaled things up by 1<<12; let's bring them back
+ // down, but keep 2 extra bits of precision
+ x0 += 512; x1 += 512; x2 += 512; x3 += 512;
+ v[ 0] = (x0+t3) >> 10;
+ v[56] = (x0-t3) >> 10;
+ v[ 8] = (x1+t2) >> 10;
+ v[48] = (x1-t2) >> 10;
+ v[16] = (x2+t1) >> 10;
+ v[40] = (x2-t1) >> 10;
+ v[24] = (x3+t0) >> 10;
+ v[32] = (x3-t0) >> 10;
+ }
+ }
+
+ for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) {
+ // no fast case since the first 1D IDCT spread components out
+ IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])
+ // constants scaled things up by 1<<12, plus we had 1<<2 from first
+ // loop, plus horizontal and vertical each scale by sqrt(8) so together
+ // we've got an extra 1<<3, so 1<<17 total we need to remove.
+ // so we want to round that, which means adding 0.5 * 1<<17,
+ // aka 65536. Also, we'll end up with -128 to 127 that we want
+ // to encode as 0..255 by adding 128, so we'll add that before the shift
+ x0 += 65536 + (128<<17);
+ x1 += 65536 + (128<<17);
+ x2 += 65536 + (128<<17);
+ x3 += 65536 + (128<<17);
+ // tried computing the shifts into temps, or'ing the temps to see
+ // if any were out of range, but that was slower
+ o[0] = clamp((x0+t3) >> 17);
+ o[7] = clamp((x0-t3) >> 17);
+ o[1] = clamp((x1+t2) >> 17);
+ o[6] = clamp((x1-t2) >> 17);
+ o[2] = clamp((x2+t1) >> 17);
+ o[5] = clamp((x2-t1) >> 17);
+ o[3] = clamp((x3+t0) >> 17);
+ o[4] = clamp((x3-t0) >> 17);
+ }
+}
+
+#ifdef STBI_SIMD
+static stbi_idct_8x8 stbi_idct_installed = idct_block;
+
+void stbi_install_idct(stbi_idct_8x8 func)
+{
+ stbi_idct_installed = func;
+}
+#endif
+
+#define MARKER_none 0xff
+// if there's a pending marker from the entropy stream, return that
+// otherwise, fetch from the stream and get a marker. if there's no
+// marker, return 0xff, which is never a valid marker value
+static stbi__uint8 get_marker(jpeg *j)
+{
+ stbi__uint8 x;
+ if (j->marker != MARKER_none) { x = j->marker; j->marker = MARKER_none; return x; }
+ x = get8u(j->s);
+ if (x != 0xff) return MARKER_none;
+ while (x == 0xff)
+ x = get8u(j->s);
+ return x;
+}
+
+// in each scan, we'll have scan_n components, and the order
+// of the components is specified by order[]
+#define RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7)
+
+// after a restart interval, reset the entropy decoder and
+// the dc prediction
+static void reset(jpeg *j)
+{
+ j->code_bits = 0;
+ j->code_buffer = 0;
+ j->nomore = 0;
+ j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0;
+ j->marker = MARKER_none;
+ j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;
+ // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,
+ // since we don't even allow 1<<30 pixels
+}
+
+static int parse_entropy_coded_data(jpeg *z)
+{
+ reset(z);
+ if (z->scan_n == 1) {
+ int i,j;
+ #ifdef STBI_SIMD
+ __declspec(align(16))
+ #endif
+ short data[64];
+ int n = z->order[0];
+ // non-interleaved data, we just need to process one block at a time,
+ // in trivial scanline order
+ // number of blocks to do just depends on how many actual "pixels" this
+ // component has, independent of interleaved MCU blocking and such
+ int w = (z->img_comp[n].x+7) >> 3;
+ int h = (z->img_comp[n].y+7) >> 3;
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; ++i) {
+ if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0;
+ #ifdef STBI_SIMD
+ stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]);
+ #else
+ idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]);
+ #endif
+ // every data block is an MCU, so countdown the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) grow_buffer_unsafe(z);
+ // if it's NOT a restart, then just bail, so we get corrupt data
+ // rather than no data
+ if (!RESTART(z->marker)) return 1;
+ reset(z);
+ }
+ }
+ }
+ } else { // interleaved!
+ int i,j,k,x,y;
+ short data[64];
+ for (j=0; j < z->img_mcu_y; ++j) {
+ for (i=0; i < z->img_mcu_x; ++i) {
+ // scan an interleaved mcu... process scan_n components in order
+ for (k=0; k < z->scan_n; ++k) {
+ int n = z->order[k];
+ // scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component
+ for (y=0; y < z->img_comp[n].v; ++y) {
+ for (x=0; x < z->img_comp[n].h; ++x) {
+ int x2 = (i*z->img_comp[n].h + x)*8;
+ int y2 = (j*z->img_comp[n].v + y)*8;
+ if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0;
+ #ifdef STBI_SIMD
+ stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]);
+ #else
+ idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]);
+ #endif
+ }
+ }
+ }
+ // after all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) grow_buffer_unsafe(z);
+ // if it's NOT a restart, then just bail, so we get corrupt data
+ // rather than no data
+ if (!RESTART(z->marker)) return 1;
+ reset(z);
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+static int process_marker(jpeg *z, int m)
+{
+ int L;
+ switch (m) {
+ case MARKER_none: // no marker found
+ return e("expected marker","Corrupt JPEG");
+
+ case 0xC2: // SOF - progressive
+ return e("progressive jpeg","JPEG format not supported (progressive)");
+
+ case 0xDD: // DRI - specify restart interval
+ if (get16(z->s) != 4) return e("bad DRI len","Corrupt JPEG");
+ z->restart_interval = get16(z->s);
+ return 1;
+
+ case 0xDB: // DQT - define quantization table
+ L = get16(z->s)-2;
+ while (L > 0) {
+ int q = get8(z->s);
+ int p = q >> 4;
+ int t = q & 15,i;
+ if (p != 0) return e("bad DQT type","Corrupt JPEG");
+ if (t > 3) return e("bad DQT table","Corrupt JPEG");
+ for (i=0; i < 64; ++i)
+ z->dequant[t][dezigzag[i]] = get8u(z->s);
+ #ifdef STBI_SIMD
+ for (i=0; i < 64; ++i)
+ z->dequant2[t][i] = z->dequant[t][i];
+ #endif
+ L -= 65;
+ }
+ return L==0;
+
+ case 0xC4: // DHT - define huffman table
+ L = get16(z->s)-2;
+ while (L > 0) {
+ stbi__uint8 *v;
+ int sizes[16],i,n=0;
+ int q = get8(z->s);
+ int tc = q >> 4;
+ int th = q & 15;
+ if (tc > 1 || th > 3) return e("bad DHT header","Corrupt JPEG");
+ for (i=0; i < 16; ++i) {
+ sizes[i] = get8(z->s);
+ n += sizes[i];
+ }
+ L -= 17;
+ if (tc == 0) {
+ if (!build_huffman(z->huff_dc+th, sizes)) return 0;
+ v = z->huff_dc[th].values;
+ } else {
+ if (!build_huffman(z->huff_ac+th, sizes)) return 0;
+ v = z->huff_ac[th].values;
+ }
+ for (i=0; i < n; ++i)
+ v[i] = get8u(z->s);
+ L -= n;
+ }
+ return L==0;
+ }
+ // check for comment block or APP blocks
+ if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {
+ skip(z->s, get16(z->s)-2);
+ return 1;
+ }
+ return 0;
+}
+
+// after we see SOS
+static int process_scan_header(jpeg *z)
+{
+ int i;
+ int Ls = get16(z->s);
+ z->scan_n = get8(z->s);
+ if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return e("bad SOS component count","Corrupt JPEG");
+ if (Ls != 6+2*z->scan_n) return e("bad SOS len","Corrupt JPEG");
+ for (i=0; i < z->scan_n; ++i) {
+ int id = get8(z->s), which;
+ int q = get8(z->s);
+ for (which = 0; which < z->s->img_n; ++which)
+ if (z->img_comp[which].id == id)
+ break;
+ if (which == z->s->img_n) return 0;
+ z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return e("bad DC huff","Corrupt JPEG");
+ z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return e("bad AC huff","Corrupt JPEG");
+ z->order[i] = which;
+ }
+ if (get8(z->s) != 0) return e("bad SOS","Corrupt JPEG");
+ get8(z->s); // should be 63, but might be 0
+ if (get8(z->s) != 0) return e("bad SOS","Corrupt JPEG");
+
+ return 1;
+}
+
+static int process_frame_header(jpeg *z, int scan)
+{
+ stbi *s = z->s;
+ int Lf,p,i,q, h_max=1,v_max=1,c;
+ Lf = get16(s); if (Lf < 11) return e("bad SOF len","Corrupt JPEG"); // JPEG
+ p = get8(s); if (p != 8) return e("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline
+ s->img_y = get16(s); if (s->img_y == 0) return e("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG
+ s->img_x = get16(s); if (s->img_x == 0) return e("0 width","Corrupt JPEG"); // JPEG requires
+ c = get8(s);
+ if (c != 3 && c != 1) return e("bad component count","Corrupt JPEG"); // JFIF requires
+ s->img_n = c;
+ for (i=0; i < c; ++i) {
+ z->img_comp[i].data = NULL;
+ z->img_comp[i].linebuf = NULL;
+ }
+
+ if (Lf != 8+3*s->img_n) return e("bad SOF len","Corrupt JPEG");
+
+ for (i=0; i < s->img_n; ++i) {
+ z->img_comp[i].id = get8(s);
+ if (z->img_comp[i].id != i+1) // JFIF requires
+ if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files!
+ return e("bad component ID","Corrupt JPEG");
+ q = get8(s);
+ z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return e("bad H","Corrupt JPEG");
+ z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return e("bad V","Corrupt JPEG");
+ z->img_comp[i].tq = get8(s); if (z->img_comp[i].tq > 3) return e("bad TQ","Corrupt JPEG");
+ }
+
+ if (scan != SCAN_load) return 1;
+
+ if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode");
+
+ for (i=0; i < s->img_n; ++i) {
+ if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;
+ if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;
+ }
+
+ // compute interleaved mcu info
+ z->img_h_max = h_max;
+ z->img_v_max = v_max;
+ z->img_mcu_w = h_max * 8;
+ z->img_mcu_h = v_max * 8;
+ z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;
+ z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;
+
+ for (i=0; i < s->img_n; ++i) {
+ // number of effective pixels (e.g. for non-interleaved MCU)
+ z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;
+ z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;
+ // to simplify generation, we'll allocate enough memory to decode
+ // the bogus oversized data from using interleaved MCUs and their
+ // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't
+ // discard the extra data until colorspace conversion
+ z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;
+ z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;
+ z->img_comp[i].raw_data = malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15);
+ if (z->img_comp[i].raw_data == NULL) {
+ for(--i; i >= 0; --i) {
+ free(z->img_comp[i].raw_data);
+ z->img_comp[i].data = NULL;
+ }
+ return e("outofmem", "Out of memory");
+ }
+ // align blocks for installable-idct using mmx/sse
+ z->img_comp[i].data = (stbi__uint8*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);
+ z->img_comp[i].linebuf = NULL;
+ }
+
+ return 1;
+}
+
+// use comparisons since in some cases we handle more than one case (e.g. SOF)
+#define DNL(x) ((x) == 0xdc)
+#define SOI(x) ((x) == 0xd8)
+#define EOI(x) ((x) == 0xd9)
+#define SOF(x) ((x) == 0xc0 || (x) == 0xc1)
+#define SOS(x) ((x) == 0xda)
+
+static int decode_jpeg_header(jpeg *z, int scan)
+{
+ int m;
+ z->marker = MARKER_none; // initialize cached marker to empty
+ m = get_marker(z);
+ if (!SOI(m)) return e("no SOI","Corrupt JPEG");
+ if (scan == SCAN_type) return 1;
+ m = get_marker(z);
+ while (!SOF(m)) {
+ if (!process_marker(z,m)) return 0;
+ m = get_marker(z);
+ while (m == MARKER_none) {
+ // some files have extra padding after their blocks, so ok, we'll scan
+ if (at_eof(z->s)) return e("no SOF", "Corrupt JPEG");
+ m = get_marker(z);
+ }
+ }
+ if (!process_frame_header(z, scan)) return 0;
+ return 1;
+}
+
+static int decode_jpeg_image(jpeg *j)
+{
+ int m;
+ j->restart_interval = 0;
+ if (!decode_jpeg_header(j, SCAN_load)) return 0;
+ m = get_marker(j);
+ while (!EOI(m)) {
+ if (SOS(m)) {
+ if (!process_scan_header(j)) return 0;
+ if (!parse_entropy_coded_data(j)) return 0;
+ if (j->marker == MARKER_none ) {
+ // handle 0s at the end of image data from IP Kamera 9060
+ while (!at_eof(j->s)) {
+ int x = get8(j->s);
+ if (x == 255) {
+ j->marker = get8u(j->s);
+ break;
+ } else if (x != 0) {
+ return 0;
+ }
+ }
+ // if we reach eof without hitting a marker, get_marker() below will fail and we'll eventually return 0
+ }
+ } else {
+ if (!process_marker(j, m)) return 0;
+ }
+ m = get_marker(j);
+ }
+ return 1;
+}
+
+// static jfif-centered resampling (across block boundaries)
+
+typedef stbi__uint8 *(*resample_row_func)(stbi__uint8 *out, stbi__uint8 *in0, stbi__uint8 *in1,
+ int w, int hs);
+
+#define div4(x) ((stbi__uint8) ((x) >> 2))
+
+static stbi__uint8 *resample_row_1(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs)
+{
+ STBI_NOTUSED(out);
+ STBI_NOTUSED(in_far);
+ STBI_NOTUSED(w);
+ STBI_NOTUSED(hs);
+ return in_near;
+}
+
+static stbi__uint8* resample_row_v_2(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs)
+{
+ // need to generate two samples vertically for every one in input
+ int i;
+ STBI_NOTUSED(hs);
+ for (i=0; i < w; ++i)
+ out[i] = div4(3*in_near[i] + in_far[i] + 2);
+ return out;
+}
+
+static stbi__uint8* resample_row_h_2(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs)
+{
+ // need to generate two samples horizontally for every one in input
+ int i;
+ stbi__uint8 *input = in_near;
+
+ if (w == 1) {
+ // if only one sample, can't do any interpolation
+ out[0] = out[1] = input[0];
+ return out;
+ }
+
+ out[0] = input[0];
+ out[1] = div4(input[0]*3 + input[1] + 2);
+ for (i=1; i < w-1; ++i) {
+ int n = 3*input[i]+2;
+ out[i*2+0] = div4(n+input[i-1]);
+ out[i*2+1] = div4(n+input[i+1]);
+ }
+ out[i*2+0] = div4(input[w-2]*3 + input[w-1] + 2);
+ out[i*2+1] = input[w-1];
+
+ STBI_NOTUSED(in_far);
+ STBI_NOTUSED(hs);
+
+ return out;
+}
+
+#define div16(x) ((stbi__uint8) ((x) >> 4))
+
+static stbi__uint8 *resample_row_hv_2(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs)
+{
+ // need to generate 2x2 samples for every one in input
+ int i,t0,t1;
+ if (w == 1) {
+ out[0] = out[1] = div4(3*in_near[0] + in_far[0] + 2);
+ return out;
+ }
+
+ t1 = 3*in_near[0] + in_far[0];
+ out[0] = div4(t1+2);
+ for (i=1; i < w; ++i) {
+ t0 = t1;
+ t1 = 3*in_near[i]+in_far[i];
+ out[i*2-1] = div16(3*t0 + t1 + 8);
+ out[i*2 ] = div16(3*t1 + t0 + 8);
+ }
+ out[w*2-1] = div4(t1+2);
+
+ STBI_NOTUSED(hs);
+
+ return out;
+}
+
+static stbi__uint8 *resample_row_generic(stbi__uint8 *out, stbi__uint8 *in_near, stbi__uint8 *in_far, int w, int hs)
+{
+ // resample with nearest-neighbor
+ int i,j;
+ STBI_NOTUSED(in_far);
+ for (i=0; i < w; ++i)
+ for (j=0; j < hs; ++j)
+ out[i*hs+j] = in_near[i];
+ return out;
+}
+
+#define float2fixed(x) ((int) ((x) * 65536 + 0.5))
+
+// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro)
+// VC6 without processor=Pro is generating multiple LEAs per multiply!
+static void YCbCr_to_RGB_row(stbi__uint8 *out, const stbi__uint8 *y, const stbi__uint8 *pcb, const stbi__uint8 *pcr, int count, int step)
+{
+ int i;
+ for (i=0; i < count; ++i) {
+ int y_fixed = (y[i] << 16) + 32768; // rounding
+ int r,g,b;
+ int cr = pcr[i] - 128;
+ int cb = pcb[i] - 128;
+ r = y_fixed + cr*float2fixed(1.40200f);
+ g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f);
+ b = y_fixed + cb*float2fixed(1.77200f);
+ r >>= 16;
+ g >>= 16;
+ b >>= 16;
+ if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+ if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+ if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+ out[0] = (stbi__uint8)r;
+ out[1] = (stbi__uint8)g;
+ out[2] = (stbi__uint8)b;
+ out[3] = 255;
+ out += step;
+ }
+}
+
+#ifdef STBI_SIMD
+static stbi_YCbCr_to_RGB_run stbi_YCbCr_installed = YCbCr_to_RGB_row;
+
+void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func)
+{
+ stbi_YCbCr_installed = func;
+}
+#endif
+
+
+// clean up the temporary component buffers
+static void cleanup_jpeg(jpeg *j)
+{
+ int i;
+ for (i=0; i < j->s->img_n; ++i) {
+ if (j->img_comp[i].data) {
+ free(j->img_comp[i].raw_data);
+ j->img_comp[i].data = NULL;
+ }
+ if (j->img_comp[i].linebuf) {
+ free(j->img_comp[i].linebuf);
+ j->img_comp[i].linebuf = NULL;
+ }
+ }
+}
+
+typedef struct
+{
+ resample_row_func resample;
+ stbi__uint8 *line0,*line1;
+ int hs,vs; // expansion factor in each axis
+ int w_lores; // horizontal pixels pre-expansion
+ int ystep; // how far through vertical expansion we are
+ int ypos; // which pre-expansion row we're on
+} stbi_resample;
+
+static stbi__uint8 *load_jpeg_image(jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
+{
+ int n, decode_n;
+ // validate req_comp
+ if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error");
+ z->s->img_n = 0;
+
+ // load a jpeg image from whichever source
+ if (!decode_jpeg_image(z)) { cleanup_jpeg(z); return NULL; }
+
+ // determine actual number of components to generate
+ n = req_comp ? req_comp : z->s->img_n;
+
+ if (z->s->img_n == 3 && n < 3)
+ decode_n = 1;
+ else
+ decode_n = z->s->img_n;
+
+ // resample and color-convert
+ {
+ int k;
+ unsigned int i,j;
+ stbi__uint8 *output;
+ stbi__uint8 *coutput[4];
+
+ stbi_resample res_comp[4];
+
+ for (k=0; k < decode_n; ++k) {
+ stbi_resample *r = &res_comp[k];
+
+ // allocate line buffer big enough for upsampling off the edges
+ // with upsample factor of 4
+ z->img_comp[k].linebuf = (stbi__uint8 *) malloc(z->s->img_x + 3);
+ if (!z->img_comp[k].linebuf) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); }
+
+ r->hs = z->img_h_max / z->img_comp[k].h;
+ r->vs = z->img_v_max / z->img_comp[k].v;
+ r->ystep = r->vs >> 1;
+ r->w_lores = (z->s->img_x + r->hs-1) / r->hs;
+ r->ypos = 0;
+ r->line0 = r->line1 = z->img_comp[k].data;
+
+ if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;
+ else if (r->hs == 1 && r->vs == 2) r->resample = resample_row_v_2;
+ else if (r->hs == 2 && r->vs == 1) r->resample = resample_row_h_2;
+ else if (r->hs == 2 && r->vs == 2) r->resample = resample_row_hv_2;
+ else r->resample = resample_row_generic;
+ }
+
+ // can't error after this so, this is safe
+ output = (stbi__uint8 *) malloc(n * z->s->img_x * z->s->img_y + 1);
+ if (!output) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); }
+
+ // now go ahead and resample
+ for (j=0; j < z->s->img_y; ++j) {
+ stbi__uint8 *out = output + n * z->s->img_x * j;
+ for (k=0; k < decode_n; ++k) {
+ stbi_resample *r = &res_comp[k];
+ int y_bot = r->ystep >= (r->vs >> 1);
+ coutput[k] = r->resample(z->img_comp[k].linebuf,
+ y_bot ? r->line1 : r->line0,
+ y_bot ? r->line0 : r->line1,
+ r->w_lores, r->hs);
+ if (++r->ystep >= r->vs) {
+ r->ystep = 0;
+ r->line0 = r->line1;
+ if (++r->ypos < z->img_comp[k].y)
+ r->line1 += z->img_comp[k].w2;
+ }
+ }
+ if (n >= 3) {
+ stbi__uint8 *y = coutput[0];
+ if (z->s->img_n == 3) {
+ #ifdef STBI_SIMD
+ stbi_YCbCr_installed(out, y, coutput[1], coutput[2], z->s->img_x, n);
+ #else
+ YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s->img_x, n);
+ #endif
+ } else
+ for (i=0; i < z->s->img_x; ++i) {
+ out[0] = out[1] = out[2] = y[i];
+ out[3] = 255; // not used if n==3
+ out += n;
+ }
+ } else {
+ stbi__uint8 *y = coutput[0];
+ if (n == 1)
+ for (i=0; i < z->s->img_x; ++i) out[i] = y[i];
+ else
+ for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255;
+ }
+ }
+ cleanup_jpeg(z);
+ *out_x = z->s->img_x;
+ *out_y = z->s->img_y;
+ if (comp) *comp = z->s->img_n; // report original components, not output
+ return output;
+ }
+}
+
+static unsigned char *stbi_jpeg_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ jpeg j;
+ j.s = s;
+ return load_jpeg_image(&j, x,y,comp,req_comp);
+}
+
+static int stbi_jpeg_test(stbi *s)
+{
+ int r;
+ jpeg j;
+ j.s = s;
+ r = decode_jpeg_header(&j, SCAN_type);
+ stbi_rewind(s);
+ return r;
+}
+
+static int stbi_jpeg_info_raw(jpeg *j, int *x, int *y, int *comp)
+{
+ if (!decode_jpeg_header(j, SCAN_header)) {
+ stbi_rewind( j->s );
+ return 0;
+ }
+ if (x) *x = j->s->img_x;
+ if (y) *y = j->s->img_y;
+ if (comp) *comp = j->s->img_n;
+ return 1;
+}
+
+static int stbi_jpeg_info(stbi *s, int *x, int *y, int *comp)
+{
+ jpeg j;
+ j.s = s;
+ return stbi_jpeg_info_raw(&j, x, y, comp);
+}
+
+// public domain zlib decode v0.2 Sean Barrett 2006-11-18
+// simple implementation
+// - all input must be provided in an upfront buffer
+// - all output is written to a single output buffer (can malloc/realloc)
+// performance
+// - fast huffman
+
+// fast-way is faster to check than jpeg huffman, but slow way is slower
+#define ZFAST_BITS 9 // accelerate all cases in default tables
+#define ZFAST_MASK ((1 << ZFAST_BITS) - 1)
+
+// zlib-style huffman encoding
+// (jpegs packs from left, zlib from right, so can't share code)
+typedef struct
+{
+ stbi__uint16 fast[1 << ZFAST_BITS];
+ stbi__uint16 firstcode[16];
+ int maxcode[17];
+ stbi__uint16 firstsymbol[16];
+ stbi__uint8 size[288];
+ stbi__uint16 value[288];
+} zhuffman;
+
+stbi_inline static int bitreverse16(int n)
+{
+ n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
+ n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
+ n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
+ n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
+ return n;
+}
+
+stbi_inline static int bit_reverse(int v, int bits)
+{
+ assert(bits <= 16);
+ // to bit reverse n bits, reverse 16 and shift
+ // e.g. 11 bits, bit reverse and shift away 5
+ return bitreverse16(v) >> (16-bits);
+}
+
+static int zbuild_huffman(zhuffman *z, stbi__uint8 *sizelist, int num)
+{
+ int i,k=0;
+ int code, next_code[16], sizes[17];
+
+ // DEFLATE spec for generating codes
+ memset(sizes, 0, sizeof(sizes));
+ memset(z->fast, 255, sizeof(z->fast));
+ for (i=0; i < num; ++i)
+ ++sizes[sizelist[i]];
+ sizes[0] = 0;
+ for (i=1; i < 16; ++i)
+ assert(sizes[i] <= (1 << i));
+ code = 0;
+ for (i=1; i < 16; ++i) {
+ next_code[i] = code;
+ z->firstcode[i] = (stbi__uint16) code;
+ z->firstsymbol[i] = (stbi__uint16) k;
+ code = (code + sizes[i]);
+ if (sizes[i])
+ if (code-1 >= (1 << i)) return e("bad codelengths","Corrupt JPEG");
+ z->maxcode[i] = code << (16-i); // preshift for inner loop
+ code <<= 1;
+ k += sizes[i];
+ }
+ z->maxcode[16] = 0x10000; // sentinel
+ for (i=0; i < num; ++i) {
+ int s = sizelist[i];
+ if (s) {
+ int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];
+ z->size[c] = (stbi__uint8)s;
+ z->value[c] = (stbi__uint16)i;
+ if (s <= ZFAST_BITS) {
+ int k = bit_reverse(next_code[s],s);
+ while (k < (1 << ZFAST_BITS)) {
+ z->fast[k] = (stbi__uint16) c;
+ k += (1 << s);
+ }
+ }
+ ++next_code[s];
+ }
+ }
+ return 1;
+}
+
+// zlib-from-memory implementation for PNG reading
+// because PNG allows splitting the zlib stream arbitrarily,
+// and it's annoying structurally to have PNG call ZLIB call PNG,
+// we require PNG read all the IDATs and combine them into a single
+// memory buffer
+
+typedef struct
+{
+ stbi__uint8 *zbuffer, *zbuffer_end;
+ int num_bits;
+ stbi__uint32 code_buffer;
+
+ char *zout;
+ char *zout_start;
+ char *zout_end;
+ int z_expandable;
+
+ zhuffman z_length, z_distance;
+} zbuf;
+
+stbi_inline static int zget8(zbuf *z)
+{
+ if (z->zbuffer >= z->zbuffer_end) return 0;
+ return *z->zbuffer++;
+}
+
+static void fill_bits(zbuf *z)
+{
+ do {
+ assert(z->code_buffer < (1U << z->num_bits));
+ z->code_buffer |= zget8(z) << z->num_bits;
+ z->num_bits += 8;
+ } while (z->num_bits <= 24);
+}
+
+stbi_inline static unsigned int zreceive(zbuf *z, int n)
+{
+ unsigned int k;
+ if (z->num_bits < n) fill_bits(z);
+ k = z->code_buffer & ((1 << n) - 1);
+ z->code_buffer >>= n;
+ z->num_bits -= n;
+ return k;
+}
+
+stbi_inline static int zhuffman_decode(zbuf *a, zhuffman *z)
+{
+ int b,s,k;
+ if (a->num_bits < 16) fill_bits(a);
+ b = z->fast[a->code_buffer & ZFAST_MASK];
+ if (b < 0xffff) {
+ s = z->size[b];
+ a->code_buffer >>= s;
+ a->num_bits -= s;
+ return z->value[b];
+ }
+
+ // not resolved by fast table, so compute it the slow way
+ // use jpeg approach, which requires MSbits at top
+ k = bit_reverse(a->code_buffer, 16);
+ for (s=ZFAST_BITS+1; ; ++s)
+ if (k < z->maxcode[s])
+ break;
+ if (s == 16) return -1; // invalid code!
+ // code size is s, so:
+ b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];
+ assert(z->size[b] == s);
+ a->code_buffer >>= s;
+ a->num_bits -= s;
+ return z->value[b];
+}
+
+static int expand(zbuf *z, int n) // need to make room for n bytes
+{
+ char *q;
+ int cur, limit;
+ if (!z->z_expandable) return e("output buffer limit","Corrupt PNG");
+ cur = (int) (z->zout - z->zout_start);
+ limit = (int) (z->zout_end - z->zout_start);
+ while (cur + n > limit)
+ limit *= 2;
+ q = (char *) realloc(z->zout_start, limit);
+ if (q == NULL) return e("outofmem", "Out of memory");
+ z->zout_start = q;
+ z->zout = q + cur;
+ z->zout_end = q + limit;
+ return 1;
+}
+
+static int 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 int 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 int 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 int 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 int parse_huffman_block(zbuf *a)
+{
+ for(;;) {
+ int z = zhuffman_decode(a, &a->z_length);
+ if (z < 256) {
+ if (z < 0) return e("bad huffman code","Corrupt PNG"); // error in huffman codes
+ if (a->zout >= a->zout_end) if (!expand(a, 1)) return 0;
+ *a->zout++ = (char) z;
+ } else {
+ stbi__uint8 *p;
+ int len,dist;
+ if (z == 256) return 1;
+ z -= 257;
+ len = length_base[z];
+ if (length_extra[z]) len += zreceive(a, length_extra[z]);
+ z = zhuffman_decode(a, &a->z_distance);
+ if (z < 0) return e("bad huffman code","Corrupt PNG");
+ dist = dist_base[z];
+ if (dist_extra[z]) dist += zreceive(a, dist_extra[z]);
+ if (a->zout - a->zout_start < dist) return e("bad dist","Corrupt PNG");
+ if (a->zout + len > a->zout_end) if (!expand(a, len)) return 0;
+ p = (stbi__uint8 *) (a->zout - dist);
+ while (len--)
+ *a->zout++ = *p++;
+ }
+ }
+}
+
+static int compute_huffman_codes(zbuf *a)
+{
+ static stbi__uint8 length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
+ zhuffman z_codelength;
+ stbi__uint8 lencodes[286+32+137];//padding for maximum single op
+ stbi__uint8 codelength_sizes[19];
+ int i,n;
+
+ int hlit = zreceive(a,5) + 257;
+ int hdist = zreceive(a,5) + 1;
+ int hclen = zreceive(a,4) + 4;
+
+ memset(codelength_sizes, 0, sizeof(codelength_sizes));
+ for (i=0; i < hclen; ++i) {
+ int s = zreceive(a,3);
+ codelength_sizes[length_dezigzag[i]] = (stbi__uint8) s;
+ }
+ if (!zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;
+
+ n = 0;
+ while (n < hlit + hdist) {
+ int c = zhuffman_decode(a, &z_codelength);
+ assert(c >= 0 && c < 19);
+ if (c < 16)
+ lencodes[n++] = (stbi__uint8) c;
+ else if (c == 16) {
+ c = zreceive(a,2)+3;
+ memset(lencodes+n, lencodes[n-1], c);
+ n += c;
+ } else if (c == 17) {
+ c = zreceive(a,3)+3;
+ memset(lencodes+n, 0, c);
+ n += c;
+ } else {
+ assert(c == 18);
+ c = zreceive(a,7)+11;
+ memset(lencodes+n, 0, c);
+ n += c;
+ }
+ }
+ if (n != hlit+hdist) return e("bad codelengths","Corrupt PNG");
+ if (!zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;
+ if (!zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;
+ return 1;
+}
+
+static int parse_uncompressed_block(zbuf *a)
+{
+ stbi__uint8 header[4];
+ int len,nlen,k;
+ if (a->num_bits & 7)
+ zreceive(a, a->num_bits & 7); // discard
+ // drain the bit-packed data into header
+ k = 0;
+ while (a->num_bits > 0) {
+ header[k++] = (stbi__uint8) (a->code_buffer & 255); // wtf this warns?
+ a->code_buffer >>= 8;
+ a->num_bits -= 8;
+ }
+ assert(a->num_bits == 0);
+ // now fill header the normal way
+ while (k < 4)
+ header[k++] = (stbi__uint8) zget8(a);
+ len = header[1] * 256 + header[0];
+ nlen = header[3] * 256 + header[2];
+ if (nlen != (len ^ 0xffff)) return e("zlib corrupt","Corrupt PNG");
+ if (a->zbuffer + len > a->zbuffer_end) return e("read past buffer","Corrupt PNG");
+ if (a->zout + len > a->zout_end)
+ if (!expand(a, len)) return 0;
+ memcpy(a->zout, a->zbuffer, len);
+ a->zbuffer += len;
+ a->zout += len;
+ return 1;
+}
+
+static int parse_zlib_header(zbuf *a)
+{
+ int cmf = zget8(a);
+ int cm = cmf & 15;
+ /* int cinfo = cmf >> 4; */
+ int flg = zget8(a);
+ if ((cmf*256+flg) % 31 != 0) return e("bad zlib header","Corrupt PNG"); // zlib spec
+ if (flg & 32) return e("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png
+ if (cm != 8) return e("bad compression","Corrupt PNG"); // DEFLATE required for png
+ // window = 1 << (8 + cinfo)... but who cares, we fully buffer output
+ return 1;
+}
+
+// @TODO: should statically initialize these for optimal thread safety
+static stbi__uint8 default_length[288], default_distance[32];
+static void init_defaults(void)
+{
+ int i; // use <= to match clearly with spec
+ for (i=0; i <= 143; ++i) default_length[i] = 8;
+ for ( ; i <= 255; ++i) default_length[i] = 9;
+ for ( ; i <= 279; ++i) default_length[i] = 7;
+ for ( ; i <= 287; ++i) default_length[i] = 8;
+
+ for (i=0; i <= 31; ++i) default_distance[i] = 5;
+}
+
+int stbi_png_partial; // a quick hack to only allow decoding some of a PNG... I should implement real streaming support instead
+static int parse_zlib(zbuf *a, int parse_header)
+{
+ int final, type;
+ if (parse_header)
+ if (!parse_zlib_header(a)) return 0;
+ a->num_bits = 0;
+ a->code_buffer = 0;
+ do {
+ final = zreceive(a,1);
+ type = zreceive(a,2);
+ if (type == 0) {
+ if (!parse_uncompressed_block(a)) return 0;
+ } else if (type == 3) {
+ return 0;
+ } else {
+ if (type == 1) {
+ // use fixed code lengths
+ if (!default_distance[31]) init_defaults();
+ if (!zbuild_huffman(&a->z_length , default_length , 288)) return 0;
+ if (!zbuild_huffman(&a->z_distance, default_distance, 32)) return 0;
+ } else {
+ if (!compute_huffman_codes(a)) return 0;
+ }
+ if (!parse_huffman_block(a)) return 0;
+ }
+ if (stbi_png_partial && a->zout - a->zout_start > 65536)
+ break;
+ } while (!final);
+ return 1;
+}
+
+static int do_zlib(zbuf *a, char *obuf, int olen, int exp, int parse_header)
+{
+ a->zout_start = obuf;
+ a->zout = obuf;
+ a->zout_end = obuf + olen;
+ a->z_expandable = exp;
+
+ return parse_zlib(a, parse_header);
+}
+
+char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)
+{
+ zbuf a;
+ char *p = (char *) malloc(initial_size);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi__uint8 *) buffer;
+ a.zbuffer_end = (stbi__uint8 *) buffer + len;
+ if (do_zlib(&a, p, initial_size, 1, 1)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ free(a.zout_start);
+ return NULL;
+ }
+}
+
+char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)
+{
+ return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
+}
+
+char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)
+{
+ zbuf a;
+ char *p = (char *) malloc(initial_size);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi__uint8 *) buffer;
+ a.zbuffer_end = (stbi__uint8 *) buffer + len;
+ if (do_zlib(&a, p, initial_size, 1, parse_header)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ free(a.zout_start);
+ return NULL;
+ }
+}
+
+int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)
+{
+ zbuf a;
+ a.zbuffer = (stbi__uint8 *) ibuffer;
+ a.zbuffer_end = (stbi__uint8 *) ibuffer + ilen;
+ if (do_zlib(&a, obuffer, olen, 0, 1))
+ return (int) (a.zout - a.zout_start);
+ else
+ return -1;
+}
+
+char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)
+{
+ zbuf a;
+ char *p = (char *) malloc(16384);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi__uint8 *) buffer;
+ a.zbuffer_end = (stbi__uint8 *) buffer+len;
+ if (do_zlib(&a, p, 16384, 1, 0)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ free(a.zout_start);
+ return NULL;
+ }
+}
+
+int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)
+{
+ zbuf a;
+ a.zbuffer = (stbi__uint8 *) ibuffer;
+ a.zbuffer_end = (stbi__uint8 *) ibuffer + ilen;
+ if (do_zlib(&a, obuffer, olen, 0, 0))
+ return (int) (a.zout - a.zout_start);
+ else
+ return -1;
+}
+
+// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18
+// simple implementation
+// - only 8-bit samples
+// - no CRC checking
+// - allocates lots of intermediate memory
+// - avoids problem of streaming data between subsystems
+// - avoids explicit window management
+// performance
+// - uses stb_zlib, a PD zlib implementation with fast huffman decoding
+
+
+typedef struct
+{
+ stbi__uint32 length;
+ stbi__uint32 type;
+} chunk;
+
+#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d))
+
+static chunk get_chunk_header(stbi *s)
+{
+ chunk c;
+ c.length = get32(s);
+ c.type = get32(s);
+ return c;
+}
+
+static int check_png_header(stbi *s)
+{
+ static stbi__uint8 png_sig[8] = { 137,80,78,71,13,10,26,10 };
+ int i;
+ for (i=0; i < 8; ++i)
+ if (get8u(s) != png_sig[i]) return e("bad png sig","Not a PNG");
+ return 1;
+}
+
+typedef struct
+{
+ stbi *s;
+ stbi__uint8 *idata, *expanded, *out;
+} png;
+
+
+enum {
+ F_none=0, F_sub=1, F_up=2, F_avg=3, F_paeth=4,
+ F_avg_first, F_paeth_first
+};
+
+static stbi__uint8 first_row_filter[5] =
+{
+ F_none, F_sub, F_none, F_avg_first, F_paeth_first
+};
+
+static int paeth(int a, int b, int c)
+{
+ int p = a + b - c;
+ int pa = abs(p-a);
+ int pb = abs(p-b);
+ int pc = abs(p-c);
+ if (pa <= pb && pa <= pc) return a;
+ if (pb <= pc) return b;
+ return c;
+}
+
+// create the png data from post-deflated data
+static int create_png_image_raw(png *a, stbi__uint8 *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y)
+{
+ stbi *s = a->s;
+ stbi__uint32 i,j,stride = x*out_n;
+ int k;
+ int img_n = s->img_n; // copy it into a local for later
+ assert(out_n == s->img_n || out_n == s->img_n+1);
+ if (stbi_png_partial) y = 1;
+ a->out = (stbi__uint8 *) malloc(x * y * out_n);
+ if (!a->out) return e("outofmem", "Out of memory");
+ if (!stbi_png_partial) {
+ if (s->img_x == x && s->img_y == y) {
+ if (raw_len != (img_n * x + 1) * y) return e("not enough pixels","Corrupt PNG");
+ } else { // interlaced:
+ if (raw_len < (img_n * x + 1) * y) return e("not enough pixels","Corrupt PNG");
+ }
+ }
+ for (j=0; j < y; ++j) {
+ stbi__uint8 *cur = a->out + stride*j;
+ stbi__uint8 *prior = cur - stride;
+ int filter = *raw++;
+ if (filter > 4) return e("invalid filter","Corrupt PNG");
+ // if first row, use special filter that doesn't sample previous row
+ if (j == 0) filter = first_row_filter[filter];
+ // handle first pixel explicitly
+ for (k=0; k < img_n; ++k) {
+ switch (filter) {
+ case F_none : cur[k] = raw[k]; break;
+ case F_sub : cur[k] = raw[k]; break;
+ case F_up : cur[k] = raw[k] + prior[k]; break;
+ case F_avg : cur[k] = raw[k] + (prior[k]>>1); break;
+ case F_paeth : cur[k] = (stbi__uint8) (raw[k] + paeth(0,prior[k],0)); break;
+ case F_avg_first : cur[k] = raw[k]; break;
+ case F_paeth_first: cur[k] = raw[k]; break;
+ }
+ }
+ if (img_n != out_n) cur[img_n] = 255;
+ raw += img_n;
+ cur += out_n;
+ prior += out_n;
+ // this is a little gross, so that we don't switch per-pixel or per-component
+ if (img_n == out_n) {
+ #define CASE(f) \
+ case f: \
+ for (i=x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n) \
+ for (k=0; k < img_n; ++k)
+ switch (filter) {
+ CASE(F_none) cur[k] = raw[k]; break;
+ CASE(F_sub) cur[k] = raw[k] + cur[k-img_n]; break;
+ CASE(F_up) cur[k] = raw[k] + prior[k]; break;
+ CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-img_n])>>1); break;
+ CASE(F_paeth) cur[k] = (stbi__uint8) (raw[k] + paeth(cur[k-img_n],prior[k],prior[k-img_n])); break;
+ CASE(F_avg_first) cur[k] = raw[k] + (cur[k-img_n] >> 1); break;
+ CASE(F_paeth_first) cur[k] = (stbi__uint8) (raw[k] + paeth(cur[k-img_n],0,0)); break;
+ }
+ #undef CASE
+ } else {
+ assert(img_n+1 == out_n);
+ #define CASE(f) \
+ case f: \
+ for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \
+ for (k=0; k < img_n; ++k)
+ switch (filter) {
+ CASE(F_none) cur[k] = raw[k]; break;
+ CASE(F_sub) cur[k] = raw[k] + cur[k-out_n]; break;
+ CASE(F_up) cur[k] = raw[k] + prior[k]; break;
+ CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-out_n])>>1); break;
+ CASE(F_paeth) cur[k] = (stbi__uint8) (raw[k] + paeth(cur[k-out_n],prior[k],prior[k-out_n])); break;
+ CASE(F_avg_first) cur[k] = raw[k] + (cur[k-out_n] >> 1); break;
+ CASE(F_paeth_first) cur[k] = (stbi__uint8) (raw[k] + paeth(cur[k-out_n],0,0)); break;
+ }
+ #undef CASE
+ }
+ }
+ return 1;
+}
+
+static int create_png_image(png *a, stbi__uint8 *raw, stbi__uint32 raw_len, int out_n, int interlaced)
+{
+ stbi__uint8 *final;
+ int p;
+ int save;
+ if (!interlaced)
+ return create_png_image_raw(a, raw, raw_len, out_n, a->s->img_x, a->s->img_y);
+ save = stbi_png_partial;
+ stbi_png_partial = 0;
+
+ // de-interlacing
+ final = (stbi__uint8 *) malloc(a->s->img_x * a->s->img_y * out_n);
+ for (p=0; p < 7; ++p) {
+ int xorig[] = { 0,4,0,2,0,1,0 };
+ int yorig[] = { 0,0,4,0,2,0,1 };
+ int xspc[] = { 8,8,4,4,2,2,1 };
+ int yspc[] = { 8,8,8,4,4,2,2 };
+ int i,j,x,y;
+ // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1
+ x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];
+ y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];
+ if (x && y) {
+ if (!create_png_image_raw(a, raw, raw_len, out_n, x, y)) {
+ free(final);
+ return 0;
+ }
+ for (j=0; j < y; ++j)
+ for (i=0; i < x; ++i)
+ memcpy(final + (j*yspc[p]+yorig[p])*a->s->img_x*out_n + (i*xspc[p]+xorig[p])*out_n,
+ a->out + (j*x+i)*out_n, out_n);
+ free(a->out);
+ raw += (x*out_n+1)*y;
+ raw_len -= (x*out_n+1)*y;
+ }
+ }
+ a->out = final;
+
+ stbi_png_partial = save;
+ return 1;
+}
+
+static int compute_transparency(png *z, stbi__uint8 tc[3], int out_n)
+{
+ stbi *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi__uint8 *p = z->out;
+
+ // compute color-based transparency, assuming we've
+ // already got 255 as the alpha value in the output
+ assert(out_n == 2 || out_n == 4);
+
+ if (out_n == 2) {
+ for (i=0; i < pixel_count; ++i) {
+ p[1] = (p[0] == tc[0] ? 0 : 255);
+ p += 2;
+ }
+ } else {
+ for (i=0; i < pixel_count; ++i) {
+ if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+ p[3] = 0;
+ p += 4;
+ }
+ }
+ return 1;
+}
+
+static int expand_palette(png *a, stbi__uint8 *palette, int len, int pal_img_n)
+{
+ stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
+ stbi__uint8 *p, *temp_out, *orig = a->out;
+
+ p = (stbi__uint8 *) malloc(pixel_count * pal_img_n);
+ if (p == NULL) return e("outofmem", "Out of memory");
+
+ // between here and free(out) below, exitting would leak
+ temp_out = p;
+
+ if (pal_img_n == 3) {
+ for (i=0; i < pixel_count; ++i) {
+ int n = orig[i]*4;
+ p[0] = palette[n ];
+ p[1] = palette[n+1];
+ p[2] = palette[n+2];
+ p += 3;
+ }
+ } else {
+ for (i=0; i < pixel_count; ++i) {
+ int n = orig[i]*4;
+ p[0] = palette[n ];
+ p[1] = palette[n+1];
+ p[2] = palette[n+2];
+ p[3] = palette[n+3];
+ p += 4;
+ }
+ }
+ free(a->out);
+ a->out = temp_out;
+
+ STBI_NOTUSED(len);
+
+ return 1;
+}
+
+static int stbi_unpremultiply_on_load = 0;
+static int stbi_de_iphone_flag = 0;
+
+void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
+{
+ stbi_unpremultiply_on_load = flag_true_if_should_unpremultiply;
+}
+void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
+{
+ stbi_de_iphone_flag = flag_true_if_should_convert;
+}
+
+static void stbi_de_iphone(png *z)
+{
+ stbi *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi__uint8 *p = z->out;
+
+ if (s->img_out_n == 3) { // convert bgr to rgb
+ for (i=0; i < pixel_count; ++i) {
+ stbi__uint8 t = p[0];
+ p[0] = p[2];
+ p[2] = t;
+ p += 3;
+ }
+ } else {
+ assert(s->img_out_n == 4);
+ if (stbi_unpremultiply_on_load) {
+ // convert bgr to rgb and unpremultiply
+ for (i=0; i < pixel_count; ++i) {
+ stbi__uint8 a = p[3];
+ stbi__uint8 t = p[0];
+ if (a) {
+ p[0] = p[2] * 255 / a;
+ p[1] = p[1] * 255 / a;
+ p[2] = t * 255 / a;
+ } else {
+ p[0] = p[2];
+ p[2] = t;
+ }
+ p += 4;
+ }
+ } else {
+ // convert bgr to rgb
+ for (i=0; i < pixel_count; ++i) {
+ stbi__uint8 t = p[0];
+ p[0] = p[2];
+ p[2] = t;
+ p += 4;
+ }
+ }
+ }
+}
+
+static int parse_png_file(png *z, int scan, int req_comp)
+{
+ stbi__uint8 palette[1024], pal_img_n=0;
+ stbi__uint8 has_trans=0, tc[3];
+ stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
+ int first=1,k,interlace=0, iphone=0;
+ stbi *s = z->s;
+
+ z->expanded = NULL;
+ z->idata = NULL;
+ z->out = NULL;
+
+ if (!check_png_header(s)) return 0;
+
+ if (scan == SCAN_type) return 1;
+
+ for (;;) {
+ chunk c = get_chunk_header(s);
+ switch (c.type) {
+ case PNG_TYPE('C','g','B','I'):
+ iphone = stbi_de_iphone_flag;
+ skip(s, c.length);
+ break;
+ case PNG_TYPE('I','H','D','R'): {
+ int depth,color,comp,filter;
+ if (!first) return e("multiple IHDR","Corrupt PNG");
+ first = 0;
+ if (c.length != 13) return e("bad IHDR len","Corrupt PNG");
+ s->img_x = get32(s); if (s->img_x > (1 << 24)) return e("too large","Very large image (corrupt?)");
+ s->img_y = get32(s); if (s->img_y > (1 << 24)) return e("too large","Very large image (corrupt?)");
+ depth = get8(s); if (depth != 8) return e("8bit only","PNG not supported: 8-bit only");
+ color = get8(s); if (color > 6) return e("bad ctype","Corrupt PNG");
+ if (color == 3) pal_img_n = 3; else if (color & 1) return e("bad ctype","Corrupt PNG");
+ comp = get8(s); if (comp) return e("bad comp method","Corrupt PNG");
+ filter= get8(s); if (filter) return e("bad filter method","Corrupt PNG");
+ interlace = get8(s); if (interlace>1) return e("bad interlace method","Corrupt PNG");
+ if (!s->img_x || !s->img_y) return e("0-pixel image","Corrupt PNG");
+ if (!pal_img_n) {
+ s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
+ if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode");
+ if (scan == SCAN_header) return 1;
+ } else {
+ // if paletted, then pal_n is our final components, and
+ // img_n is # components to decompress/filter.
+ s->img_n = 1;
+ if ((1 << 30) / s->img_x / 4 < s->img_y) return e("too large","Corrupt PNG");
+ // if SCAN_header, have to scan to see if we have a tRNS
+ }
+ break;
+ }
+
+ case PNG_TYPE('P','L','T','E'): {
+ if (first) return e("first not IHDR", "Corrupt PNG");
+ if (c.length > 256*3) return e("invalid PLTE","Corrupt PNG");
+ pal_len = c.length / 3;
+ if (pal_len * 3 != c.length) return e("invalid PLTE","Corrupt PNG");
+ for (i=0; i < pal_len; ++i) {
+ palette[i*4+0] = get8u(s);
+ palette[i*4+1] = get8u(s);
+ palette[i*4+2] = get8u(s);
+ palette[i*4+3] = 255;
+ }
+ break;
+ }
+
+ case PNG_TYPE('t','R','N','S'): {
+ if (first) return e("first not IHDR", "Corrupt PNG");
+ if (z->idata) return e("tRNS after IDAT","Corrupt PNG");
+ if (pal_img_n) {
+ if (scan == SCAN_header) { s->img_n = 4; return 1; }
+ if (pal_len == 0) return e("tRNS before PLTE","Corrupt PNG");
+ if (c.length > pal_len) return e("bad tRNS len","Corrupt PNG");
+ pal_img_n = 4;
+ for (i=0; i < c.length; ++i)
+ palette[i*4+3] = get8u(s);
+ } else {
+ if (!(s->img_n & 1)) return e("tRNS with alpha","Corrupt PNG");
+ if (c.length != (stbi__uint32) s->img_n*2) return e("bad tRNS len","Corrupt PNG");
+ has_trans = 1;
+ for (k=0; k < s->img_n; ++k)
+ tc[k] = (stbi__uint8) get16(s); // non 8-bit images will be larger
+ }
+ break;
+ }
+
+ case PNG_TYPE('I','D','A','T'): {
+ if (first) return e("first not IHDR", "Corrupt PNG");
+ if (pal_img_n && !pal_len) return e("no PLTE","Corrupt PNG");
+ if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; }
+ if (ioff + c.length > idata_limit) {
+ stbi__uint8 *p;
+ if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
+ while (ioff + c.length > idata_limit)
+ idata_limit *= 2;
+ p = (stbi__uint8 *) realloc(z->idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory");
+ z->idata = p;
+ }
+ if (!getn(s, z->idata+ioff,c.length)) return e("outofdata","Corrupt PNG");
+ ioff += c.length;
+ break;
+ }
+
+ case PNG_TYPE('I','E','N','D'): {
+ stbi__uint32 raw_len;
+ if (first) return e("first not IHDR", "Corrupt PNG");
+ if (scan != SCAN_load) return 1;
+ if (z->idata == NULL) return e("no IDAT","Corrupt PNG");
+ z->expanded = (stbi__uint8 *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, 16384, (int *) &raw_len, !iphone);
+ if (z->expanded == NULL) return 0; // zlib should set error
+ free(z->idata); z->idata = NULL;
+ if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
+ s->img_out_n = s->img_n+1;
+ else
+ s->img_out_n = s->img_n;
+ if (!create_png_image(z, z->expanded, raw_len, s->img_out_n, interlace)) return 0;
+ if (has_trans)
+ if (!compute_transparency(z, tc, s->img_out_n)) return 0;
+ if (iphone && s->img_out_n > 2)
+ stbi_de_iphone(z);
+ if (pal_img_n) {
+ // pal_img_n == 3 or 4
+ s->img_n = pal_img_n; // record the actual colors we had
+ s->img_out_n = pal_img_n;
+ if (req_comp >= 3) s->img_out_n = req_comp;
+ if (!expand_palette(z, palette, pal_len, s->img_out_n))
+ return 0;
+ }
+ free(z->expanded); z->expanded = NULL;
+ return 1;
+ }
+
+ default:
+ // if critical, fail
+ if (first) return e("first not IHDR", "Corrupt PNG");
+ if ((c.type & (1 << 29)) == 0) {
+ #ifndef STBI_NO_FAILURE_STRINGS
+ // not threadsafe
+ static char invalid_chunk[] = "XXXX chunk not known";
+ invalid_chunk[0] = (stbi__uint8) (c.type >> 24);
+ invalid_chunk[1] = (stbi__uint8) (c.type >> 16);
+ invalid_chunk[2] = (stbi__uint8) (c.type >> 8);
+ invalid_chunk[3] = (stbi__uint8) (c.type >> 0);
+ #endif
+ return e(invalid_chunk, "PNG not supported: unknown chunk type");
+ }
+ skip(s, c.length);
+ break;
+ }
+ // end of chunk, read and skip CRC
+ get32(s);
+ }
+}
+
+static unsigned char *do_png(png *p, int *x, int *y, int *n, int req_comp)
+{
+ unsigned char *result=NULL;
+ if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error");
+ if (parse_png_file(p, SCAN_load, req_comp)) {
+ result = p->out;
+ p->out = NULL;
+ if (req_comp && req_comp != p->s->img_out_n) {
+ result = convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+ p->s->img_out_n = req_comp;
+ if (result == NULL) return result;
+ }
+ *x = p->s->img_x;
+ *y = p->s->img_y;
+ if (n) *n = p->s->img_n;
+ }
+ free(p->out); p->out = NULL;
+ free(p->expanded); p->expanded = NULL;
+ free(p->idata); p->idata = NULL;
+
+ return result;
+}
+
+static unsigned char *stbi_png_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ png p;
+ p.s = s;
+ return do_png(&p, x,y,comp,req_comp);
+}
+
+static int stbi_png_test(stbi *s)
+{
+ int r;
+ r = check_png_header(s);
+ stbi_rewind(s);
+ return r;
+}
+
+static int stbi_png_info_raw(png *p, int *x, int *y, int *comp)
+{
+ if (!parse_png_file(p, SCAN_header, 0)) {
+ stbi_rewind( p->s );
+ return 0;
+ }
+ if (x) *x = p->s->img_x;
+ if (y) *y = p->s->img_y;
+ if (comp) *comp = p->s->img_n;
+ return 1;
+}
+
+static int stbi_png_info(stbi *s, int *x, int *y, int *comp)
+{
+ png p;
+ p.s = s;
+ return stbi_png_info_raw(&p, x, y, comp);
+}
+
+// Microsoft/Windows BMP image
+
+static int bmp_test(stbi *s)
+{
+ int sz;
+ if (get8(s) != 'B') return 0;
+ if (get8(s) != 'M') return 0;
+ get32le(s); // discard filesize
+ get16le(s); // discard reserved
+ get16le(s); // discard reserved
+ get32le(s); // discard data offset
+ sz = get32le(s);
+ if (sz == 12 || sz == 40 || sz == 56 || sz == 108) return 1;
+ return 0;
+}
+
+static int stbi_bmp_test(stbi *s)
+{
+ int r = bmp_test(s);
+ stbi_rewind(s);
+ return r;
+}
+
+
+// returns 0..31 for the highest set bit
+static int high_bit(unsigned int z)
+{
+ int n=0;
+ if (z == 0) return -1;
+ if (z >= 0x10000) n += 16, z >>= 16;
+ if (z >= 0x00100) n += 8, z >>= 8;
+ if (z >= 0x00010) n += 4, z >>= 4;
+ if (z >= 0x00004) n += 2, z >>= 2;
+ if (z >= 0x00002) n += 1, z >>= 1;
+ return n;
+}
+
+static int bitcount(unsigned int a)
+{
+ a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2
+ a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4
+ a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits
+ a = (a + (a >> 8)); // max 16 per 8 bits
+ a = (a + (a >> 16)); // max 32 per 8 bits
+ return a & 0xff;
+}
+
+static int shiftsigned(int v, int shift, int bits)
+{
+ int result;
+ int z=0;
+
+ if (shift < 0) v <<= -shift;
+ else v >>= shift;
+ result = v;
+
+ z = bits;
+ while (z < 8) {
+ result += v >> z;
+ z += bits;
+ }
+ return result;
+}
+
+static stbi_uc *bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__uint8 *out;
+ unsigned int mr=0,mg=0,mb=0,ma=0, fake_a=0;
+ stbi_uc pal[256][4];
+ int psize=0,i,j,compress=0,width;
+ int bpp, flip_vertically, pad, target, offset, hsz;
+ if (get8(s) != 'B' || get8(s) != 'M') return epuc("not BMP", "Corrupt BMP");
+ get32le(s); // discard filesize
+ get16le(s); // discard reserved
+ get16le(s); // discard reserved
+ offset = get32le(s);
+ hsz = get32le(s);
+ if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) return epuc("unknown BMP", "BMP type not supported: unknown");
+ if (hsz == 12) {
+ s->img_x = get16le(s);
+ s->img_y = get16le(s);
+ } else {
+ s->img_x = get32le(s);
+ s->img_y = get32le(s);
+ }
+ if (get16le(s) != 1) return epuc("bad BMP", "bad BMP");
+ bpp = get16le(s);
+ if (bpp == 1) return epuc("monochrome", "BMP type not supported: 1-bit");
+ flip_vertically = ((int) s->img_y) > 0;
+ s->img_y = abs((int) s->img_y);
+ if (hsz == 12) {
+ if (bpp < 24)
+ psize = (offset - 14 - 24) / 3;
+ } else {
+ compress = get32le(s);
+ if (compress == 1 || compress == 2) return epuc("BMP RLE", "BMP type not supported: RLE");
+ get32le(s); // discard sizeof
+ get32le(s); // discard hres
+ get32le(s); // discard vres
+ get32le(s); // discard colorsused
+ get32le(s); // discard max important
+ if (hsz == 40 || hsz == 56) {
+ if (hsz == 56) {
+ get32le(s);
+ get32le(s);
+ get32le(s);
+ get32le(s);
+ }
+ if (bpp == 16 || bpp == 32) {
+ mr = mg = mb = 0;
+ if (compress == 0) {
+ if (bpp == 32) {
+ mr = 0xffu << 16;
+ mg = 0xffu << 8;
+ mb = 0xffu << 0;
+ ma = 0xffu << 24;
+ fake_a = 1; // @TODO: check for cases like alpha value is all 0 and switch it to 255
+ STBI_NOTUSED(fake_a);
+ } else {
+ mr = 31u << 10;
+ mg = 31u << 5;
+ mb = 31u << 0;
+ }
+ } else if (compress == 3) {
+ mr = get32le(s);
+ mg = get32le(s);
+ mb = get32le(s);
+ // not documented, but generated by photoshop and handled by mspaint
+ if (mr == mg && mg == mb) {
+ // ?!?!?
+ return epuc("bad BMP", "bad BMP");
+ }
+ } else
+ return epuc("bad BMP", "bad BMP");
+ }
+ } else {
+ assert(hsz == 108);
+ mr = get32le(s);
+ mg = get32le(s);
+ mb = get32le(s);
+ ma = get32le(s);
+ get32le(s); // discard color space
+ for (i=0; i < 12; ++i)
+ get32le(s); // discard color space parameters
+ }
+ if (bpp < 16)
+ psize = (offset - 14 - hsz) >> 2;
+ }
+ s->img_n = ma ? 4 : 3;
+ if (req_comp && req_comp >= 3) // we can directly decode 3 or 4
+ target = req_comp;
+ else
+ target = s->img_n; // if they want monochrome, we'll post-convert
+ out = (stbi_uc *) malloc(target * s->img_x * s->img_y);
+ if (!out) return epuc("outofmem", "Out of memory");
+ if (bpp < 16) {
+ int z=0;
+ if (psize == 0 || psize > 256) { free(out); return epuc("invalid", "Corrupt BMP"); }
+ for (i=0; i < psize; ++i) {
+ pal[i][2] = get8u(s);
+ pal[i][1] = get8u(s);
+ pal[i][0] = get8u(s);
+ if (hsz != 12) get8(s);
+ pal[i][3] = 255;
+ }
+ skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4));
+ if (bpp == 4) width = (s->img_x + 1) >> 1;
+ else if (bpp == 8) width = s->img_x;
+ else { free(out); return epuc("bad bpp", "Corrupt BMP"); }
+ pad = (-width)&3;
+ for (j=0; j < (int) s->img_y; ++j) {
+ for (i=0; i < (int) s->img_x; i += 2) {
+ int v=get8(s),v2=0;
+ if (bpp == 4) {
+ v2 = v & 15;
+ v >>= 4;
+ }
+ out[z++] = pal[v][0];
+ out[z++] = pal[v][1];
+ out[z++] = pal[v][2];
+ if (target == 4) out[z++] = 255;
+ if (i+1 == (int) s->img_x) break;
+ v = (bpp == 8) ? get8(s) : v2;
+ out[z++] = pal[v][0];
+ out[z++] = pal[v][1];
+ out[z++] = pal[v][2];
+ if (target == 4) out[z++] = 255;
+ }
+ skip(s, pad);
+ }
+ } else {
+ int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;
+ int z = 0;
+ int easy=0;
+ skip(s, offset - 14 - hsz);
+ if (bpp == 24) width = 3 * s->img_x;
+ else if (bpp == 16) width = 2*s->img_x;
+ else /* bpp = 32 and pad = 0 */ width=0;
+ pad = (-width) & 3;
+ if (bpp == 24) {
+ easy = 1;
+ } else if (bpp == 32) {
+ if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
+ easy = 2;
+ }
+ if (!easy) {
+ if (!mr || !mg || !mb) { free(out); return epuc("bad masks", "Corrupt BMP"); }
+ // right shift amt to put high bit in position #7
+ rshift = high_bit(mr)-7; rcount = bitcount(mr);
+ gshift = high_bit(mg)-7; gcount = bitcount(mg);
+ bshift = high_bit(mb)-7; bcount = bitcount(mb);
+ ashift = high_bit(ma)-7; acount = bitcount(ma);
+ }
+ for (j=0; j < (int) s->img_y; ++j) {
+ if (easy) {
+ for (i=0; i < (int) s->img_x; ++i) {
+ int a;
+ out[z+2] = get8u(s);
+ out[z+1] = get8u(s);
+ out[z+0] = get8u(s);
+ z += 3;
+ a = (easy == 2 ? get8(s) : 255);
+ if (target == 4) out[z++] = (stbi__uint8) a;
+ }
+ } else {
+ for (i=0; i < (int) s->img_x; ++i) {
+ stbi__uint32 v = (stbi__uint32) (bpp == 16 ? get16le(s) : get32le(s));
+ int a;
+ out[z++] = (stbi__uint8) shiftsigned(v & mr, rshift, rcount);
+ out[z++] = (stbi__uint8) shiftsigned(v & mg, gshift, gcount);
+ out[z++] = (stbi__uint8) shiftsigned(v & mb, bshift, bcount);
+ a = (ma ? shiftsigned(v & ma, ashift, acount) : 255);
+ if (target == 4) out[z++] = (stbi__uint8) a;
+ }
+ }
+ skip(s, pad);
+ }
+ }
+ if (flip_vertically) {
+ stbi_uc t;
+ for (j=0; j < (int) s->img_y>>1; ++j) {
+ stbi_uc *p1 = out + j *s->img_x*target;
+ stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;
+ for (i=0; i < (int) s->img_x*target; ++i) {
+ t = p1[i], p1[i] = p2[i], p2[i] = t;
+ }
+ }
+ }
+
+ if (req_comp && req_comp != target) {
+ out = convert_format(out, target, req_comp, s->img_x, s->img_y);
+ if (out == NULL) return out; // convert_format frees input on failure
+ }
+
+ *x = s->img_x;
+ *y = s->img_y;
+ if (comp) *comp = s->img_n;
+ return out;
+}
+
+static stbi_uc *stbi_bmp_load(stbi *s,int *x, int *y, int *comp, int req_comp)
+{
+ return bmp_load(s, x,y,comp,req_comp);
+}
+
+
+// Targa Truevision - TGA
+// by Jonathan Dummer
+
+static int tga_info(stbi *s, int *x, int *y, int *comp)
+{
+ int tga_w, tga_h, tga_comp;
+ int sz;
+ get8u(s); // discard Offset
+ sz = get8u(s); // color type
+ if( sz > 1 ) {
+ stbi_rewind(s);
+ return 0; // only RGB or indexed allowed
+ }
+ sz = get8u(s); // image type
+ // only RGB or grey allowed, +/- RLE
+ if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0;
+ skip(s,9);
+ tga_w = get16le(s);
+ if( tga_w < 1 ) {
+ stbi_rewind(s);
+ return 0; // test width
+ }
+ tga_h = get16le(s);
+ if( tga_h < 1 ) {
+ stbi_rewind(s);
+ return 0; // test height
+ }
+ sz = get8(s); // bits per pixel
+ // only RGB or RGBA or grey allowed
+ if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) {
+ stbi_rewind(s);
+ return 0;
+ }
+ tga_comp = sz;
+ if (x) *x = tga_w;
+ if (y) *y = tga_h;
+ if (comp) *comp = tga_comp / 8;
+ return 1; // seems to have passed everything
+}
+
+int stbi_tga_info(stbi *s, int *x, int *y, int *comp)
+{
+ return tga_info(s, x, y, comp);
+}
+
+static int tga_test(stbi *s)
+{
+ int sz;
+ get8u(s); // discard Offset
+ sz = get8u(s); // color type
+ if ( sz > 1 ) return 0; // only RGB or indexed allowed
+ sz = get8u(s); // image type
+ if ( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE
+ get16(s); // discard palette start
+ get16(s); // discard palette length
+ get8(s); // discard bits per palette color entry
+ get16(s); // discard x origin
+ get16(s); // discard y origin
+ if ( get16(s) < 1 ) return 0; // test width
+ if ( get16(s) < 1 ) return 0; // test height
+ sz = get8(s); // bits per pixel
+ if ( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) return 0; // only RGB or RGBA or grey allowed
+ return 1; // seems to have passed everything
+}
+
+static int stbi_tga_test(stbi *s)
+{
+ int res = tga_test(s);
+ stbi_rewind(s);
+ return res;
+}
+
+static stbi_uc *tga_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ // read in the TGA header stuff
+ int tga_offset = get8u(s);
+ int tga_indexed = get8u(s);
+ int tga_image_type = get8u(s);
+ int tga_is_RLE = 0;
+ int tga_palette_start = get16le(s);
+ int tga_palette_len = get16le(s);
+ int tga_palette_bits = get8u(s);
+ int tga_x_origin = get16le(s);
+ int tga_y_origin = get16le(s);
+ int tga_width = get16le(s);
+ int tga_height = get16le(s);
+ int tga_bits_per_pixel = get8u(s);
+ int tga_comp = tga_bits_per_pixel / 8;
+ int tga_inverted = get8u(s);
+ // image data
+ unsigned char *tga_data;
+ unsigned char *tga_palette = NULL;
+ int i, j;
+ unsigned char raw_data[4];
+ int RLE_count = 0;
+ int RLE_repeating = 0;
+ int read_next_pixel = 1;
+
+ // do a tiny bit of precessing
+ if ( tga_image_type >= 8 )
+ {
+ tga_image_type -= 8;
+ tga_is_RLE = 1;
+ }
+ /* int tga_alpha_bits = tga_inverted & 15; */
+ tga_inverted = 1 - ((tga_inverted >> 5) & 1);
+
+ // error check
+ if ( //(tga_indexed) ||
+ (tga_width < 1) || (tga_height < 1) ||
+ (tga_image_type < 1) || (tga_image_type > 3) ||
+ ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) &&
+ (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32))
+ )
+ {
+ return NULL; // we don't report this as a bad TGA because we don't even know if it's TGA
+ }
+
+ // If I'm paletted, then I'll use the number of bits from the palette
+ if ( tga_indexed )
+ {
+ tga_comp = tga_palette_bits / 8;
+ }
+
+ // tga info
+ *x = tga_width;
+ *y = tga_height;
+ if (comp) *comp = tga_comp;
+
+ tga_data = (unsigned char*)malloc( tga_width * tga_height * req_comp );
+ if (!tga_data) return epuc("outofmem", "Out of memory");
+
+ // skip to the data's starting position (offset usually = 0)
+ skip(s, tga_offset );
+
+ if ( !tga_indexed && !tga_is_RLE) {
+ for (i=0; i < tga_height; ++i) {
+ int y = tga_inverted ? tga_height -i - 1 : i;
+ stbi__uint8 *tga_row = tga_data + y*tga_width*tga_comp;
+ getn(s, tga_row, tga_width * tga_comp);
+ }
+ } else {
+ // do I need to load a palette?
+ if ( tga_indexed)
+ {
+ // any data to skip? (offset usually = 0)
+ skip(s, tga_palette_start );
+ // load the palette
+ tga_palette = (unsigned char*)malloc( tga_palette_len * tga_palette_bits / 8 );
+ if (!tga_palette) {
+ free(tga_data);
+ return epuc("outofmem", "Out of memory");
+ }
+ if (!getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 )) {
+ free(tga_data);
+ free(tga_palette);
+ return epuc("bad palette", "Corrupt TGA");
+ }
+ }
+ // load the data
+ for (i=0; i < tga_width * tga_height; ++i)
+ {
+ // if I'm in RLE mode, do I need to get a RLE chunk?
+ if ( tga_is_RLE )
+ {
+ if ( RLE_count == 0 )
+ {
+ // yep, get the next byte as a RLE command
+ int RLE_cmd = get8u(s);
+ RLE_count = 1 + (RLE_cmd & 127);
+ RLE_repeating = RLE_cmd >> 7;
+ read_next_pixel = 1;
+ } else if ( !RLE_repeating )
+ {
+ read_next_pixel = 1;
+ }
+ } else
+ {
+ read_next_pixel = 1;
+ }
+ // OK, if I need to read a pixel, do it now
+ if ( read_next_pixel )
+ {
+ // load however much data we did have
+ if ( tga_indexed )
+ {
+ // read in 1 byte, then perform the lookup
+ int pal_idx = get8u(s);
+ if ( pal_idx >= tga_palette_len )
+ {
+ // invalid index
+ pal_idx = 0;
+ }
+ pal_idx *= tga_bits_per_pixel / 8;
+ for (j = 0; j*8 < tga_bits_per_pixel; ++j)
+ {
+ raw_data[j] = tga_palette[pal_idx+j];
+ }
+ } else
+ {
+ // read in the data raw
+ for (j = 0; j*8 < tga_bits_per_pixel; ++j)
+ {
+ raw_data[j] = get8u(s);
+ }
+ }
+ // clear the reading flag for the next pixel
+ read_next_pixel = 0;
+ } // end of reading a pixel
+
+ // copy data
+ for (j = 0; j < tga_comp; ++j)
+ tga_data[i*tga_comp+j] = raw_data[j];
+
+ // in case we're in RLE mode, keep counting down
+ --RLE_count;
+ }
+ // do I need to invert the image?
+ if ( tga_inverted )
+ {
+ for (j = 0; j*2 < tga_height; ++j)
+ {
+ int index1 = j * tga_width * req_comp;
+ int index2 = (tga_height - 1 - j) * tga_width * req_comp;
+ for (i = tga_width * req_comp; i > 0; --i)
+ {
+ unsigned char temp = tga_data[index1];
+ tga_data[index1] = tga_data[index2];
+ tga_data[index2] = temp;
+ ++index1;
+ ++index2;
+ }
+ }
+ }
+ // clear my palette, if I had one
+ if ( tga_palette != NULL )
+ {
+ free( tga_palette );
+ }
+ }
+
+ // swap RGB
+ if (tga_comp >= 3)
+ {
+ unsigned char* tga_pixel = tga_data;
+ for (i=0; i < tga_width * tga_height; ++i)
+ {
+ unsigned char temp = tga_pixel[0];
+ tga_pixel[0] = tga_pixel[2];
+ tga_pixel[2] = temp;
+ tga_pixel += tga_comp;
+ }
+ }
+
+ // convert to target component count
+ if (req_comp && req_comp != tga_comp)
+ tga_data = convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);
+
+ // the things I do to get rid of an error message, and yet keep
+ // Microsoft's C compilers happy... [8^(
+ tga_palette_start = tga_palette_len = tga_palette_bits =
+ tga_x_origin = tga_y_origin = 0;
+ // OK, done
+ return tga_data;
+}
+
+static stbi_uc *stbi_tga_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ return tga_load(s,x,y,comp,req_comp);
+}
+
+
+// *************************************************************************************************
+// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB
+
+static int psd_test(stbi *s)
+{
+ if (get32(s) != 0x38425053) return 0; // "8BPS"
+ else return 1;
+}
+
+static int stbi_psd_test(stbi *s)
+{
+ int r = psd_test(s);
+ stbi_rewind(s);
+ return r;
+}
+
+static stbi_uc *psd_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ int pixelCount;
+ int channelCount, compression;
+ int channel, i, count, len;
+ int w,h;
+ stbi__uint8 *out;
+
+ // Check identifier
+ if (get32(s) != 0x38425053) // "8BPS"
+ return epuc("not PSD", "Corrupt PSD image");
+
+ // Check file type version.
+ if (get16(s) != 1)
+ return epuc("wrong version", "Unsupported version of PSD image");
+
+ // Skip 6 reserved bytes.
+ skip(s, 6 );
+
+ // Read the number of channels (R, G, B, A, etc).
+ channelCount = get16(s);
+ if (channelCount < 0 || channelCount > 16)
+ return epuc("wrong channel count", "Unsupported number of channels in PSD image");
+
+ // Read the rows and columns of the image.
+ h = get32(s);
+ w = get32(s);
+
+ // Make sure the depth is 8 bits.
+ if (get16(s) != 8)
+ return epuc("unsupported bit depth", "PSD bit depth is not 8 bit");
+
+ // Make sure the color mode is RGB.
+ // Valid options are:
+ // 0: Bitmap
+ // 1: Grayscale
+ // 2: Indexed color
+ // 3: RGB color
+ // 4: CMYK color
+ // 7: Multichannel
+ // 8: Duotone
+ // 9: Lab color
+ if (get16(s) != 3)
+ return epuc("wrong color format", "PSD is not in RGB color format");
+
+ // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.)
+ skip(s,get32(s) );
+
+ // Skip the image resources. (resolution, pen tool paths, etc)
+ skip(s, get32(s) );
+
+ // Skip the reserved data.
+ skip(s, get32(s) );
+
+ // Find out if the data is compressed.
+ // Known values:
+ // 0: no compression
+ // 1: RLE compressed
+ compression = get16(s);
+ if (compression > 1)
+ return epuc("bad compression", "PSD has an unknown compression format");
+
+ // Create the destination image.
+ out = (stbi_uc *) malloc(4 * w*h);
+ if (!out) return epuc("outofmem", "Out of memory");
+ pixelCount = w*h;
+
+ // Initialize the data to zero.
+ //memset( out, 0, pixelCount * 4 );
+
+ // Finally, the image data.
+ if (compression) {
+ // RLE as used by .PSD and .TIFF
+ // Loop until you get the number of unpacked bytes you are expecting:
+ // Read the next source byte into n.
+ // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
+ // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
+ // Else if n is 128, noop.
+ // Endloop
+
+ // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data,
+ // which we're going to just skip.
+ skip(s, h * channelCount * 2 );
+
+ // Read the RLE data by channel.
+ for (channel = 0; channel < 4; channel++) {
+ stbi__uint8 *p;
+
+ p = out+channel;
+ if (channel >= channelCount) {
+ // Fill this channel with default data.
+ for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4;
+ } else {
+ // Read the RLE data.
+ count = 0;
+ while (count < pixelCount) {
+ len = get8(s);
+ if (len == 128) {
+ // No-op.
+ } else if (len < 128) {
+ // Copy next len+1 bytes literally.
+ len++;
+ count += len;
+ while (len) {
+ *p = get8u(s);
+ p += 4;
+ len--;
+ }
+ } else if (len > 128) {
+ stbi__uint8 val;
+ // Next -len+1 bytes in the dest are replicated from next source byte.
+ // (Interpret len as a negative 8-bit int.)
+ len ^= 0x0FF;
+ len += 2;
+ val = get8u(s);
+ count += len;
+ while (len) {
+ *p = val;
+ p += 4;
+ len--;
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+ // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...)
+ // where each channel consists of an 8-bit value for each pixel in the image.
+
+ // Read the data by channel.
+ for (channel = 0; channel < 4; channel++) {
+ stbi__uint8 *p;
+
+ p = out + channel;
+ if (channel > channelCount) {
+ // Fill this channel with default data.
+ for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4;
+ } else {
+ // Read the data.
+ for (i = 0; i < pixelCount; i++)
+ *p = get8u(s), p += 4;
+ }
+ }
+ }
+
+ if (req_comp && req_comp != 4) {
+ out = convert_format(out, 4, req_comp, w, h);
+ if (out == NULL) return out; // convert_format frees input on failure
+ }
+
+ if (comp) *comp = channelCount;
+ *y = h;
+ *x = w;
+
+ return out;
+}
+
+static stbi_uc *stbi_psd_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ return psd_load(s,x,y,comp,req_comp);
+}
+
+// *************************************************************************************************
+// Softimage PIC loader
+// by Tom Seddon
+//
+// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format
+// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/
+
+static int pic_is4(stbi *s,const char *str)
+{
+ int i;
+ for (i=0; i<4; ++i)
+ if (get8(s) != (stbi_uc)str[i])
+ return 0;
+
+ return 1;
+}
+
+static int pic_test(stbi *s)
+{
+ int i;
+
+ if (!pic_is4(s,"\x53\x80\xF6\x34"))
+ return 0;
+
+ for(i=0;i<84;++i)
+ get8(s);
+
+ if (!pic_is4(s,"PICT"))
+ return 0;
+
+ return 1;
+}
+
+typedef struct
+{
+ stbi_uc size,type,channel;
+} pic_packet_t;
+
+static stbi_uc *pic_readval(stbi *s, int channel, stbi_uc *dest)
+{
+ int mask=0x80, i;
+
+ for (i=0; i<4; ++i, mask>>=1) {
+ if (channel & mask) {
+ if (at_eof(s)) return epuc("bad file","PIC file too short");
+ dest[i]=get8u(s);
+ }
+ }
+
+ return dest;
+}
+
+static void pic_copyval(int channel,stbi_uc *dest,const stbi_uc *src)
+{
+ int mask=0x80,i;
+
+ for (i=0;i<4; ++i, mask>>=1)
+ if (channel&mask)
+ dest[i]=src[i];
+}
+
+static stbi_uc *pic_load2(stbi *s,int width,int height,int *comp, stbi_uc *result)
+{
+ int act_comp=0,num_packets=0,y,chained;
+ pic_packet_t packets[10];
+
+ // this will (should...) cater for even some bizarre stuff like having data
+ // for the same channel in multiple packets.
+ do {
+ pic_packet_t *packet;
+
+ if (num_packets==sizeof(packets)/sizeof(packets[0]))
+ return epuc("bad format","too many packets");
+
+ packet = &packets[num_packets++];
+
+ chained = get8(s);
+ packet->size = get8u(s);
+ packet->type = get8u(s);
+ packet->channel = get8u(s);
+
+ act_comp |= packet->channel;
+
+ if (at_eof(s)) return epuc("bad file","file too short (reading packets)");
+ if (packet->size != 8) return epuc("bad format","packet isn't 8bpp");
+ } while (chained);
+
+ *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?
+
+ for(y=0; ytype) {
+ default:
+ return epuc("bad format","packet has bad compression type");
+
+ case 0: {//uncompressed
+ int x;
+
+ for(x=0;xchannel,dest))
+ return 0;
+ break;
+ }
+
+ case 1://Pure RLE
+ {
+ int left=width, i;
+
+ while (left>0) {
+ stbi_uc count,value[4];
+
+ count=get8u(s);
+ if (at_eof(s)) return epuc("bad file","file too short (pure read count)");
+
+ if (count > left)
+ count = (stbi__uint8) left;
+
+ if (!pic_readval(s,packet->channel,value)) return 0;
+
+ for(i=0; ichannel,dest,value);
+ left -= count;
+ }
+ }
+ break;
+
+ case 2: {//Mixed RLE
+ int left=width;
+ while (left>0) {
+ int count = get8(s), i;
+ if (at_eof(s)) return epuc("bad file","file too short (mixed read count)");
+
+ if (count >= 128) { // Repeated
+ stbi_uc value[4];
+ int i;
+
+ if (count==128)
+ count = get16(s);
+ else
+ count -= 127;
+ if (count > left)
+ return epuc("bad file","scanline overrun");
+
+ if (!pic_readval(s,packet->channel,value))
+ return 0;
+
+ for(i=0;ichannel,dest,value);
+ } else { // Raw
+ ++count;
+ if (count>left) return epuc("bad file","scanline overrun");
+
+ for(i=0;ichannel,dest))
+ return 0;
+ }
+ left-=count;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+static stbi_uc *pic_load(stbi *s,int *px,int *py,int *comp,int req_comp)
+{
+ stbi_uc *result;
+ int i, x,y;
+
+ for (i=0; i<92; ++i)
+ get8(s);
+
+ x = get16(s);
+ y = get16(s);
+ if (at_eof(s)) return epuc("bad file","file too short (pic header)");
+ if ((1 << 28) / x < y) return epuc("too large", "Image too large to decode");
+
+ get32(s); //skip `ratio'
+ get16(s); //skip `fields'
+ get16(s); //skip `pad'
+
+ // intermediate buffer is RGBA
+ result = (stbi_uc *) malloc(x*y*4);
+ memset(result, 0xff, x*y*4);
+
+ if (!pic_load2(s,x,y,comp, result)) {
+ free(result);
+ result=0;
+ }
+ *px = x;
+ *py = y;
+ if (req_comp == 0) req_comp = *comp;
+ result=convert_format(result,4,req_comp,x,y);
+
+ return result;
+}
+
+static int stbi_pic_test(stbi *s)
+{
+ int r = pic_test(s);
+ stbi_rewind(s);
+ return r;
+}
+
+static stbi_uc *stbi_pic_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ return pic_load(s,x,y,comp,req_comp);
+}
+
+// *************************************************************************************************
+// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb
+typedef struct stbi_gif_lzw_struct {
+ stbi__int16 prefix;
+ stbi__uint8 first;
+ stbi__uint8 suffix;
+} stbi_gif_lzw;
+
+typedef struct stbi_gif_struct
+{
+ int w,h;
+ stbi_uc *out; // output buffer (always 4 components)
+ int flags, bgindex, ratio, transparent, eflags;
+ stbi__uint8 pal[256][4];
+ stbi__uint8 lpal[256][4];
+ stbi_gif_lzw codes[4096];
+ stbi__uint8 *color_table;
+ int parse, step;
+ int lflags;
+ int start_x, start_y;
+ int max_x, max_y;
+ int cur_x, cur_y;
+ int line_size;
+} stbi_gif;
+
+static int gif_test(stbi *s)
+{
+ int sz;
+ if (get8(s) != 'G' || get8(s) != 'I' || get8(s) != 'F' || get8(s) != '8') return 0;
+ sz = get8(s);
+ if (sz != '9' && sz != '7') return 0;
+ if (get8(s) != 'a') return 0;
+ return 1;
+}
+
+static int stbi_gif_test(stbi *s)
+{
+ int r = gif_test(s);
+ stbi_rewind(s);
+ return r;
+}
+
+static void stbi_gif_parse_colortable(stbi *s, stbi__uint8 pal[256][4], int num_entries, int transp)
+{
+ int i;
+ for (i=0; i < num_entries; ++i) {
+ pal[i][2] = get8u(s);
+ pal[i][1] = get8u(s);
+ pal[i][0] = get8u(s);
+ pal[i][3] = transp ? 0 : 255;
+ }
+}
+
+static int stbi_gif_header(stbi *s, stbi_gif *g, int *comp, int is_info)
+{
+ stbi__uint8 version;
+ if (get8(s) != 'G' || get8(s) != 'I' || get8(s) != 'F' || get8(s) != '8')
+ return e("not GIF", "Corrupt GIF");
+
+ version = get8u(s);
+ if (version != '7' && version != '9') return e("not GIF", "Corrupt GIF");
+ if (get8(s) != 'a') return e("not GIF", "Corrupt GIF");
+
+ failure_reason = "";
+ g->w = get16le(s);
+ g->h = get16le(s);
+ g->flags = get8(s);
+ g->bgindex = get8(s);
+ g->ratio = get8(s);
+ g->transparent = -1;
+
+ if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments
+
+ if (is_info) return 1;
+
+ if (g->flags & 0x80)
+ stbi_gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);
+
+ return 1;
+}
+
+static int stbi_gif_info_raw(stbi *s, int *x, int *y, int *comp)
+{
+ stbi_gif g;
+ if (!stbi_gif_header(s, &g, comp, 1)) {
+ stbi_rewind( s );
+ return 0;
+ }
+ if (x) *x = g.w;
+ if (y) *y = g.h;
+ return 1;
+}
+
+static void stbi_out_gif_code(stbi_gif *g, stbi__uint16 code)
+{
+ stbi__uint8 *p, *c;
+
+ // recurse to decode the prefixes, since the linked-list is backwards,
+ // and working backwards through an interleaved image would be nasty
+ if (g->codes[code].prefix >= 0)
+ stbi_out_gif_code(g, g->codes[code].prefix);
+
+ if (g->cur_y >= g->max_y) return;
+
+ p = &g->out[g->cur_x + g->cur_y];
+ c = &g->color_table[g->codes[code].suffix * 4];
+
+ if (c[3] >= 128) {
+ p[0] = c[2];
+ p[1] = c[1];
+ p[2] = c[0];
+ p[3] = c[3];
+ }
+ g->cur_x += 4;
+
+ if (g->cur_x >= g->max_x) {
+ g->cur_x = g->start_x;
+ g->cur_y += g->step;
+
+ while (g->cur_y >= g->max_y && g->parse > 0) {
+ g->step = (1 << g->parse) * g->line_size;
+ g->cur_y = g->start_y + (g->step >> 1);
+ --g->parse;
+ }
+ }
+}
+
+static stbi__uint8 *stbi_process_gif_raster(stbi *s, stbi_gif *g)
+{
+ stbi__uint8 lzw_cs;
+ stbi__int32 len, code;
+ stbi__uint32 first;
+ stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
+ stbi_gif_lzw *p;
+
+ lzw_cs = get8u(s);
+ clear = 1 << lzw_cs;
+ first = 1;
+ codesize = lzw_cs + 1;
+ codemask = (1 << codesize) - 1;
+ bits = 0;
+ valid_bits = 0;
+ for (code = 0; code < clear; code++) {
+ g->codes[code].prefix = -1;
+ g->codes[code].first = (stbi__uint8) code;
+ g->codes[code].suffix = (stbi__uint8) code;
+ }
+
+ // support no starting clear code
+ avail = clear+2;
+ oldcode = -1;
+
+ len = 0;
+ for(;;) {
+ if (valid_bits < codesize) {
+ if (len == 0) {
+ len = get8(s); // start new block
+ if (len == 0)
+ return g->out;
+ }
+ --len;
+ bits |= (stbi__int32) get8(s) << valid_bits;
+ valid_bits += 8;
+ } else {
+ stbi__int32 code = bits & codemask;
+ bits >>= codesize;
+ valid_bits -= codesize;
+ // @OPTIMIZE: is there some way we can accelerate the non-clear path?
+ if (code == clear) { // clear code
+ codesize = lzw_cs + 1;
+ codemask = (1 << codesize) - 1;
+ avail = clear + 2;
+ oldcode = -1;
+ first = 0;
+ } else if (code == clear + 1) { // end of stream code
+ skip(s, len);
+ while ((len = get8(s)) > 0)
+ skip(s,len);
+ return g->out;
+ } else if (code <= avail) {
+ if (first) return epuc("no clear code", "Corrupt GIF");
+
+ if (oldcode >= 0) {
+ p = &g->codes[avail++];
+ if (avail > 4096) return epuc("too many codes", "Corrupt GIF");
+ p->prefix = (stbi__int16) oldcode;
+ p->first = g->codes[oldcode].first;
+ p->suffix = (code == avail) ? p->first : g->codes[code].first;
+ } else if (code == avail)
+ return epuc("illegal code in raster", "Corrupt GIF");
+
+ stbi_out_gif_code(g, (stbi__uint16) code);
+
+ if ((avail & codemask) == 0 && avail <= 0x0FFF) {
+ codesize++;
+ codemask = (1 << codesize) - 1;
+ }
+
+ oldcode = code;
+ } else {
+ return epuc("illegal code in raster", "Corrupt GIF");
+ }
+ }
+ }
+}
+
+static void stbi_fill_gif_background(stbi_gif *g)
+{
+ int i;
+ stbi__uint8 *c = g->pal[g->bgindex];
+ // @OPTIMIZE: write a dword at a time
+ for (i = 0; i < g->w * g->h * 4; i += 4) {
+ stbi__uint8 *p = &g->out[i];
+ p[0] = c[2];
+ p[1] = c[1];
+ p[2] = c[0];
+ p[3] = c[3];
+ }
+}
+
+// this function is designed to support animated gifs, although stb_image doesn't support it
+static stbi__uint8 *stbi_gif_load_next(stbi *s, stbi_gif *g, int *comp, int req_comp)
+{
+ int i;
+ stbi__uint8 *old_out = 0;
+
+ if (g->out == 0) {
+ if (!stbi_gif_header(s, g, comp,0)) return 0; // failure_reason set by stbi_gif_header
+ g->out = (stbi__uint8 *) malloc(4 * g->w * g->h);
+ if (g->out == 0) return epuc("outofmem", "Out of memory");
+ stbi_fill_gif_background(g);
+ } else {
+ // animated-gif-only path
+ if (((g->eflags & 0x1C) >> 2) == 3) {
+ old_out = g->out;
+ g->out = (stbi__uint8 *) malloc(4 * g->w * g->h);
+ if (g->out == 0) return epuc("outofmem", "Out of memory");
+ memcpy(g->out, old_out, g->w*g->h*4);
+ }
+ }
+
+ for (;;) {
+ switch (get8(s)) {
+ case 0x2C: /* Image Descriptor */
+ {
+ stbi__int32 x, y, w, h;
+ stbi__uint8 *o;
+
+ x = get16le(s);
+ y = get16le(s);
+ w = get16le(s);
+ h = get16le(s);
+ if (((x + w) > (g->w)) || ((y + h) > (g->h)))
+ return epuc("bad Image Descriptor", "Corrupt GIF");
+
+ g->line_size = g->w * 4;
+ g->start_x = x * 4;
+ g->start_y = y * g->line_size;
+ g->max_x = g->start_x + w * 4;
+ g->max_y = g->start_y + h * g->line_size;
+ g->cur_x = g->start_x;
+ g->cur_y = g->start_y;
+
+ g->lflags = get8(s);
+
+ if (g->lflags & 0x40) {
+ g->step = 8 * g->line_size; // first interlaced spacing
+ g->parse = 3;
+ } else {
+ g->step = g->line_size;
+ g->parse = 0;
+ }
+
+ if (g->lflags & 0x80) {
+ stbi_gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
+ g->color_table = (stbi__uint8 *) g->lpal;
+ } else if (g->flags & 0x80) {
+ for (i=0; i < 256; ++i) // @OPTIMIZE: reset only the previous transparent
+ g->pal[i][3] = 255;
+ if (g->transparent >= 0 && (g->eflags & 0x01))
+ g->pal[g->transparent][3] = 0;
+ g->color_table = (stbi__uint8 *) g->pal;
+ } else
+ return epuc("missing color table", "Corrupt GIF");
+
+ o = stbi_process_gif_raster(s, g);
+ if (o == NULL) return NULL;
+
+ if (req_comp && req_comp != 4)
+ o = convert_format(o, 4, req_comp, g->w, g->h);
+ return o;
+ }
+
+ case 0x21: // Comment Extension.
+ {
+ int len;
+ if (get8(s) == 0xF9) { // Graphic Control Extension.
+ len = get8(s);
+ if (len == 4) {
+ g->eflags = get8(s);
+ get16le(s); // delay
+ g->transparent = get8(s);
+ } else {
+ skip(s, len);
+ break;
+ }
+ }
+ while ((len = get8(s)) != 0)
+ skip(s, len);
+ break;
+ }
+
+ case 0x3B: // gif stream termination code
+ return (stbi__uint8 *) 1;
+
+ default:
+ return epuc("unknown code", "Corrupt GIF");
+ }
+ }
+}
+
+static stbi_uc *stbi_gif_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__uint8 *u = 0;
+ stbi_gif g={0};
+
+ u = stbi_gif_load_next(s, &g, comp, req_comp);
+ if (u == (void *) 1) u = 0; // end of animated gif marker
+ if (u) {
+ *x = g.w;
+ *y = g.h;
+ }
+
+ return u;
+}
+
+static int stbi_gif_info(stbi *s, int *x, int *y, int *comp)
+{
+ return stbi_gif_info_raw(s,x,y,comp);
+}
+
+
+// *************************************************************************************************
+// Radiance RGBE HDR loader
+// originally by Nicolas Schulz
+#ifndef STBI_NO_HDR
+static int hdr_test(stbi *s)
+{
+ const char *signature = "#?RADIANCE\n";
+ int i;
+ for (i=0; signature[i]; ++i)
+ if (get8(s) != signature[i])
+ return 0;
+ return 1;
+}
+
+static int stbi_hdr_test(stbi* s)
+{
+ int r = hdr_test(s);
+ stbi_rewind(s);
+ return r;
+}
+
+#define HDR_BUFLEN 1024
+static char *hdr_gettoken(stbi *z, char *buffer)
+{
+ int len=0;
+ char c = '\0';
+
+ c = (char) get8(z);
+
+ while (!at_eof(z) && c != '\n') {
+ buffer[len++] = c;
+ if (len == HDR_BUFLEN-1) {
+ // flush to end of line
+ while (!at_eof(z) && get8(z) != '\n')
+ ;
+ break;
+ }
+ c = (char) get8(z);
+ }
+
+ buffer[len] = 0;
+ return buffer;
+}
+
+static void hdr_convert(float *output, stbi_uc *input, int req_comp)
+{
+ if ( input[3] != 0 ) {
+ float f1;
+ // Exponent
+ f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));
+ if (req_comp <= 2)
+ output[0] = (input[0] + input[1] + input[2]) * f1 / 3;
+ else {
+ output[0] = input[0] * f1;
+ output[1] = input[1] * f1;
+ output[2] = input[2] * f1;
+ }
+ if (req_comp == 2) output[1] = 1;
+ if (req_comp == 4) output[3] = 1;
+ } else {
+ switch (req_comp) {
+ case 4: output[3] = 1; /* fallthrough */
+ case 3: output[0] = output[1] = output[2] = 0;
+ break;
+ case 2: output[1] = 1; /* fallthrough */
+ case 1: output[0] = 0;
+ break;
+ }
+ }
+}
+
+static float *hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ char buffer[HDR_BUFLEN];
+ char *token;
+ int valid = 0;
+ int width, height;
+ stbi_uc *scanline;
+ float *hdr_data;
+ int len;
+ unsigned char count, value;
+ int i, j, k, c1,c2, z;
+
+
+ // Check identifier
+ if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0)
+ return epf("not HDR", "Corrupt HDR image");
+
+ // Parse header
+ for(;;) {
+ token = hdr_gettoken(s,buffer);
+ if (token[0] == 0) break;
+ if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+ }
+
+ if (!valid) return epf("unsupported format", "Unsupported HDR format");
+
+ // Parse width and height
+ // can't use sscanf() if we're not using stdio!
+ token = hdr_gettoken(s,buffer);
+ if (strncmp(token, "-Y ", 3)) return epf("unsupported data layout", "Unsupported HDR format");
+ token += 3;
+ height = (int) strtol(token, &token, 10);
+ while (*token == ' ') ++token;
+ if (strncmp(token, "+X ", 3)) return epf("unsupported data layout", "Unsupported HDR format");
+ token += 3;
+ width = (int) strtol(token, NULL, 10);
+
+ *x = width;
+ *y = height;
+
+ *comp = 3;
+ if (req_comp == 0) req_comp = 3;
+
+ // Read data
+ hdr_data = (float *) malloc(height * width * req_comp * sizeof(float));
+
+ // Load image data
+ // image data is stored as some number of sca
+ if ( width < 8 || width >= 32768) {
+ // Read flat data
+ for (j=0; j < height; ++j) {
+ for (i=0; i < width; ++i) {
+ stbi_uc rgbe[4];
+ main_decode_loop:
+ getn(s, rgbe, 4);
+ hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
+ }
+ }
+ } else {
+ // Read RLE-encoded data
+ scanline = NULL;
+
+ for (j = 0; j < height; ++j) {
+ c1 = get8(s);
+ c2 = get8(s);
+ len = get8(s);
+ if (c1 != 2 || c2 != 2 || (len & 0x80)) {
+ // not run-length encoded, so we have to actually use THIS data as a decoded
+ // pixel (note this can't be a valid pixel--one of RGB must be >= 128)
+ stbi__uint8 rgbe[4];
+ rgbe[0] = (stbi__uint8) c1;
+ rgbe[1] = (stbi__uint8) c2;
+ rgbe[2] = (stbi__uint8) len;
+ rgbe[3] = (stbi__uint8) get8u(s);
+ hdr_convert(hdr_data, rgbe, req_comp);
+ i = 1;
+ j = 0;
+ free(scanline);
+ goto main_decode_loop; // yes, this makes no sense
+ }
+ len <<= 8;
+ len |= get8(s);
+ if (len != width) { free(hdr_data); free(scanline); return epf("invalid decoded scanline length", "corrupt HDR"); }
+ if (scanline == NULL) scanline = (stbi_uc *) malloc(width * 4);
+
+ for (k = 0; k < 4; ++k) {
+ i = 0;
+ while (i < width) {
+ count = get8u(s);
+ if (count > 128) {
+ // Run
+ value = get8u(s);
+ count -= 128;
+ for (z = 0; z < count; ++z)
+ scanline[i++ * 4 + k] = value;
+ } else {
+ // Dump
+ for (z = 0; z < count; ++z)
+ scanline[i++ * 4 + k] = get8u(s);
+ }
+ }
+ }
+ for (i=0; i < width; ++i)
+ hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);
+ }
+ free(scanline);
+ }
+
+ return hdr_data;
+}
+
+static float *stbi_hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp)
+{
+ return hdr_load(s,x,y,comp,req_comp);
+}
+
+static int stbi_hdr_info(stbi *s, int *x, int *y, int *comp)
+{
+ char buffer[HDR_BUFLEN];
+ char *token;
+ int valid = 0;
+
+ if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) {
+ stbi_rewind( s );
+ return 0;
+ }
+
+ for(;;) {
+ token = hdr_gettoken(s,buffer);
+ if (token[0] == 0) break;
+ if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+ }
+
+ if (!valid) {
+ stbi_rewind( s );
+ return 0;
+ }
+ token = hdr_gettoken(s,buffer);
+ if (strncmp(token, "-Y ", 3)) {
+ stbi_rewind( s );
+ return 0;
+ }
+ token += 3;
+ *y = (int) strtol(token, &token, 10);
+ while (*token == ' ') ++token;
+ if (strncmp(token, "+X ", 3)) {
+ stbi_rewind( s );
+ return 0;
+ }
+ token += 3;
+ *x = (int) strtol(token, NULL, 10);
+ *comp = 3;
+ return 1;
+}
+#endif // STBI_NO_HDR
+
+static int stbi_bmp_info(stbi *s, int *x, int *y, int *comp)
+{
+ int hsz;
+ if (get8(s) != 'B' || get8(s) != 'M') {
+ stbi_rewind( s );
+ return 0;
+ }
+ skip(s,12);
+ hsz = get32le(s);
+ if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) {
+ stbi_rewind( s );
+ return 0;
+ }
+ if (hsz == 12) {
+ *x = get16le(s);
+ *y = get16le(s);
+ } else {
+ *x = get32le(s);
+ *y = get32le(s);
+ }
+ if (get16le(s) != 1) {
+ stbi_rewind( s );
+ return 0;
+ }
+ *comp = get16le(s) / 8;
+ return 1;
+}
+
+static int stbi_psd_info(stbi *s, int *x, int *y, int *comp)
+{
+ int channelCount;
+ if (get32(s) != 0x38425053) {
+ stbi_rewind( s );
+ return 0;
+ }
+ if (get16(s) != 1) {
+ stbi_rewind( s );
+ return 0;
+ }
+ skip(s, 6);
+ channelCount = get16(s);
+ if (channelCount < 0 || channelCount > 16) {
+ stbi_rewind( s );
+ return 0;
+ }
+ *y = get32(s);
+ *x = get32(s);
+ if (get16(s) != 8) {
+ stbi_rewind( s );
+ return 0;
+ }
+ if (get16(s) != 3) {
+ stbi_rewind( s );
+ return 0;
+ }
+ *comp = 4;
+ return 1;
+}
+
+static int stbi_pic_info(stbi *s, int *x, int *y, int *comp)
+{
+ int act_comp=0,num_packets=0,chained;
+ pic_packet_t packets[10];
+
+ skip(s, 92);
+
+ *x = get16(s);
+ *y = get16(s);
+ if (at_eof(s)) return 0;
+ if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
+ stbi_rewind( s );
+ return 0;
+ }
+
+ skip(s, 8);
+
+ do {
+ pic_packet_t *packet;
+
+ if (num_packets==sizeof(packets)/sizeof(packets[0]))
+ return 0;
+
+ packet = &packets[num_packets++];
+ chained = get8(s);
+ packet->size = get8u(s);
+ packet->type = get8u(s);
+ packet->channel = get8u(s);
+ act_comp |= packet->channel;
+
+ if (at_eof(s)) {
+ stbi_rewind( s );
+ return 0;
+ }
+ if (packet->size != 8) {
+ stbi_rewind( s );
+ return 0;
+ }
+ } while (chained);
+
+ *comp = (act_comp & 0x10 ? 4 : 3);
+
+ return 1;
+}
+
+static int stbi_info_main(stbi *s, int *x, int *y, int *comp)
+{
+ if (stbi_jpeg_info(s, x, y, comp))
+ return 1;
+ if (stbi_png_info(s, x, y, comp))
+ return 1;
+ if (stbi_gif_info(s, x, y, comp))
+ return 1;
+ if (stbi_bmp_info(s, x, y, comp))
+ return 1;
+ if (stbi_psd_info(s, x, y, comp))
+ return 1;
+ if (stbi_pic_info(s, x, y, comp))
+ return 1;
+ #ifndef STBI_NO_HDR
+ if (stbi_hdr_info(s, x, y, comp))
+ return 1;
+ #endif
+ // test tga last because it's a crappy test!
+ if (stbi_tga_info(s, x, y, comp))
+ return 1;
+ return e("unknown image type", "Image not of any known type, or corrupt");
+}
+
+#ifndef STBI_NO_STDIO
+int stbi_info(char const *filename, int *x, int *y, int *comp)
+{
+ FILE *f = fopen(filename, "rb");
+ int result;
+ if (!f) return e("can't fopen", "Unable to open file");
+ result = stbi_info_from_file(f, x, y, comp);
+ fclose(f);
+ return result;
+}
+
+int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)
+{
+ int r;
+ stbi s;
+ long pos = ftell(f);
+ start_file(&s, f);
+ r = stbi_info_main(&s,x,y,comp);
+ fseek(f,pos,SEEK_SET);
+ return r;
+}
+#endif // !STBI_NO_STDIO
+
+int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)
+{
+ stbi s;
+ start_mem(&s,buffer,len);
+ return stbi_info_main(&s,x,y,comp);
+}
+
+int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)
+{
+ stbi s;
+ start_callbacks(&s, (stbi_io_callbacks *) c, user);
+ return stbi_info_main(&s,x,y,comp);
+}
+
+#endif // STBI_HEADER_FILE_ONLY
+
+#if !defined(STBI_NO_STDIO) && defined(_MSC_VER) && _MSC_VER >= 1400
+#pragma warning(pop)
+#endif
+
+
+/*
+ revision history:
+ 1.35 (2014-05-27)
+ various warnings
+ fix broken STBI_SIMD path
+ fix bug where stbi_load_from_file no longer left file pointer in correct place
+ fix broken non-easy path for 32-bit BMP (possibly never used)
+ TGA optimization by Arseny Kapoulkine
+ 1.34 (unknown)
+ use STBI_NOTUSED in resample_row_generic(), fix one more leak in tga failure case
+ 1.33 (2011-07-14)
+ make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements
+ 1.32 (2011-07-13)
+ support for "info" function for all supported filetypes (SpartanJ)
+ 1.31 (2011-06-20)
+ a few more leak fixes, bug in PNG handling (SpartanJ)
+ 1.30 (2011-06-11)
+ added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)
+ removed deprecated format-specific test/load functions
+ removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway
+ error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)
+ fix inefficiency in decoding 32-bit BMP (David Woo)
+ 1.29 (2010-08-16)
+ various warning fixes from Aurelien Pocheville
+ 1.28 (2010-08-01)
+ fix bug in GIF palette transparency (SpartanJ)
+ 1.27 (2010-08-01)
+ cast-to-stbi__uint8 to fix warnings
+ 1.26 (2010-07-24)
+ fix bug in file buffering for PNG reported by SpartanJ
+ 1.25 (2010-07-17)
+ refix trans_data warning (Won Chun)
+ 1.24 (2010-07-12)
+ perf improvements reading from files on platforms with lock-heavy fgetc()
+ minor perf improvements for jpeg
+ deprecated type-specific functions so we'll get feedback if they're needed
+ attempt to fix trans_data warning (Won Chun)
+ 1.23 fixed bug in iPhone support
+ 1.22 (2010-07-10)
+ removed image *writing* support
+ stbi_info support from Jetro Lauha
+ GIF support from Jean-Marc Lienher
+ iPhone PNG-extensions from James Brown
+ warning-fixes from Nicolas Schulz and Janez Zemva (i.e. Janez (U+017D)emva)
+ 1.21 fix use of 'stbi__uint8' in header (reported by jon blow)
+ 1.20 added support for Softimage PIC, by Tom Seddon
+ 1.19 bug in interlaced PNG corruption check (found by ryg)
+ 1.18 2008-08-02
+ fix a threading bug (local mutable static)
+ 1.17 support interlaced PNG
+ 1.16 major bugfix - convert_format converted one too many pixels
+ 1.15 initialize some fields for thread safety
+ 1.14 fix threadsafe conversion bug
+ header-file-only version (#define STBI_HEADER_FILE_ONLY before including)
+ 1.13 threadsafe
+ 1.12 const qualifiers in the API
+ 1.11 Support installable IDCT, colorspace conversion routines
+ 1.10 Fixes for 64-bit (don't use "unsigned long")
+ optimized upsampling by Fabian "ryg" Giesen
+ 1.09 Fix format-conversion for PSD code (bad global variables!)
+ 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz
+ 1.07 attempt to fix C++ warning/errors again
+ 1.06 attempt to fix C++ warning/errors again
+ 1.05 fix TGA loading to return correct *comp and use good luminance calc
+ 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free
+ 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR
+ 1.02 support for (subset of) HDR files, float interface for preferred access to them
+ 1.01 fix bug: possible bug in handling right-side up bmps... not sure
+ fix bug: the stbi_bmp_load() and stbi_tga_load() functions didn't work at all
+ 1.00 interface to zlib that skips zlib header
+ 0.99 correct handling of alpha in palette
+ 0.98 TGA loader by lonesock; dynamically add loaders (untested)
+ 0.97 jpeg errors on too large a file; also catch another malloc failure
+ 0.96 fix detection of invalid v value - particleman@mollyrocket forum
+ 0.95 during header scan, seek to markers in case of padding
+ 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same
+ 0.93 handle jpegtran output; verbose errors
+ 0.92 read 4,8,16,24,32-bit BMP files of several formats
+ 0.91 output 24-bit Windows 3.0 BMP files
+ 0.90 fix a few more warnings; bump version number to approach 1.0
+ 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd
+ 0.60 fix compiling as c++
+ 0.59 fix warnings: merge Dave Moore's -Wall fixes
+ 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian
+ 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available
+ 0.56 fix bug: zlib uncompressed mode len vs. nlen
+ 0.55 fix bug: restart_interval not initialized to 0
+ 0.54 allow NULL for 'int *comp'
+ 0.53 fix bug in png 3->4; speedup png decoding
+ 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments
+ 0.51 obey req_comp requests, 1-component jpegs return as 1-component,
+ on 'test' only check type, not whether we support this variant
+ 0.50 first released version
+*/
diff --git a/third-party/stb/deprecated/stb_image_resize.h b/third-party/stb/deprecated/stb_image_resize.h
new file mode 100644
index 0000000..ef9e6fe
--- /dev/null
+++ b/third-party/stb/deprecated/stb_image_resize.h
@@ -0,0 +1,2634 @@
+/* stb_image_resize - v0.97 - public domain image resizing
+ by Jorge L Rodriguez (@VinoBS) - 2014
+ http://github.com/nothings/stb
+
+ Written with emphasis on usability, portability, and efficiency. (No
+ SIMD or threads, so it be easily outperformed by libs that use those.)
+ Only scaling and translation is supported, no rotations or shears.
+ Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation.
+
+ COMPILING & LINKING
+ In one C/C++ file that #includes this file, do this:
+ #define STB_IMAGE_RESIZE_IMPLEMENTATION
+ before the #include. That will create the implementation in that file.
+
+ QUICKSTART
+ stbir_resize_uint8( input_pixels , in_w , in_h , 0,
+ output_pixels, out_w, out_h, 0, num_channels)
+ stbir_resize_float(...)
+ stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0,
+ output_pixels, out_w, out_h, 0,
+ num_channels , alpha_chan , 0)
+ stbir_resize_uint8_srgb_edgemode(
+ input_pixels , in_w , in_h , 0,
+ output_pixels, out_w, out_h, 0,
+ num_channels , alpha_chan , 0, STBIR_EDGE_CLAMP)
+ // WRAP/REFLECT/ZERO
+
+ FULL API
+ See the "header file" section of the source for API documentation.
+
+ ADDITIONAL DOCUMENTATION
+
+ SRGB & FLOATING POINT REPRESENTATION
+ The sRGB functions presume IEEE floating point. If you do not have
+ IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use
+ a slower implementation.
+
+ MEMORY ALLOCATION
+ The resize functions here perform a single memory allocation using
+ malloc. To control the memory allocation, before the #include that
+ triggers the implementation, do:
+
+ #define STBIR_MALLOC(size,context) ...
+ #define STBIR_FREE(ptr,context) ...
+
+ Each resize function makes exactly one call to malloc/free, so to use
+ temp memory, store the temp memory in the context and return that.
+
+ ASSERT
+ Define STBIR_ASSERT(boolval) to override assert() and not use assert.h
+
+ OPTIMIZATION
+ Define STBIR_SATURATE_INT to compute clamp values in-range using
+ integer operations instead of float operations. This may be faster
+ on some platforms.
+
+ DEFAULT FILTERS
+ For functions which don't provide explicit control over what filters
+ to use, you can change the compile-time defaults with
+
+ #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something
+ #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something
+
+ See stbir_filter in the header-file section for the list of filters.
+
+ NEW FILTERS
+ A number of 1D filter kernels are used. For a list of
+ supported filters see the stbir_filter enum. To add a new filter,
+ write a filter function and add it to stbir__filter_info_table.
+
+ PROGRESS
+ For interactive use with slow resize operations, you can install
+ a progress-report callback:
+
+ #define STBIR_PROGRESS_REPORT(val) some_func(val)
+
+ The parameter val is a float which goes from 0 to 1 as progress is made.
+
+ For example:
+
+ static void my_progress_report(float progress);
+ #define STBIR_PROGRESS_REPORT(val) my_progress_report(val)
+
+ #define STB_IMAGE_RESIZE_IMPLEMENTATION
+ #include "stb_image_resize.h"
+
+ static void my_progress_report(float progress)
+ {
+ printf("Progress: %f%%\n", progress*100);
+ }
+
+ MAX CHANNELS
+ If your image has more than 64 channels, define STBIR_MAX_CHANNELS
+ to the max you'll have.
+
+ ALPHA CHANNEL
+ Most of the resizing functions provide the ability to control how
+ the alpha channel of an image is processed. The important things
+ to know about this:
+
+ 1. The best mathematically-behaved version of alpha to use is
+ called "premultiplied alpha", in which the other color channels
+ have had the alpha value multiplied in. If you use premultiplied
+ alpha, linear filtering (such as image resampling done by this
+ library, or performed in texture units on GPUs) does the "right
+ thing". While premultiplied alpha is standard in the movie CGI
+ industry, it is still uncommon in the videogame/real-time world.
+
+ If you linearly filter non-premultiplied alpha, strange effects
+ occur. (For example, the 50/50 average of 99% transparent bright green
+ and 1% transparent black produces 50% transparent dark green when
+ non-premultiplied, whereas premultiplied it produces 50%
+ transparent near-black. The former introduces green energy
+ that doesn't exist in the source image.)
+
+ 2. Artists should not edit premultiplied-alpha images; artists
+ want non-premultiplied alpha images. Thus, art tools generally output
+ non-premultiplied alpha images.
+
+ 3. You will get best results in most cases by converting images
+ to premultiplied alpha before processing them mathematically.
+
+ 4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the
+ resizer does not do anything special for the alpha channel;
+ it is resampled identically to other channels. This produces
+ the correct results for premultiplied-alpha images, but produces
+ less-than-ideal results for non-premultiplied-alpha images.
+
+ 5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED,
+ then the resizer weights the contribution of input pixels
+ based on their alpha values, or, equivalently, it multiplies
+ the alpha value into the color channels, resamples, then divides
+ by the resultant alpha value. Input pixels which have alpha=0 do
+ not contribute at all to output pixels unless _all_ of the input
+ pixels affecting that output pixel have alpha=0, in which case
+ the result for that pixel is the same as it would be without
+ STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for
+ input images in integer formats. For input images in float format,
+ input pixels with alpha=0 have no effect, and output pixels
+ which have alpha=0 will be 0 in all channels. (For float images,
+ you can manually achieve the same result by adding a tiny epsilon
+ value to the alpha channel of every image, and then subtracting
+ or clamping it at the end.)
+
+ 6. You can suppress the behavior described in #5 and make
+ all-0-alpha pixels have 0 in all channels by #defining
+ STBIR_NO_ALPHA_EPSILON.
+
+ 7. You can separately control whether the alpha channel is
+ interpreted as linear or affected by the colorspace. By default
+ it is linear; you almost never want to apply the colorspace.
+ (For example, graphics hardware does not apply sRGB conversion
+ to the alpha channel.)
+
+ CONTRIBUTORS
+ Jorge L Rodriguez: Implementation
+ Sean Barrett: API design, optimizations
+ Aras Pranckevicius: bugfix
+ Nathan Reed: warning fixes
+
+ REVISIONS
+ 0.97 (2020-02-02) fixed warning
+ 0.96 (2019-03-04) fixed warnings
+ 0.95 (2017-07-23) fixed warnings
+ 0.94 (2017-03-18) fixed warnings
+ 0.93 (2017-03-03) fixed bug with certain combinations of heights
+ 0.92 (2017-01-02) fix integer overflow on large (>2GB) images
+ 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions
+ 0.90 (2014-09-17) first released version
+
+ LICENSE
+ See end of file for license information.
+
+ TODO
+ Don't decode all of the image data when only processing a partial tile
+ Don't use full-width decode buffers when only processing a partial tile
+ When processing wide images, break processing into tiles so data fits in L1 cache
+ Installable filters?
+ Resize that respects alpha test coverage
+ (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage:
+ https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp )
+*/
+
+#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+
+#ifdef _MSC_VER
+typedef unsigned char stbir_uint8;
+typedef unsigned short stbir_uint16;
+typedef unsigned int stbir_uint32;
+#else
+#include
+typedef uint8_t stbir_uint8;
+typedef uint16_t stbir_uint16;
+typedef uint32_t stbir_uint32;
+#endif
+
+#ifndef STBIRDEF
+#ifdef STB_IMAGE_RESIZE_STATIC
+#define STBIRDEF static
+#else
+#ifdef __cplusplus
+#define STBIRDEF extern "C"
+#else
+#define STBIRDEF extern
+#endif
+#endif
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Easy-to-use API:
+//
+// * "input pixels" points to an array of image data with 'num_channels' channels (e.g. RGB=3, RGBA=4)
+// * input_w is input image width (x-axis), input_h is input image height (y-axis)
+// * stride is the offset between successive rows of image data in memory, in bytes. you can
+// specify 0 to mean packed continuously in memory
+// * alpha channel is treated identically to other channels.
+// * colorspace is linear or sRGB as specified by function name
+// * returned result is 1 for success or 0 in case of an error.
+// #define STBIR_ASSERT() to trigger an assert on parameter validation errors.
+// * Memory required grows approximately linearly with input and output size, but with
+// discontinuities at input_w == output_w and input_h == output_h.
+// * These functions use a "default" resampling filter defined at compile time. To change the filter,
+// you can change the compile-time defaults by #defining STBIR_DEFAULT_FILTER_UPSAMPLE
+// and STBIR_DEFAULT_FILTER_DOWNSAMPLE, or you can use the medium-complexity API.
+
+STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels);
+
+STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels);
+
+
+// The following functions interpret image data as gamma-corrected sRGB.
+// Specify STBIR_ALPHA_CHANNEL_NONE if you have no alpha channel,
+// or otherwise provide the index of the alpha channel. Flags value
+// of 0 will probably do the right thing if you're not sure what
+// the flags mean.
+
+#define STBIR_ALPHA_CHANNEL_NONE -1
+
+// Set this flag if your texture has premultiplied alpha. Otherwise, stbir will
+// use alpha-weighted resampling (effectively premultiplying, resampling,
+// then unpremultiplying).
+#define STBIR_FLAG_ALPHA_PREMULTIPLIED (1 << 0)
+// The specified alpha channel should be handled as gamma-corrected value even
+// when doing sRGB operations.
+#define STBIR_FLAG_ALPHA_USES_COLORSPACE (1 << 1)
+
+STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags);
+
+
+typedef enum
+{
+ STBIR_EDGE_CLAMP = 1,
+ STBIR_EDGE_REFLECT = 2,
+ STBIR_EDGE_WRAP = 3,
+ STBIR_EDGE_ZERO = 4,
+} stbir_edge;
+
+// This function adds the ability to specify how requests to sample off the edge of the image are handled.
+STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_wrap_mode);
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Medium-complexity API
+//
+// This extends the easy-to-use API as follows:
+//
+// * Alpha-channel can be processed separately
+// * If alpha_channel is not STBIR_ALPHA_CHANNEL_NONE
+// * Alpha channel will not be gamma corrected (unless flags&STBIR_FLAG_GAMMA_CORRECT)
+// * Filters will be weighted by alpha channel (unless flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)
+// * Filter can be selected explicitly
+// * uint16 image type
+// * sRGB colorspace available for all types
+// * context parameter for passing to STBIR_MALLOC
+
+typedef enum
+{
+ STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses
+ STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios
+ STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering
+ STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque
+ STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline
+ STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3
+} stbir_filter;
+
+typedef enum
+{
+ STBIR_COLORSPACE_LINEAR,
+ STBIR_COLORSPACE_SRGB,
+
+ STBIR_MAX_COLORSPACES,
+} stbir_colorspace;
+
+// The following functions are all identical except for the type of the image data
+
+STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
+ void *alloc_context);
+
+STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
+ void *alloc_context);
+
+STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ float *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
+ void *alloc_context);
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Full-complexity API
+//
+// This extends the medium API as follows:
+//
+// * uint32 image type
+// * not typesafe
+// * separate filter types for each axis
+// * separate edge modes for each axis
+// * can specify scale explicitly for subpixel correctness
+// * can specify image source tile using texture coordinates
+
+typedef enum
+{
+ STBIR_TYPE_UINT8 ,
+ STBIR_TYPE_UINT16,
+ STBIR_TYPE_UINT32,
+ STBIR_TYPE_FLOAT ,
+
+ STBIR_MAX_TYPES
+} stbir_datatype;
+
+STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ stbir_datatype datatype,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
+ stbir_filter filter_horizontal, stbir_filter filter_vertical,
+ stbir_colorspace space, void *alloc_context);
+
+STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ stbir_datatype datatype,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
+ stbir_filter filter_horizontal, stbir_filter filter_vertical,
+ stbir_colorspace space, void *alloc_context,
+ float x_scale, float y_scale,
+ float x_offset, float y_offset);
+
+STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ stbir_datatype datatype,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
+ stbir_filter filter_horizontal, stbir_filter filter_vertical,
+ stbir_colorspace space, void *alloc_context,
+ float s0, float t0, float s1, float t1);
+// (s0, t0) & (s1, t1) are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use.
+
+//
+//
+//// end header file /////////////////////////////////////////////////////
+#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+
+
+
+
+
+#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION
+
+#ifndef STBIR_ASSERT
+#include
+#define STBIR_ASSERT(x) assert(x)
+#endif
+
+// For memset
+#include
+
+#include
+
+#ifndef STBIR_MALLOC
+#include
+// use comma operator to evaluate c, to avoid "unused parameter" warnings
+#define STBIR_MALLOC(size,c) ((void)(c), malloc(size))
+#define STBIR_FREE(ptr,c) ((void)(c), free(ptr))
+#endif
+
+#ifndef _MSC_VER
+#ifdef __cplusplus
+#define stbir__inline inline
+#else
+#define stbir__inline
+#endif
+#else
+#define stbir__inline __forceinline
+#endif
+
+
+// should produce compiler error if size is wrong
+typedef unsigned char stbir__validate_uint32[sizeof(stbir_uint32) == 4 ? 1 : -1];
+
+#ifdef _MSC_VER
+#define STBIR__NOTUSED(v) (void)(v)
+#else
+#define STBIR__NOTUSED(v) (void)sizeof(v)
+#endif
+
+#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0]))
+
+#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE
+#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM
+#endif
+
+#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE
+#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL
+#endif
+
+#ifndef STBIR_PROGRESS_REPORT
+#define STBIR_PROGRESS_REPORT(float_0_to_1)
+#endif
+
+#ifndef STBIR_MAX_CHANNELS
+#define STBIR_MAX_CHANNELS 64
+#endif
+
+#if STBIR_MAX_CHANNELS > 65536
+#error "Too many channels; STBIR_MAX_CHANNELS must be no more than 65536."
+// because we store the indices in 16-bit variables
+#endif
+
+// This value is added to alpha just before premultiplication to avoid
+// zeroing out color values. It is equivalent to 2^-80. If you don't want
+// that behavior (it may interfere if you have floating point images with
+// very small alpha values) then you can define STBIR_NO_ALPHA_EPSILON to
+// disable it.
+#ifndef STBIR_ALPHA_EPSILON
+#define STBIR_ALPHA_EPSILON ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20))
+#endif
+
+
+
+#ifdef _MSC_VER
+#define STBIR__UNUSED_PARAM(v) (void)(v)
+#else
+#define STBIR__UNUSED_PARAM(v) (void)sizeof(v)
+#endif
+
+// must match stbir_datatype
+static unsigned char stbir__type_size[] = {
+ 1, // STBIR_TYPE_UINT8
+ 2, // STBIR_TYPE_UINT16
+ 4, // STBIR_TYPE_UINT32
+ 4, // STBIR_TYPE_FLOAT
+};
+
+// Kernel function centered at 0
+typedef float (stbir__kernel_fn)(float x, float scale);
+typedef float (stbir__support_fn)(float scale);
+
+typedef struct
+{
+ stbir__kernel_fn* kernel;
+ stbir__support_fn* support;
+} stbir__filter_info;
+
+// When upsampling, the contributors are which source pixels contribute.
+// When downsampling, the contributors are which destination pixels are contributed to.
+typedef struct
+{
+ int n0; // First contributing pixel
+ int n1; // Last contributing pixel
+} stbir__contributors;
+
+typedef struct
+{
+ const void* input_data;
+ int input_w;
+ int input_h;
+ int input_stride_bytes;
+
+ void* output_data;
+ int output_w;
+ int output_h;
+ int output_stride_bytes;
+
+ float s0, t0, s1, t1;
+
+ float horizontal_shift; // Units: output pixels
+ float vertical_shift; // Units: output pixels
+ float horizontal_scale;
+ float vertical_scale;
+
+ int channels;
+ int alpha_channel;
+ stbir_uint32 flags;
+ stbir_datatype type;
+ stbir_filter horizontal_filter;
+ stbir_filter vertical_filter;
+ stbir_edge edge_horizontal;
+ stbir_edge edge_vertical;
+ stbir_colorspace colorspace;
+
+ stbir__contributors* horizontal_contributors;
+ float* horizontal_coefficients;
+
+ stbir__contributors* vertical_contributors;
+ float* vertical_coefficients;
+
+ int decode_buffer_pixels;
+ float* decode_buffer;
+
+ float* horizontal_buffer;
+
+ // cache these because ceil/floor are inexplicably showing up in profile
+ int horizontal_coefficient_width;
+ int vertical_coefficient_width;
+ int horizontal_filter_pixel_width;
+ int vertical_filter_pixel_width;
+ int horizontal_filter_pixel_margin;
+ int vertical_filter_pixel_margin;
+ int horizontal_num_contributors;
+ int vertical_num_contributors;
+
+ int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter)
+ int ring_buffer_num_entries; // Total number of entries in the ring buffer.
+ int ring_buffer_first_scanline;
+ int ring_buffer_last_scanline;
+ int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer
+ float* ring_buffer;
+
+ float* encode_buffer; // A temporary buffer to store floats so we don't lose precision while we do multiply-adds.
+
+ int horizontal_contributors_size;
+ int horizontal_coefficients_size;
+ int vertical_contributors_size;
+ int vertical_coefficients_size;
+ int decode_buffer_size;
+ int horizontal_buffer_size;
+ int ring_buffer_size;
+ int encode_buffer_size;
+} stbir__info;
+
+
+static const float stbir__max_uint8_as_float = 255.0f;
+static const float stbir__max_uint16_as_float = 65535.0f;
+static const double stbir__max_uint32_as_float = 4294967295.0;
+
+
+static stbir__inline int stbir__min(int a, int b)
+{
+ return a < b ? a : b;
+}
+
+static stbir__inline float stbir__saturate(float x)
+{
+ if (x < 0)
+ return 0;
+
+ if (x > 1)
+ return 1;
+
+ return x;
+}
+
+#ifdef STBIR_SATURATE_INT
+static stbir__inline stbir_uint8 stbir__saturate8(int x)
+{
+ if ((unsigned int) x <= 255)
+ return x;
+
+ if (x < 0)
+ return 0;
+
+ return 255;
+}
+
+static stbir__inline stbir_uint16 stbir__saturate16(int x)
+{
+ if ((unsigned int) x <= 65535)
+ return x;
+
+ if (x < 0)
+ return 0;
+
+ return 65535;
+}
+#endif
+
+static float stbir__srgb_uchar_to_linear_float[256] = {
+ 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f,
+ 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f,
+ 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f,
+ 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f,
+ 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f,
+ 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f,
+ 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f,
+ 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f,
+ 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f,
+ 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f,
+ 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f,
+ 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f,
+ 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f,
+ 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f,
+ 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f,
+ 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f,
+ 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f,
+ 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f,
+ 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f,
+ 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f,
+ 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f,
+ 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f,
+ 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f,
+ 0.982251f, 0.991102f, 1.0f
+};
+
+static float stbir__srgb_to_linear(float f)
+{
+ if (f <= 0.04045f)
+ return f / 12.92f;
+ else
+ return (float)pow((f + 0.055f) / 1.055f, 2.4f);
+}
+
+static float stbir__linear_to_srgb(float f)
+{
+ if (f <= 0.0031308f)
+ return f * 12.92f;
+ else
+ return 1.055f * (float)pow(f, 1 / 2.4f) - 0.055f;
+}
+
+#ifndef STBIR_NON_IEEE_FLOAT
+// From https://gist.github.com/rygorous/2203834
+
+typedef union
+{
+ stbir_uint32 u;
+ float f;
+} stbir__FP32;
+
+static const stbir_uint32 fp32_to_srgb8_tab4[104] = {
+ 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d,
+ 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a,
+ 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033,
+ 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067,
+ 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5,
+ 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2,
+ 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143,
+ 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af,
+ 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240,
+ 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300,
+ 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401,
+ 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559,
+ 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723,
+};
+
+static stbir_uint8 stbir__linear_to_srgb_uchar(float in)
+{
+ static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps
+ static const stbir__FP32 minval = { (127-13) << 23 };
+ stbir_uint32 tab,bias,scale,t;
+ stbir__FP32 f;
+
+ // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively.
+ // The tests are carefully written so that NaNs map to 0, same as in the reference
+ // implementation.
+ if (!(in > minval.f)) // written this way to catch NaNs
+ in = minval.f;
+ if (in > almostone.f)
+ in = almostone.f;
+
+ // Do the table lookup and unpack bias, scale
+ f.f = in;
+ tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20];
+ bias = (tab >> 16) << 9;
+ scale = tab & 0xffff;
+
+ // Grab next-highest mantissa bits and perform linear interpolation
+ t = (f.u >> 12) & 0xff;
+ return (unsigned char) ((bias + scale*t) >> 16);
+}
+
+#else
+// sRGB transition values, scaled by 1<<28
+static int stbir__srgb_offset_to_linear_scaled[256] =
+{
+ 0, 40738, 122216, 203693, 285170, 366648, 448125, 529603,
+ 611080, 692557, 774035, 855852, 942009, 1033024, 1128971, 1229926,
+ 1335959, 1447142, 1563542, 1685229, 1812268, 1944725, 2082664, 2226148,
+ 2375238, 2529996, 2690481, 2856753, 3028870, 3206888, 3390865, 3580856,
+ 3776916, 3979100, 4187460, 4402049, 4622919, 4850123, 5083710, 5323731,
+ 5570236, 5823273, 6082892, 6349140, 6622065, 6901714, 7188133, 7481369,
+ 7781466, 8088471, 8402427, 8723380, 9051372, 9386448, 9728650, 10078021,
+ 10434603, 10798439, 11169569, 11548036, 11933879, 12327139, 12727857, 13136073,
+ 13551826, 13975156, 14406100, 14844697, 15290987, 15745007, 16206795, 16676389,
+ 17153826, 17639142, 18132374, 18633560, 19142734, 19659934, 20185196, 20718552,
+ 21260042, 21809696, 22367554, 22933648, 23508010, 24090680, 24681686, 25281066,
+ 25888850, 26505076, 27129772, 27762974, 28404716, 29055026, 29713942, 30381490,
+ 31057708, 31742624, 32436272, 33138682, 33849884, 34569912, 35298800, 36036568,
+ 36783260, 37538896, 38303512, 39077136, 39859796, 40651528, 41452360, 42262316,
+ 43081432, 43909732, 44747252, 45594016, 46450052, 47315392, 48190064, 49074096,
+ 49967516, 50870356, 51782636, 52704392, 53635648, 54576432, 55526772, 56486700,
+ 57456236, 58435408, 59424248, 60422780, 61431036, 62449032, 63476804, 64514376,
+ 65561776, 66619028, 67686160, 68763192, 69850160, 70947088, 72053992, 73170912,
+ 74297864, 75434880, 76581976, 77739184, 78906536, 80084040, 81271736, 82469648,
+ 83677792, 84896192, 86124888, 87363888, 88613232, 89872928, 91143016, 92423512,
+ 93714432, 95015816, 96327688, 97650056, 98982952, 100326408, 101680440, 103045072,
+ 104420320, 105806224, 107202800, 108610064, 110028048, 111456776, 112896264, 114346544,
+ 115807632, 117279552, 118762328, 120255976, 121760536, 123276016, 124802440, 126339832,
+ 127888216, 129447616, 131018048, 132599544, 134192112, 135795792, 137410592, 139036528,
+ 140673648, 142321952, 143981456, 145652208, 147334208, 149027488, 150732064, 152447968,
+ 154175200, 155913792, 157663776, 159425168, 161197984, 162982240, 164777968, 166585184,
+ 168403904, 170234160, 172075968, 173929344, 175794320, 177670896, 179559120, 181458992,
+ 183370528, 185293776, 187228736, 189175424, 191133888, 193104112, 195086128, 197079968,
+ 199085648, 201103184, 203132592, 205173888, 207227120, 209292272, 211369392, 213458480,
+ 215559568, 217672656, 219797792, 221934976, 224084240, 226245600, 228419056, 230604656,
+ 232802400, 235012320, 237234432, 239468736, 241715280, 243974080, 246245120, 248528464,
+ 250824112, 253132064, 255452368, 257785040, 260130080, 262487520, 264857376, 267239664,
+};
+
+static stbir_uint8 stbir__linear_to_srgb_uchar(float f)
+{
+ int x = (int) (f * (1 << 28)); // has headroom so you don't need to clamp
+ int v = 0;
+ int i;
+
+ // Refine the guess with a short binary search.
+ i = v + 128; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
+ i = v + 64; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
+ i = v + 32; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
+ i = v + 16; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
+ i = v + 8; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
+ i = v + 4; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
+ i = v + 2; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
+ i = v + 1; if (x >= stbir__srgb_offset_to_linear_scaled[i]) v = i;
+
+ return (stbir_uint8) v;
+}
+#endif
+
+static float stbir__filter_trapezoid(float x, float scale)
+{
+ float halfscale = scale / 2;
+ float t = 0.5f + halfscale;
+ STBIR_ASSERT(scale <= 1);
+
+ x = (float)fabs(x);
+
+ if (x >= t)
+ return 0;
+ else
+ {
+ float r = 0.5f - halfscale;
+ if (x <= r)
+ return 1;
+ else
+ return (t - x) / scale;
+ }
+}
+
+static float stbir__support_trapezoid(float scale)
+{
+ STBIR_ASSERT(scale <= 1);
+ return 0.5f + scale / 2;
+}
+
+static float stbir__filter_triangle(float x, float s)
+{
+ STBIR__UNUSED_PARAM(s);
+
+ x = (float)fabs(x);
+
+ if (x <= 1.0f)
+ return 1 - x;
+ else
+ return 0;
+}
+
+static float stbir__filter_cubic(float x, float s)
+{
+ STBIR__UNUSED_PARAM(s);
+
+ x = (float)fabs(x);
+
+ if (x < 1.0f)
+ return (4 + x*x*(3*x - 6))/6;
+ else if (x < 2.0f)
+ return (8 + x*(-12 + x*(6 - x)))/6;
+
+ return (0.0f);
+}
+
+static float stbir__filter_catmullrom(float x, float s)
+{
+ STBIR__UNUSED_PARAM(s);
+
+ x = (float)fabs(x);
+
+ if (x < 1.0f)
+ return 1 - x*x*(2.5f - 1.5f*x);
+ else if (x < 2.0f)
+ return 2 - x*(4 + x*(0.5f*x - 2.5f));
+
+ return (0.0f);
+}
+
+static float stbir__filter_mitchell(float x, float s)
+{
+ STBIR__UNUSED_PARAM(s);
+
+ x = (float)fabs(x);
+
+ if (x < 1.0f)
+ return (16 + x*x*(21 * x - 36))/18;
+ else if (x < 2.0f)
+ return (32 + x*(-60 + x*(36 - 7*x)))/18;
+
+ return (0.0f);
+}
+
+static float stbir__support_zero(float s)
+{
+ STBIR__UNUSED_PARAM(s);
+ return 0;
+}
+
+static float stbir__support_one(float s)
+{
+ STBIR__UNUSED_PARAM(s);
+ return 1;
+}
+
+static float stbir__support_two(float s)
+{
+ STBIR__UNUSED_PARAM(s);
+ return 2;
+}
+
+static stbir__filter_info stbir__filter_info_table[] = {
+ { NULL, stbir__support_zero },
+ { stbir__filter_trapezoid, stbir__support_trapezoid },
+ { stbir__filter_triangle, stbir__support_one },
+ { stbir__filter_cubic, stbir__support_two },
+ { stbir__filter_catmullrom, stbir__support_two },
+ { stbir__filter_mitchell, stbir__support_two },
+};
+
+stbir__inline static int stbir__use_upsampling(float ratio)
+{
+ return ratio > 1;
+}
+
+stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info)
+{
+ return stbir__use_upsampling(stbir_info->horizontal_scale);
+}
+
+stbir__inline static int stbir__use_height_upsampling(stbir__info* stbir_info)
+{
+ return stbir__use_upsampling(stbir_info->vertical_scale);
+}
+
+// This is the maximum number of input samples that can affect an output sample
+// with the given filter
+static int stbir__get_filter_pixel_width(stbir_filter filter, float scale)
+{
+ STBIR_ASSERT(filter != 0);
+ STBIR_ASSERT(filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
+
+ if (stbir__use_upsampling(scale))
+ return (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2);
+ else
+ return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2 / scale);
+}
+
+// This is how much to expand buffers to account for filters seeking outside
+// the image boundaries.
+static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale)
+{
+ return stbir__get_filter_pixel_width(filter, scale) / 2;
+}
+
+static int stbir__get_coefficient_width(stbir_filter filter, float scale)
+{
+ if (stbir__use_upsampling(scale))
+ return (int)ceil(stbir__filter_info_table[filter].support(1 / scale) * 2);
+ else
+ return (int)ceil(stbir__filter_info_table[filter].support(scale) * 2);
+}
+
+static int stbir__get_contributors(float scale, stbir_filter filter, int input_size, int output_size)
+{
+ if (stbir__use_upsampling(scale))
+ return output_size;
+ else
+ return (input_size + stbir__get_filter_pixel_margin(filter, scale) * 2);
+}
+
+static int stbir__get_total_horizontal_coefficients(stbir__info* info)
+{
+ return info->horizontal_num_contributors
+ * stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale);
+}
+
+static int stbir__get_total_vertical_coefficients(stbir__info* info)
+{
+ return info->vertical_num_contributors
+ * stbir__get_coefficient_width (info->vertical_filter, info->vertical_scale);
+}
+
+static stbir__contributors* stbir__get_contributor(stbir__contributors* contributors, int n)
+{
+ return &contributors[n];
+}
+
+// For perf reasons this code is duplicated in stbir__resample_horizontal_upsample/downsample,
+// if you change it here change it there too.
+static float* stbir__get_coefficient(float* coefficients, stbir_filter filter, float scale, int n, int c)
+{
+ int width = stbir__get_coefficient_width(filter, scale);
+ return &coefficients[width*n + c];
+}
+
+static int stbir__edge_wrap_slow(stbir_edge edge, int n, int max)
+{
+ switch (edge)
+ {
+ case STBIR_EDGE_ZERO:
+ return 0; // we'll decode the wrong pixel here, and then overwrite with 0s later
+
+ case STBIR_EDGE_CLAMP:
+ if (n < 0)
+ return 0;
+
+ if (n >= max)
+ return max - 1;
+
+ return n; // NOTREACHED
+
+ case STBIR_EDGE_REFLECT:
+ {
+ if (n < 0)
+ {
+ if (n < max)
+ return -n;
+ else
+ return max - 1;
+ }
+
+ if (n >= max)
+ {
+ int max2 = max * 2;
+ if (n >= max2)
+ return 0;
+ else
+ return max2 - n - 1;
+ }
+
+ return n; // NOTREACHED
+ }
+
+ case STBIR_EDGE_WRAP:
+ if (n >= 0)
+ return (n % max);
+ else
+ {
+ int m = (-n) % max;
+
+ if (m != 0)
+ m = max - m;
+
+ return (m);
+ }
+ // NOTREACHED
+
+ default:
+ STBIR_ASSERT(!"Unimplemented edge type");
+ return 0;
+ }
+}
+
+stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max)
+{
+ // avoid per-pixel switch
+ if (n >= 0 && n < max)
+ return n;
+ return stbir__edge_wrap_slow(edge, n, max);
+}
+
+// What input pixels contribute to this output pixel?
+static void stbir__calculate_sample_range_upsample(int n, float out_filter_radius, float scale_ratio, float out_shift, int* in_first_pixel, int* in_last_pixel, float* in_center_of_out)
+{
+ float out_pixel_center = (float)n + 0.5f;
+ float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius;
+ float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius;
+
+ float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) / scale_ratio;
+ float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) / scale_ratio;
+
+ *in_center_of_out = (out_pixel_center + out_shift) / scale_ratio;
+ *in_first_pixel = (int)(floor(in_pixel_influence_lowerbound + 0.5));
+ *in_last_pixel = (int)(floor(in_pixel_influence_upperbound - 0.5));
+}
+
+// What output pixels does this input pixel contribute to?
+static void stbir__calculate_sample_range_downsample(int n, float in_pixels_radius, float scale_ratio, float out_shift, int* out_first_pixel, int* out_last_pixel, float* out_center_of_in)
+{
+ float in_pixel_center = (float)n + 0.5f;
+ float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius;
+ float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius;
+
+ float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale_ratio - out_shift;
+ float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale_ratio - out_shift;
+
+ *out_center_of_in = in_pixel_center * scale_ratio - out_shift;
+ *out_first_pixel = (int)(floor(out_pixel_influence_lowerbound + 0.5));
+ *out_last_pixel = (int)(floor(out_pixel_influence_upperbound - 0.5));
+}
+
+static void stbir__calculate_coefficients_upsample(stbir_filter filter, float scale, int in_first_pixel, int in_last_pixel, float in_center_of_out, stbir__contributors* contributor, float* coefficient_group)
+{
+ int i;
+ float total_filter = 0;
+ float filter_scale;
+
+ STBIR_ASSERT(in_last_pixel - in_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(1/scale) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
+
+ contributor->n0 = in_first_pixel;
+ contributor->n1 = in_last_pixel;
+
+ STBIR_ASSERT(contributor->n1 >= contributor->n0);
+
+ for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
+ {
+ float in_pixel_center = (float)(i + in_first_pixel) + 0.5f;
+ coefficient_group[i] = stbir__filter_info_table[filter].kernel(in_center_of_out - in_pixel_center, 1 / scale);
+
+ // If the coefficient is zero, skip it. (Don't do the <0 check here, we want the influence of those outside pixels.)
+ if (i == 0 && !coefficient_group[i])
+ {
+ contributor->n0 = ++in_first_pixel;
+ i--;
+ continue;
+ }
+
+ total_filter += coefficient_group[i];
+ }
+
+ // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be.
+ // It would be true in exact math but is at best approximately true in floating-point math,
+ // and it would not make sense to try and put actual bounds on this here because it depends
+ // on the image aspect ratio which can get pretty extreme.
+ //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1/scale) == 0);
+
+ STBIR_ASSERT(total_filter > 0.9);
+ STBIR_ASSERT(total_filter < 1.1f); // Make sure it's not way off.
+
+ // Make sure the sum of all coefficients is 1.
+ filter_scale = 1 / total_filter;
+
+ for (i = 0; i <= in_last_pixel - in_first_pixel; i++)
+ coefficient_group[i] *= filter_scale;
+
+ for (i = in_last_pixel - in_first_pixel; i >= 0; i--)
+ {
+ if (coefficient_group[i])
+ break;
+
+ // This line has no weight. We can skip it.
+ contributor->n1 = contributor->n0 + i - 1;
+ }
+}
+
+static void stbir__calculate_coefficients_downsample(stbir_filter filter, float scale_ratio, int out_first_pixel, int out_last_pixel, float out_center_of_in, stbir__contributors* contributor, float* coefficient_group)
+{
+ int i;
+
+ STBIR_ASSERT(out_last_pixel - out_first_pixel <= (int)ceil(stbir__filter_info_table[filter].support(scale_ratio) * 2)); // Taken directly from stbir__get_coefficient_width() which we can't call because we don't know if we're horizontal or vertical.
+
+ contributor->n0 = out_first_pixel;
+ contributor->n1 = out_last_pixel;
+
+ STBIR_ASSERT(contributor->n1 >= contributor->n0);
+
+ for (i = 0; i <= out_last_pixel - out_first_pixel; i++)
+ {
+ float out_pixel_center = (float)(i + out_first_pixel) + 0.5f;
+ float x = out_pixel_center - out_center_of_in;
+ coefficient_group[i] = stbir__filter_info_table[filter].kernel(x, scale_ratio) * scale_ratio;
+ }
+
+ // NOTE(fg): Not actually true in general, nor is there any reason to expect it should be.
+ // It would be true in exact math but is at best approximately true in floating-point math,
+ // and it would not make sense to try and put actual bounds on this here because it depends
+ // on the image aspect ratio which can get pretty extreme.
+ //STBIR_ASSERT(stbir__filter_info_table[filter].kernel((float)(out_last_pixel + 1) + 0.5f - out_center_of_in, scale_ratio) == 0);
+
+ for (i = out_last_pixel - out_first_pixel; i >= 0; i--)
+ {
+ if (coefficient_group[i])
+ break;
+
+ // This line has no weight. We can skip it.
+ contributor->n1 = contributor->n0 + i - 1;
+ }
+}
+
+static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, int input_size, int output_size)
+{
+ int num_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size);
+ int num_coefficients = stbir__get_coefficient_width(filter, scale_ratio);
+ int i, j;
+ int skip;
+
+ for (i = 0; i < output_size; i++)
+ {
+ float scale;
+ float total = 0;
+
+ for (j = 0; j < num_contributors; j++)
+ {
+ if (i >= contributors[j].n0 && i <= contributors[j].n1)
+ {
+ float coefficient = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0);
+ total += coefficient;
+ }
+ else if (i < contributors[j].n0)
+ break;
+ }
+
+ STBIR_ASSERT(total > 0.9f);
+ STBIR_ASSERT(total < 1.1f);
+
+ scale = 1 / total;
+
+ for (j = 0; j < num_contributors; j++)
+ {
+ if (i >= contributors[j].n0 && i <= contributors[j].n1)
+ *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i - contributors[j].n0) *= scale;
+ else if (i < contributors[j].n0)
+ break;
+ }
+ }
+
+ // Optimize: Skip zero coefficients and contributions outside of image bounds.
+ // Do this after normalizing because normalization depends on the n0/n1 values.
+ for (j = 0; j < num_contributors; j++)
+ {
+ int range, max, width;
+
+ skip = 0;
+ while (*stbir__get_coefficient(coefficients, filter, scale_ratio, j, skip) == 0)
+ skip++;
+
+ contributors[j].n0 += skip;
+
+ while (contributors[j].n0 < 0)
+ {
+ contributors[j].n0++;
+ skip++;
+ }
+
+ range = contributors[j].n1 - contributors[j].n0 + 1;
+ max = stbir__min(num_coefficients, range);
+
+ width = stbir__get_coefficient_width(filter, scale_ratio);
+ for (i = 0; i < max; i++)
+ {
+ if (i + skip >= width)
+ break;
+
+ *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i) = *stbir__get_coefficient(coefficients, filter, scale_ratio, j, i + skip);
+ }
+
+ continue;
+ }
+
+ // Using min to avoid writing into invalid pixels.
+ for (i = 0; i < num_contributors; i++)
+ contributors[i].n1 = stbir__min(contributors[i].n1, output_size - 1);
+}
+
+// Each scan line uses the same kernel values so we should calculate the kernel
+// values once and then we can use them for every scan line.
+static void stbir__calculate_filters(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, float shift, int input_size, int output_size)
+{
+ int n;
+ int total_contributors = stbir__get_contributors(scale_ratio, filter, input_size, output_size);
+
+ if (stbir__use_upsampling(scale_ratio))
+ {
+ float out_pixels_radius = stbir__filter_info_table[filter].support(1 / scale_ratio) * scale_ratio;
+
+ // Looping through out pixels
+ for (n = 0; n < total_contributors; n++)
+ {
+ float in_center_of_out; // Center of the current out pixel in the in pixel space
+ int in_first_pixel, in_last_pixel;
+
+ stbir__calculate_sample_range_upsample(n, out_pixels_radius, scale_ratio, shift, &in_first_pixel, &in_last_pixel, &in_center_of_out);
+
+ stbir__calculate_coefficients_upsample(filter, scale_ratio, in_first_pixel, in_last_pixel, in_center_of_out, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0));
+ }
+ }
+ else
+ {
+ float in_pixels_radius = stbir__filter_info_table[filter].support(scale_ratio) / scale_ratio;
+
+ // Looping through in pixels
+ for (n = 0; n < total_contributors; n++)
+ {
+ float out_center_of_in; // Center of the current out pixel in the in pixel space
+ int out_first_pixel, out_last_pixel;
+ int n_adjusted = n - stbir__get_filter_pixel_margin(filter, scale_ratio);
+
+ stbir__calculate_sample_range_downsample(n_adjusted, in_pixels_radius, scale_ratio, shift, &out_first_pixel, &out_last_pixel, &out_center_of_in);
+
+ stbir__calculate_coefficients_downsample(filter, scale_ratio, out_first_pixel, out_last_pixel, out_center_of_in, stbir__get_contributor(contributors, n), stbir__get_coefficient(coefficients, filter, scale_ratio, n, 0));
+ }
+
+ stbir__normalize_downsample_coefficients(contributors, coefficients, filter, scale_ratio, input_size, output_size);
+ }
+}
+
+static float* stbir__get_decode_buffer(stbir__info* stbir_info)
+{
+ // The 0 index of the decode buffer starts after the margin. This makes
+ // it okay to use negative indexes on the decode buffer.
+ return &stbir_info->decode_buffer[stbir_info->horizontal_filter_pixel_margin * stbir_info->channels];
+}
+
+#define STBIR__DECODE(type, colorspace) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace))
+
+static void stbir__decode_scanline(stbir__info* stbir_info, int n)
+{
+ int c;
+ int channels = stbir_info->channels;
+ int alpha_channel = stbir_info->alpha_channel;
+ int type = stbir_info->type;
+ int colorspace = stbir_info->colorspace;
+ int input_w = stbir_info->input_w;
+ size_t input_stride_bytes = stbir_info->input_stride_bytes;
+ float* decode_buffer = stbir__get_decode_buffer(stbir_info);
+ stbir_edge edge_horizontal = stbir_info->edge_horizontal;
+ stbir_edge edge_vertical = stbir_info->edge_vertical;
+ size_t in_buffer_row_offset = stbir__edge_wrap(edge_vertical, n, stbir_info->input_h) * input_stride_bytes;
+ const void* input_data = (char *) stbir_info->input_data + in_buffer_row_offset;
+ int max_x = input_w + stbir_info->horizontal_filter_pixel_margin;
+ int decode = STBIR__DECODE(type, colorspace);
+
+ int x = -stbir_info->horizontal_filter_pixel_margin;
+
+ // special handling for STBIR_EDGE_ZERO because it needs to return an item that doesn't appear in the input,
+ // and we want to avoid paying overhead on every pixel if not STBIR_EDGE_ZERO
+ if (edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->input_h))
+ {
+ for (; x < max_x; x++)
+ for (c = 0; c < channels; c++)
+ decode_buffer[x*channels + c] = 0;
+ return;
+ }
+
+ switch (decode)
+ {
+ case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
+ for (; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+ int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
+ for (c = 0; c < channels; c++)
+ decode_buffer[decode_pixel_index + c] = ((float)((const unsigned char*)input_data)[input_pixel_index + c]) / stbir__max_uint8_as_float;
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
+ for (; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+ int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
+ for (c = 0; c < channels; c++)
+ decode_buffer[decode_pixel_index + c] = stbir__srgb_uchar_to_linear_float[((const unsigned char*)input_data)[input_pixel_index + c]];
+
+ if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned char*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint8_as_float;
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
+ for (; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+ int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
+ for (c = 0; c < channels; c++)
+ decode_buffer[decode_pixel_index + c] = ((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float;
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
+ for (; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+ int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
+ for (c = 0; c < channels; c++)
+ decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((float)((const unsigned short*)input_data)[input_pixel_index + c]) / stbir__max_uint16_as_float);
+
+ if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ decode_buffer[decode_pixel_index + alpha_channel] = ((float)((const unsigned short*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint16_as_float;
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
+ for (; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+ int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
+ for (c = 0; c < channels; c++)
+ decode_buffer[decode_pixel_index + c] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float);
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
+ for (; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+ int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
+ for (c = 0; c < channels; c++)
+ decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear((float)(((double)((const unsigned int*)input_data)[input_pixel_index + c]) / stbir__max_uint32_as_float));
+
+ if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ decode_buffer[decode_pixel_index + alpha_channel] = (float)(((double)((const unsigned int*)input_data)[input_pixel_index + alpha_channel]) / stbir__max_uint32_as_float);
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
+ for (; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+ int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
+ for (c = 0; c < channels; c++)
+ decode_buffer[decode_pixel_index + c] = ((const float*)input_data)[input_pixel_index + c];
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
+ for (; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+ int input_pixel_index = stbir__edge_wrap(edge_horizontal, x, input_w) * channels;
+ for (c = 0; c < channels; c++)
+ decode_buffer[decode_pixel_index + c] = stbir__srgb_to_linear(((const float*)input_data)[input_pixel_index + c]);
+
+ if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ decode_buffer[decode_pixel_index + alpha_channel] = ((const float*)input_data)[input_pixel_index + alpha_channel];
+ }
+
+ break;
+
+ default:
+ STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
+ break;
+ }
+
+ if (!(stbir_info->flags & STBIR_FLAG_ALPHA_PREMULTIPLIED))
+ {
+ for (x = -stbir_info->horizontal_filter_pixel_margin; x < max_x; x++)
+ {
+ int decode_pixel_index = x * channels;
+
+ // If the alpha value is 0 it will clobber the color values. Make sure it's not.
+ float alpha = decode_buffer[decode_pixel_index + alpha_channel];
+#ifndef STBIR_NO_ALPHA_EPSILON
+ if (stbir_info->type != STBIR_TYPE_FLOAT) {
+ alpha += STBIR_ALPHA_EPSILON;
+ decode_buffer[decode_pixel_index + alpha_channel] = alpha;
+ }
+#endif
+ for (c = 0; c < channels; c++)
+ {
+ if (c == alpha_channel)
+ continue;
+
+ decode_buffer[decode_pixel_index + c] *= alpha;
+ }
+ }
+ }
+
+ if (edge_horizontal == STBIR_EDGE_ZERO)
+ {
+ for (x = -stbir_info->horizontal_filter_pixel_margin; x < 0; x++)
+ {
+ for (c = 0; c < channels; c++)
+ decode_buffer[x*channels + c] = 0;
+ }
+ for (x = input_w; x < max_x; x++)
+ {
+ for (c = 0; c < channels; c++)
+ decode_buffer[x*channels + c] = 0;
+ }
+ }
+}
+
+static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length)
+{
+ return &ring_buffer[index * ring_buffer_length];
+}
+
+static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n)
+{
+ int ring_buffer_index;
+ float* ring_buffer;
+
+ stbir_info->ring_buffer_last_scanline = n;
+
+ if (stbir_info->ring_buffer_begin_index < 0)
+ {
+ ring_buffer_index = stbir_info->ring_buffer_begin_index = 0;
+ stbir_info->ring_buffer_first_scanline = n;
+ }
+ else
+ {
+ ring_buffer_index = (stbir_info->ring_buffer_begin_index + (stbir_info->ring_buffer_last_scanline - stbir_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries;
+ STBIR_ASSERT(ring_buffer_index != stbir_info->ring_buffer_begin_index);
+ }
+
+ ring_buffer = stbir__get_ring_buffer_entry(stbir_info->ring_buffer, ring_buffer_index, stbir_info->ring_buffer_length_bytes / sizeof(float));
+ memset(ring_buffer, 0, stbir_info->ring_buffer_length_bytes);
+
+ return ring_buffer;
+}
+
+
+static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer)
+{
+ int x, k;
+ int output_w = stbir_info->output_w;
+ int channels = stbir_info->channels;
+ float* decode_buffer = stbir__get_decode_buffer(stbir_info);
+ stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors;
+ float* horizontal_coefficients = stbir_info->horizontal_coefficients;
+ int coefficient_width = stbir_info->horizontal_coefficient_width;
+
+ for (x = 0; x < output_w; x++)
+ {
+ int n0 = horizontal_contributors[x].n0;
+ int n1 = horizontal_contributors[x].n1;
+
+ int out_pixel_index = x * channels;
+ int coefficient_group = coefficient_width * x;
+ int coefficient_counter = 0;
+
+ STBIR_ASSERT(n1 >= n0);
+ STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin);
+ STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin);
+ STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
+ STBIR_ASSERT(n1 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
+
+ switch (channels) {
+ case 1:
+ for (k = n0; k <= n1; k++)
+ {
+ int in_pixel_index = k * 1;
+ float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
+ STBIR_ASSERT(coefficient != 0);
+ output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
+ }
+ break;
+ case 2:
+ for (k = n0; k <= n1; k++)
+ {
+ int in_pixel_index = k * 2;
+ float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
+ STBIR_ASSERT(coefficient != 0);
+ output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
+ output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
+ }
+ break;
+ case 3:
+ for (k = n0; k <= n1; k++)
+ {
+ int in_pixel_index = k * 3;
+ float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
+ STBIR_ASSERT(coefficient != 0);
+ output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
+ output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
+ output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
+ }
+ break;
+ case 4:
+ for (k = n0; k <= n1; k++)
+ {
+ int in_pixel_index = k * 4;
+ float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
+ STBIR_ASSERT(coefficient != 0);
+ output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
+ output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
+ output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
+ output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
+ }
+ break;
+ default:
+ for (k = n0; k <= n1; k++)
+ {
+ int in_pixel_index = k * channels;
+ float coefficient = horizontal_coefficients[coefficient_group + coefficient_counter++];
+ int c;
+ STBIR_ASSERT(coefficient != 0);
+ for (c = 0; c < channels; c++)
+ output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
+ }
+ break;
+ }
+ }
+}
+
+static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer)
+{
+ int x, k;
+ int input_w = stbir_info->input_w;
+ int channels = stbir_info->channels;
+ float* decode_buffer = stbir__get_decode_buffer(stbir_info);
+ stbir__contributors* horizontal_contributors = stbir_info->horizontal_contributors;
+ float* horizontal_coefficients = stbir_info->horizontal_coefficients;
+ int coefficient_width = stbir_info->horizontal_coefficient_width;
+ int filter_pixel_margin = stbir_info->horizontal_filter_pixel_margin;
+ int max_x = input_w + filter_pixel_margin * 2;
+
+ STBIR_ASSERT(!stbir__use_width_upsampling(stbir_info));
+
+ switch (channels) {
+ case 1:
+ for (x = 0; x < max_x; x++)
+ {
+ int n0 = horizontal_contributors[x].n0;
+ int n1 = horizontal_contributors[x].n1;
+
+ int in_x = x - filter_pixel_margin;
+ int in_pixel_index = in_x * 1;
+ int max_n = n1;
+ int coefficient_group = coefficient_width * x;
+
+ for (k = n0; k <= max_n; k++)
+ {
+ int out_pixel_index = k * 1;
+ float coefficient = horizontal_coefficients[coefficient_group + k - n0];
+ output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
+ }
+ }
+ break;
+
+ case 2:
+ for (x = 0; x < max_x; x++)
+ {
+ int n0 = horizontal_contributors[x].n0;
+ int n1 = horizontal_contributors[x].n1;
+
+ int in_x = x - filter_pixel_margin;
+ int in_pixel_index = in_x * 2;
+ int max_n = n1;
+ int coefficient_group = coefficient_width * x;
+
+ for (k = n0; k <= max_n; k++)
+ {
+ int out_pixel_index = k * 2;
+ float coefficient = horizontal_coefficients[coefficient_group + k - n0];
+ output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
+ output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
+ }
+ }
+ break;
+
+ case 3:
+ for (x = 0; x < max_x; x++)
+ {
+ int n0 = horizontal_contributors[x].n0;
+ int n1 = horizontal_contributors[x].n1;
+
+ int in_x = x - filter_pixel_margin;
+ int in_pixel_index = in_x * 3;
+ int max_n = n1;
+ int coefficient_group = coefficient_width * x;
+
+ for (k = n0; k <= max_n; k++)
+ {
+ int out_pixel_index = k * 3;
+ float coefficient = horizontal_coefficients[coefficient_group + k - n0];
+ output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
+ output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
+ output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
+ }
+ }
+ break;
+
+ case 4:
+ for (x = 0; x < max_x; x++)
+ {
+ int n0 = horizontal_contributors[x].n0;
+ int n1 = horizontal_contributors[x].n1;
+
+ int in_x = x - filter_pixel_margin;
+ int in_pixel_index = in_x * 4;
+ int max_n = n1;
+ int coefficient_group = coefficient_width * x;
+
+ for (k = n0; k <= max_n; k++)
+ {
+ int out_pixel_index = k * 4;
+ float coefficient = horizontal_coefficients[coefficient_group + k - n0];
+ output_buffer[out_pixel_index + 0] += decode_buffer[in_pixel_index + 0] * coefficient;
+ output_buffer[out_pixel_index + 1] += decode_buffer[in_pixel_index + 1] * coefficient;
+ output_buffer[out_pixel_index + 2] += decode_buffer[in_pixel_index + 2] * coefficient;
+ output_buffer[out_pixel_index + 3] += decode_buffer[in_pixel_index + 3] * coefficient;
+ }
+ }
+ break;
+
+ default:
+ for (x = 0; x < max_x; x++)
+ {
+ int n0 = horizontal_contributors[x].n0;
+ int n1 = horizontal_contributors[x].n1;
+
+ int in_x = x - filter_pixel_margin;
+ int in_pixel_index = in_x * channels;
+ int max_n = n1;
+ int coefficient_group = coefficient_width * x;
+
+ for (k = n0; k <= max_n; k++)
+ {
+ int c;
+ int out_pixel_index = k * channels;
+ float coefficient = horizontal_coefficients[coefficient_group + k - n0];
+ for (c = 0; c < channels; c++)
+ output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
+ }
+ }
+ break;
+ }
+}
+
+static void stbir__decode_and_resample_upsample(stbir__info* stbir_info, int n)
+{
+ // Decode the nth scanline from the source image into the decode buffer.
+ stbir__decode_scanline(stbir_info, n);
+
+ // Now resample it into the ring buffer.
+ if (stbir__use_width_upsampling(stbir_info))
+ stbir__resample_horizontal_upsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n));
+ else
+ stbir__resample_horizontal_downsample(stbir_info, stbir__add_empty_ring_buffer_entry(stbir_info, n));
+
+ // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling.
+}
+
+static void stbir__decode_and_resample_downsample(stbir__info* stbir_info, int n)
+{
+ // Decode the nth scanline from the source image into the decode buffer.
+ stbir__decode_scanline(stbir_info, n);
+
+ memset(stbir_info->horizontal_buffer, 0, stbir_info->output_w * stbir_info->channels * sizeof(float));
+
+ // Now resample it into the horizontal buffer.
+ if (stbir__use_width_upsampling(stbir_info))
+ stbir__resample_horizontal_upsample(stbir_info, stbir_info->horizontal_buffer);
+ else
+ stbir__resample_horizontal_downsample(stbir_info, stbir_info->horizontal_buffer);
+
+ // Now it's sitting in the horizontal buffer ready to be distributed into the ring buffers.
+}
+
+// Get the specified scan line from the ring buffer.
+static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length)
+{
+ int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries;
+ return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length);
+}
+
+
+static void stbir__encode_scanline(stbir__info* stbir_info, int num_pixels, void *output_buffer, float *encode_buffer, int channels, int alpha_channel, int decode)
+{
+ int x;
+ int n;
+ int num_nonalpha;
+ stbir_uint16 nonalpha[STBIR_MAX_CHANNELS];
+
+ if (!(stbir_info->flags&STBIR_FLAG_ALPHA_PREMULTIPLIED))
+ {
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ float alpha = encode_buffer[pixel_index + alpha_channel];
+ float reciprocal_alpha = alpha ? 1.0f / alpha : 0;
+
+ // unrolling this produced a 1% slowdown upscaling a large RGBA linear-space image on my machine - stb
+ for (n = 0; n < channels; n++)
+ if (n != alpha_channel)
+ encode_buffer[pixel_index + n] *= reciprocal_alpha;
+
+ // We added in a small epsilon to prevent the color channel from being deleted with zero alpha.
+ // Because we only add it for integer types, it will automatically be discarded on integer
+ // conversion, so we don't need to subtract it back out (which would be problematic for
+ // numeric precision reasons).
+ }
+ }
+
+ // build a table of all channels that need colorspace correction, so
+ // we don't perform colorspace correction on channels that don't need it.
+ for (x = 0, num_nonalpha = 0; x < channels; ++x)
+ {
+ if (x != alpha_channel || (stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ {
+ nonalpha[num_nonalpha++] = (stbir_uint16)x;
+ }
+ }
+
+ #define STBIR__ROUND_INT(f) ((int) ((f)+0.5))
+ #define STBIR__ROUND_UINT(f) ((stbir_uint32) ((f)+0.5))
+
+ #ifdef STBIR__SATURATE_INT
+ #define STBIR__ENCODE_LINEAR8(f) stbir__saturate8 (STBIR__ROUND_INT((f) * stbir__max_uint8_as_float ))
+ #define STBIR__ENCODE_LINEAR16(f) stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float))
+ #else
+ #define STBIR__ENCODE_LINEAR8(f) (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float )
+ #define STBIR__ENCODE_LINEAR16(f) (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float)
+ #endif
+
+ switch (decode)
+ {
+ case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_LINEAR):
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ for (n = 0; n < channels; n++)
+ {
+ int index = pixel_index + n;
+ ((unsigned char*)output_buffer)[index] = STBIR__ENCODE_LINEAR8(encode_buffer[index]);
+ }
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT8, STBIR_COLORSPACE_SRGB):
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ for (n = 0; n < num_nonalpha; n++)
+ {
+ int index = pixel_index + nonalpha[n];
+ ((unsigned char*)output_buffer)[index] = stbir__linear_to_srgb_uchar(encode_buffer[index]);
+ }
+
+ if (!(stbir_info->flags & STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ ((unsigned char *)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR8(encode_buffer[pixel_index+alpha_channel]);
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_LINEAR):
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ for (n = 0; n < channels; n++)
+ {
+ int index = pixel_index + n;
+ ((unsigned short*)output_buffer)[index] = STBIR__ENCODE_LINEAR16(encode_buffer[index]);
+ }
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT16, STBIR_COLORSPACE_SRGB):
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ for (n = 0; n < num_nonalpha; n++)
+ {
+ int index = pixel_index + nonalpha[n];
+ ((unsigned short*)output_buffer)[index] = (unsigned short)STBIR__ROUND_INT(stbir__linear_to_srgb(stbir__saturate(encode_buffer[index])) * stbir__max_uint16_as_float);
+ }
+
+ if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ ((unsigned short*)output_buffer)[pixel_index + alpha_channel] = STBIR__ENCODE_LINEAR16(encode_buffer[pixel_index + alpha_channel]);
+ }
+
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_LINEAR):
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ for (n = 0; n < channels; n++)
+ {
+ int index = pixel_index + n;
+ ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__saturate(encode_buffer[index])) * stbir__max_uint32_as_float);
+ }
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_UINT32, STBIR_COLORSPACE_SRGB):
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ for (n = 0; n < num_nonalpha; n++)
+ {
+ int index = pixel_index + nonalpha[n];
+ ((unsigned int*)output_buffer)[index] = (unsigned int)STBIR__ROUND_UINT(((double)stbir__linear_to_srgb(stbir__saturate(encode_buffer[index]))) * stbir__max_uint32_as_float);
+ }
+
+ if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ ((unsigned int*)output_buffer)[pixel_index + alpha_channel] = (unsigned int)STBIR__ROUND_INT(((double)stbir__saturate(encode_buffer[pixel_index + alpha_channel])) * stbir__max_uint32_as_float);
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_LINEAR):
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ for (n = 0; n < channels; n++)
+ {
+ int index = pixel_index + n;
+ ((float*)output_buffer)[index] = encode_buffer[index];
+ }
+ }
+ break;
+
+ case STBIR__DECODE(STBIR_TYPE_FLOAT, STBIR_COLORSPACE_SRGB):
+ for (x=0; x < num_pixels; ++x)
+ {
+ int pixel_index = x*channels;
+
+ for (n = 0; n < num_nonalpha; n++)
+ {
+ int index = pixel_index + nonalpha[n];
+ ((float*)output_buffer)[index] = stbir__linear_to_srgb(encode_buffer[index]);
+ }
+
+ if (!(stbir_info->flags&STBIR_FLAG_ALPHA_USES_COLORSPACE))
+ ((float*)output_buffer)[pixel_index + alpha_channel] = encode_buffer[pixel_index + alpha_channel];
+ }
+ break;
+
+ default:
+ STBIR_ASSERT(!"Unknown type/colorspace/channels combination.");
+ break;
+ }
+}
+
+static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n)
+{
+ int x, k;
+ int output_w = stbir_info->output_w;
+ stbir__contributors* vertical_contributors = stbir_info->vertical_contributors;
+ float* vertical_coefficients = stbir_info->vertical_coefficients;
+ int channels = stbir_info->channels;
+ int alpha_channel = stbir_info->alpha_channel;
+ int type = stbir_info->type;
+ int colorspace = stbir_info->colorspace;
+ int ring_buffer_entries = stbir_info->ring_buffer_num_entries;
+ void* output_data = stbir_info->output_data;
+ float* encode_buffer = stbir_info->encode_buffer;
+ int decode = STBIR__DECODE(type, colorspace);
+ int coefficient_width = stbir_info->vertical_coefficient_width;
+ int coefficient_counter;
+ int contributor = n;
+
+ float* ring_buffer = stbir_info->ring_buffer;
+ int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index;
+ int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline;
+ int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
+
+ int n0,n1, output_row_start;
+ int coefficient_group = coefficient_width * contributor;
+
+ n0 = vertical_contributors[contributor].n0;
+ n1 = vertical_contributors[contributor].n1;
+
+ output_row_start = n * stbir_info->output_stride_bytes;
+
+ STBIR_ASSERT(stbir__use_height_upsampling(stbir_info));
+
+ memset(encode_buffer, 0, output_w * sizeof(float) * channels);
+
+ // I tried reblocking this for better cache usage of encode_buffer
+ // (using x_outer, k, x_inner), but it lost speed. -- stb
+
+ coefficient_counter = 0;
+ switch (channels) {
+ case 1:
+ for (k = n0; k <= n1; k++)
+ {
+ int coefficient_index = coefficient_counter++;
+ float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
+ float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
+ for (x = 0; x < output_w; ++x)
+ {
+ int in_pixel_index = x * 1;
+ encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
+ }
+ }
+ break;
+ case 2:
+ for (k = n0; k <= n1; k++)
+ {
+ int coefficient_index = coefficient_counter++;
+ float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
+ float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
+ for (x = 0; x < output_w; ++x)
+ {
+ int in_pixel_index = x * 2;
+ encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
+ encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
+ }
+ }
+ break;
+ case 3:
+ for (k = n0; k <= n1; k++)
+ {
+ int coefficient_index = coefficient_counter++;
+ float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
+ float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
+ for (x = 0; x < output_w; ++x)
+ {
+ int in_pixel_index = x * 3;
+ encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
+ encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
+ encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
+ }
+ }
+ break;
+ case 4:
+ for (k = n0; k <= n1; k++)
+ {
+ int coefficient_index = coefficient_counter++;
+ float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
+ float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
+ for (x = 0; x < output_w; ++x)
+ {
+ int in_pixel_index = x * 4;
+ encode_buffer[in_pixel_index + 0] += ring_buffer_entry[in_pixel_index + 0] * coefficient;
+ encode_buffer[in_pixel_index + 1] += ring_buffer_entry[in_pixel_index + 1] * coefficient;
+ encode_buffer[in_pixel_index + 2] += ring_buffer_entry[in_pixel_index + 2] * coefficient;
+ encode_buffer[in_pixel_index + 3] += ring_buffer_entry[in_pixel_index + 3] * coefficient;
+ }
+ }
+ break;
+ default:
+ for (k = n0; k <= n1; k++)
+ {
+ int coefficient_index = coefficient_counter++;
+ float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
+ float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
+ for (x = 0; x < output_w; ++x)
+ {
+ int in_pixel_index = x * channels;
+ int c;
+ for (c = 0; c < channels; c++)
+ encode_buffer[in_pixel_index + c] += ring_buffer_entry[in_pixel_index + c] * coefficient;
+ }
+ }
+ break;
+ }
+ stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, encode_buffer, channels, alpha_channel, decode);
+}
+
+static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n)
+{
+ int x, k;
+ int output_w = stbir_info->output_w;
+ stbir__contributors* vertical_contributors = stbir_info->vertical_contributors;
+ float* vertical_coefficients = stbir_info->vertical_coefficients;
+ int channels = stbir_info->channels;
+ int ring_buffer_entries = stbir_info->ring_buffer_num_entries;
+ float* horizontal_buffer = stbir_info->horizontal_buffer;
+ int coefficient_width = stbir_info->vertical_coefficient_width;
+ int contributor = n + stbir_info->vertical_filter_pixel_margin;
+
+ float* ring_buffer = stbir_info->ring_buffer;
+ int ring_buffer_begin_index = stbir_info->ring_buffer_begin_index;
+ int ring_buffer_first_scanline = stbir_info->ring_buffer_first_scanline;
+ int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
+ int n0,n1;
+
+ n0 = vertical_contributors[contributor].n0;
+ n1 = vertical_contributors[contributor].n1;
+
+ STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info));
+
+ for (k = n0; k <= n1; k++)
+ {
+ int coefficient_index = k - n0;
+ int coefficient_group = coefficient_width * contributor;
+ float coefficient = vertical_coefficients[coefficient_group + coefficient_index];
+
+ float* ring_buffer_entry = stbir__get_ring_buffer_scanline(k, ring_buffer, ring_buffer_begin_index, ring_buffer_first_scanline, ring_buffer_entries, ring_buffer_length);
+
+ switch (channels) {
+ case 1:
+ for (x = 0; x < output_w; x++)
+ {
+ int in_pixel_index = x * 1;
+ ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
+ }
+ break;
+ case 2:
+ for (x = 0; x < output_w; x++)
+ {
+ int in_pixel_index = x * 2;
+ ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
+ ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
+ }
+ break;
+ case 3:
+ for (x = 0; x < output_w; x++)
+ {
+ int in_pixel_index = x * 3;
+ ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
+ ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
+ ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
+ }
+ break;
+ case 4:
+ for (x = 0; x < output_w; x++)
+ {
+ int in_pixel_index = x * 4;
+ ring_buffer_entry[in_pixel_index + 0] += horizontal_buffer[in_pixel_index + 0] * coefficient;
+ ring_buffer_entry[in_pixel_index + 1] += horizontal_buffer[in_pixel_index + 1] * coefficient;
+ ring_buffer_entry[in_pixel_index + 2] += horizontal_buffer[in_pixel_index + 2] * coefficient;
+ ring_buffer_entry[in_pixel_index + 3] += horizontal_buffer[in_pixel_index + 3] * coefficient;
+ }
+ break;
+ default:
+ for (x = 0; x < output_w; x++)
+ {
+ int in_pixel_index = x * channels;
+
+ int c;
+ for (c = 0; c < channels; c++)
+ ring_buffer_entry[in_pixel_index + c] += horizontal_buffer[in_pixel_index + c] * coefficient;
+ }
+ break;
+ }
+ }
+}
+
+static void stbir__buffer_loop_upsample(stbir__info* stbir_info)
+{
+ int y;
+ float scale_ratio = stbir_info->vertical_scale;
+ float out_scanlines_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(1/scale_ratio) * scale_ratio;
+
+ STBIR_ASSERT(stbir__use_height_upsampling(stbir_info));
+
+ for (y = 0; y < stbir_info->output_h; y++)
+ {
+ float in_center_of_out = 0; // Center of the current out scanline in the in scanline space
+ int in_first_scanline = 0, in_last_scanline = 0;
+
+ stbir__calculate_sample_range_upsample(y, out_scanlines_radius, scale_ratio, stbir_info->vertical_shift, &in_first_scanline, &in_last_scanline, &in_center_of_out);
+
+ STBIR_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
+
+ if (stbir_info->ring_buffer_begin_index >= 0)
+ {
+ // Get rid of whatever we don't need anymore.
+ while (in_first_scanline > stbir_info->ring_buffer_first_scanline)
+ {
+ if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
+ {
+ // We just popped the last scanline off the ring buffer.
+ // Reset it to the empty state.
+ stbir_info->ring_buffer_begin_index = -1;
+ stbir_info->ring_buffer_first_scanline = 0;
+ stbir_info->ring_buffer_last_scanline = 0;
+ break;
+ }
+ else
+ {
+ stbir_info->ring_buffer_first_scanline++;
+ stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries;
+ }
+ }
+ }
+
+ // Load in new ones.
+ if (stbir_info->ring_buffer_begin_index < 0)
+ stbir__decode_and_resample_upsample(stbir_info, in_first_scanline);
+
+ while (in_last_scanline > stbir_info->ring_buffer_last_scanline)
+ stbir__decode_and_resample_upsample(stbir_info, stbir_info->ring_buffer_last_scanline + 1);
+
+ // Now all buffers should be ready to write a row of vertical sampling.
+ stbir__resample_vertical_upsample(stbir_info, y);
+
+ STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h);
+ }
+}
+
+static void stbir__empty_ring_buffer(stbir__info* stbir_info, int first_necessary_scanline)
+{
+ int output_stride_bytes = stbir_info->output_stride_bytes;
+ int channels = stbir_info->channels;
+ int alpha_channel = stbir_info->alpha_channel;
+ int type = stbir_info->type;
+ int colorspace = stbir_info->colorspace;
+ int output_w = stbir_info->output_w;
+ void* output_data = stbir_info->output_data;
+ int decode = STBIR__DECODE(type, colorspace);
+
+ float* ring_buffer = stbir_info->ring_buffer;
+ int ring_buffer_length = stbir_info->ring_buffer_length_bytes/sizeof(float);
+
+ if (stbir_info->ring_buffer_begin_index >= 0)
+ {
+ // Get rid of whatever we don't need anymore.
+ while (first_necessary_scanline > stbir_info->ring_buffer_first_scanline)
+ {
+ if (stbir_info->ring_buffer_first_scanline >= 0 && stbir_info->ring_buffer_first_scanline < stbir_info->output_h)
+ {
+ int output_row_start = stbir_info->ring_buffer_first_scanline * output_stride_bytes;
+ float* ring_buffer_entry = stbir__get_ring_buffer_entry(ring_buffer, stbir_info->ring_buffer_begin_index, ring_buffer_length);
+ stbir__encode_scanline(stbir_info, output_w, (char *) output_data + output_row_start, ring_buffer_entry, channels, alpha_channel, decode);
+ STBIR_PROGRESS_REPORT((float)stbir_info->ring_buffer_first_scanline / stbir_info->output_h);
+ }
+
+ if (stbir_info->ring_buffer_first_scanline == stbir_info->ring_buffer_last_scanline)
+ {
+ // We just popped the last scanline off the ring buffer.
+ // Reset it to the empty state.
+ stbir_info->ring_buffer_begin_index = -1;
+ stbir_info->ring_buffer_first_scanline = 0;
+ stbir_info->ring_buffer_last_scanline = 0;
+ break;
+ }
+ else
+ {
+ stbir_info->ring_buffer_first_scanline++;
+ stbir_info->ring_buffer_begin_index = (stbir_info->ring_buffer_begin_index + 1) % stbir_info->ring_buffer_num_entries;
+ }
+ }
+ }
+}
+
+static void stbir__buffer_loop_downsample(stbir__info* stbir_info)
+{
+ int y;
+ float scale_ratio = stbir_info->vertical_scale;
+ int output_h = stbir_info->output_h;
+ float in_pixels_radius = stbir__filter_info_table[stbir_info->vertical_filter].support(scale_ratio) / scale_ratio;
+ int pixel_margin = stbir_info->vertical_filter_pixel_margin;
+ int max_y = stbir_info->input_h + pixel_margin;
+
+ STBIR_ASSERT(!stbir__use_height_upsampling(stbir_info));
+
+ for (y = -pixel_margin; y < max_y; y++)
+ {
+ float out_center_of_in; // Center of the current out scanline in the in scanline space
+ int out_first_scanline, out_last_scanline;
+
+ stbir__calculate_sample_range_downsample(y, in_pixels_radius, scale_ratio, stbir_info->vertical_shift, &out_first_scanline, &out_last_scanline, &out_center_of_in);
+
+ STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
+
+ if (out_last_scanline < 0 || out_first_scanline >= output_h)
+ continue;
+
+ stbir__empty_ring_buffer(stbir_info, out_first_scanline);
+
+ stbir__decode_and_resample_downsample(stbir_info, y);
+
+ // Load in new ones.
+ if (stbir_info->ring_buffer_begin_index < 0)
+ stbir__add_empty_ring_buffer_entry(stbir_info, out_first_scanline);
+
+ while (out_last_scanline > stbir_info->ring_buffer_last_scanline)
+ stbir__add_empty_ring_buffer_entry(stbir_info, stbir_info->ring_buffer_last_scanline + 1);
+
+ // Now the horizontal buffer is ready to write to all ring buffer rows.
+ stbir__resample_vertical_downsample(stbir_info, y);
+ }
+
+ stbir__empty_ring_buffer(stbir_info, stbir_info->output_h);
+}
+
+static void stbir__setup(stbir__info *info, int input_w, int input_h, int output_w, int output_h, int channels)
+{
+ info->input_w = input_w;
+ info->input_h = input_h;
+ info->output_w = output_w;
+ info->output_h = output_h;
+ info->channels = channels;
+}
+
+static void stbir__calculate_transform(stbir__info *info, float s0, float t0, float s1, float t1, float *transform)
+{
+ info->s0 = s0;
+ info->t0 = t0;
+ info->s1 = s1;
+ info->t1 = t1;
+
+ if (transform)
+ {
+ info->horizontal_scale = transform[0];
+ info->vertical_scale = transform[1];
+ info->horizontal_shift = transform[2];
+ info->vertical_shift = transform[3];
+ }
+ else
+ {
+ info->horizontal_scale = ((float)info->output_w / info->input_w) / (s1 - s0);
+ info->vertical_scale = ((float)info->output_h / info->input_h) / (t1 - t0);
+
+ info->horizontal_shift = s0 * info->output_w / (s1 - s0);
+ info->vertical_shift = t0 * info->output_h / (t1 - t0);
+ }
+}
+
+static void stbir__choose_filter(stbir__info *info, stbir_filter h_filter, stbir_filter v_filter)
+{
+ if (h_filter == 0)
+ h_filter = stbir__use_upsampling(info->horizontal_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
+ if (v_filter == 0)
+ v_filter = stbir__use_upsampling(info->vertical_scale) ? STBIR_DEFAULT_FILTER_UPSAMPLE : STBIR_DEFAULT_FILTER_DOWNSAMPLE;
+ info->horizontal_filter = h_filter;
+ info->vertical_filter = v_filter;
+}
+
+static stbir_uint32 stbir__calculate_memory(stbir__info *info)
+{
+ int pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale);
+ int filter_height = stbir__get_filter_pixel_width(info->vertical_filter, info->vertical_scale);
+
+ info->horizontal_num_contributors = stbir__get_contributors(info->horizontal_scale, info->horizontal_filter, info->input_w, info->output_w);
+ info->vertical_num_contributors = stbir__get_contributors(info->vertical_scale , info->vertical_filter , info->input_h, info->output_h);
+
+ // One extra entry because floating point precision problems sometimes cause an extra to be necessary.
+ info->ring_buffer_num_entries = filter_height + 1;
+
+ info->horizontal_contributors_size = info->horizontal_num_contributors * sizeof(stbir__contributors);
+ info->horizontal_coefficients_size = stbir__get_total_horizontal_coefficients(info) * sizeof(float);
+ info->vertical_contributors_size = info->vertical_num_contributors * sizeof(stbir__contributors);
+ info->vertical_coefficients_size = stbir__get_total_vertical_coefficients(info) * sizeof(float);
+ info->decode_buffer_size = (info->input_w + pixel_margin * 2) * info->channels * sizeof(float);
+ info->horizontal_buffer_size = info->output_w * info->channels * sizeof(float);
+ info->ring_buffer_size = info->output_w * info->channels * info->ring_buffer_num_entries * sizeof(float);
+ info->encode_buffer_size = info->output_w * info->channels * sizeof(float);
+
+ STBIR_ASSERT(info->horizontal_filter != 0);
+ STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late
+ STBIR_ASSERT(info->vertical_filter != 0);
+ STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table)); // this now happens too late
+
+ if (stbir__use_height_upsampling(info))
+ // The horizontal buffer is for when we're downsampling the height and we
+ // can't output the result of sampling the decode buffer directly into the
+ // ring buffers.
+ info->horizontal_buffer_size = 0;
+ else
+ // The encode buffer is to retain precision in the height upsampling method
+ // and isn't used when height downsampling.
+ info->encode_buffer_size = 0;
+
+ return info->horizontal_contributors_size + info->horizontal_coefficients_size
+ + info->vertical_contributors_size + info->vertical_coefficients_size
+ + info->decode_buffer_size + info->horizontal_buffer_size
+ + info->ring_buffer_size + info->encode_buffer_size;
+}
+
+static int stbir__resize_allocated(stbir__info *info,
+ const void* input_data, int input_stride_in_bytes,
+ void* output_data, int output_stride_in_bytes,
+ int alpha_channel, stbir_uint32 flags, stbir_datatype type,
+ stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace,
+ void* tempmem, size_t tempmem_size_in_bytes)
+{
+ size_t memory_required = stbir__calculate_memory(info);
+
+ int width_stride_input = input_stride_in_bytes ? input_stride_in_bytes : info->channels * info->input_w * stbir__type_size[type];
+ int width_stride_output = output_stride_in_bytes ? output_stride_in_bytes : info->channels * info->output_w * stbir__type_size[type];
+
+#ifdef STBIR_DEBUG_OVERWRITE_TEST
+#define OVERWRITE_ARRAY_SIZE 8
+ unsigned char overwrite_output_before_pre[OVERWRITE_ARRAY_SIZE];
+ unsigned char overwrite_tempmem_before_pre[OVERWRITE_ARRAY_SIZE];
+ unsigned char overwrite_output_after_pre[OVERWRITE_ARRAY_SIZE];
+ unsigned char overwrite_tempmem_after_pre[OVERWRITE_ARRAY_SIZE];
+
+ size_t begin_forbidden = width_stride_output * (info->output_h - 1) + info->output_w * info->channels * stbir__type_size[type];
+ memcpy(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE);
+ memcpy(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE);
+ memcpy(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE);
+ memcpy(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE);
+#endif
+
+ STBIR_ASSERT(info->channels >= 0);
+ STBIR_ASSERT(info->channels <= STBIR_MAX_CHANNELS);
+
+ if (info->channels < 0 || info->channels > STBIR_MAX_CHANNELS)
+ return 0;
+
+ STBIR_ASSERT(info->horizontal_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
+ STBIR_ASSERT(info->vertical_filter < STBIR__ARRAY_SIZE(stbir__filter_info_table));
+
+ if (info->horizontal_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table))
+ return 0;
+ if (info->vertical_filter >= STBIR__ARRAY_SIZE(stbir__filter_info_table))
+ return 0;
+
+ if (alpha_channel < 0)
+ flags |= STBIR_FLAG_ALPHA_USES_COLORSPACE | STBIR_FLAG_ALPHA_PREMULTIPLIED;
+
+ if (!(flags&STBIR_FLAG_ALPHA_USES_COLORSPACE) || !(flags&STBIR_FLAG_ALPHA_PREMULTIPLIED)) {
+ STBIR_ASSERT(alpha_channel >= 0 && alpha_channel < info->channels);
+ }
+
+ if (alpha_channel >= info->channels)
+ return 0;
+
+ STBIR_ASSERT(tempmem);
+
+ if (!tempmem)
+ return 0;
+
+ STBIR_ASSERT(tempmem_size_in_bytes >= memory_required);
+
+ if (tempmem_size_in_bytes < memory_required)
+ return 0;
+
+ memset(tempmem, 0, tempmem_size_in_bytes);
+
+ info->input_data = input_data;
+ info->input_stride_bytes = width_stride_input;
+
+ info->output_data = output_data;
+ info->output_stride_bytes = width_stride_output;
+
+ info->alpha_channel = alpha_channel;
+ info->flags = flags;
+ info->type = type;
+ info->edge_horizontal = edge_horizontal;
+ info->edge_vertical = edge_vertical;
+ info->colorspace = colorspace;
+
+ info->horizontal_coefficient_width = stbir__get_coefficient_width (info->horizontal_filter, info->horizontal_scale);
+ info->vertical_coefficient_width = stbir__get_coefficient_width (info->vertical_filter , info->vertical_scale );
+ info->horizontal_filter_pixel_width = stbir__get_filter_pixel_width (info->horizontal_filter, info->horizontal_scale);
+ info->vertical_filter_pixel_width = stbir__get_filter_pixel_width (info->vertical_filter , info->vertical_scale );
+ info->horizontal_filter_pixel_margin = stbir__get_filter_pixel_margin(info->horizontal_filter, info->horizontal_scale);
+ info->vertical_filter_pixel_margin = stbir__get_filter_pixel_margin(info->vertical_filter , info->vertical_scale );
+
+ info->ring_buffer_length_bytes = info->output_w * info->channels * sizeof(float);
+ info->decode_buffer_pixels = info->input_w + info->horizontal_filter_pixel_margin * 2;
+
+#define STBIR__NEXT_MEMPTR(current, newtype) (newtype*)(((unsigned char*)current) + current##_size)
+
+ info->horizontal_contributors = (stbir__contributors *) tempmem;
+ info->horizontal_coefficients = STBIR__NEXT_MEMPTR(info->horizontal_contributors, float);
+ info->vertical_contributors = STBIR__NEXT_MEMPTR(info->horizontal_coefficients, stbir__contributors);
+ info->vertical_coefficients = STBIR__NEXT_MEMPTR(info->vertical_contributors, float);
+ info->decode_buffer = STBIR__NEXT_MEMPTR(info->vertical_coefficients, float);
+
+ if (stbir__use_height_upsampling(info))
+ {
+ info->horizontal_buffer = NULL;
+ info->ring_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
+ info->encode_buffer = STBIR__NEXT_MEMPTR(info->ring_buffer, float);
+
+ STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->encode_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
+ }
+ else
+ {
+ info->horizontal_buffer = STBIR__NEXT_MEMPTR(info->decode_buffer, float);
+ info->ring_buffer = STBIR__NEXT_MEMPTR(info->horizontal_buffer, float);
+ info->encode_buffer = NULL;
+
+ STBIR_ASSERT((size_t)STBIR__NEXT_MEMPTR(info->ring_buffer, unsigned char) == (size_t)tempmem + tempmem_size_in_bytes);
+ }
+
+#undef STBIR__NEXT_MEMPTR
+
+ // This signals that the ring buffer is empty
+ info->ring_buffer_begin_index = -1;
+
+ stbir__calculate_filters(info->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w);
+ stbir__calculate_filters(info->vertical_contributors, info->vertical_coefficients, info->vertical_filter, info->vertical_scale, info->vertical_shift, info->input_h, info->output_h);
+
+ STBIR_PROGRESS_REPORT(0);
+
+ if (stbir__use_height_upsampling(info))
+ stbir__buffer_loop_upsample(info);
+ else
+ stbir__buffer_loop_downsample(info);
+
+ STBIR_PROGRESS_REPORT(1);
+
+#ifdef STBIR_DEBUG_OVERWRITE_TEST
+ STBIR_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
+ STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0);
+ STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
+ STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0);
+#endif
+
+ return 1;
+}
+
+
+static int stbir__resize_arbitrary(
+ void *alloc_context,
+ const void* input_data, int input_w, int input_h, int input_stride_in_bytes,
+ void* output_data, int output_w, int output_h, int output_stride_in_bytes,
+ float s0, float t0, float s1, float t1, float *transform,
+ int channels, int alpha_channel, stbir_uint32 flags, stbir_datatype type,
+ stbir_filter h_filter, stbir_filter v_filter,
+ stbir_edge edge_horizontal, stbir_edge edge_vertical, stbir_colorspace colorspace)
+{
+ stbir__info info;
+ int result;
+ size_t memory_required;
+ void* extra_memory;
+
+ stbir__setup(&info, input_w, input_h, output_w, output_h, channels);
+ stbir__calculate_transform(&info, s0,t0,s1,t1,transform);
+ stbir__choose_filter(&info, h_filter, v_filter);
+ memory_required = stbir__calculate_memory(&info);
+ extra_memory = STBIR_MALLOC(memory_required, alloc_context);
+
+ if (!extra_memory)
+ return 0;
+
+ result = stbir__resize_allocated(&info, input_data, input_stride_in_bytes,
+ output_data, output_stride_in_bytes,
+ alpha_channel, flags, type,
+ edge_horizontal, edge_vertical,
+ colorspace, extra_memory, memory_required);
+
+ STBIR_FREE(extra_memory, alloc_context);
+
+ return result;
+}
+
+STBIRDEF int stbir_resize_uint8( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels)
+{
+ return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
+ STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
+}
+
+STBIRDEF int stbir_resize_float( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ float *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels)
+{
+ return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,NULL,num_channels,-1,0, STBIR_TYPE_FLOAT, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
+ STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_LINEAR);
+}
+
+STBIRDEF int stbir_resize_uint8_srgb(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags)
+{
+ return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
+ STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP, STBIR_COLORSPACE_SRGB);
+}
+
+STBIRDEF int stbir_resize_uint8_srgb_edgemode(const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_wrap_mode)
+{
+ return stbir__resize_arbitrary(NULL, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, STBIR_FILTER_DEFAULT, STBIR_FILTER_DEFAULT,
+ edge_wrap_mode, edge_wrap_mode, STBIR_COLORSPACE_SRGB);
+}
+
+STBIRDEF int stbir_resize_uint8_generic( const unsigned char *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
+ void *alloc_context)
+{
+ return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT8, filter, filter,
+ edge_wrap_mode, edge_wrap_mode, space);
+}
+
+STBIRDEF int stbir_resize_uint16_generic(const stbir_uint16 *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ stbir_uint16 *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
+ void *alloc_context)
+{
+ return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_UINT16, filter, filter,
+ edge_wrap_mode, edge_wrap_mode, space);
+}
+
+
+STBIRDEF int stbir_resize_float_generic( const float *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ float *output_pixels , int output_w, int output_h, int output_stride_in_bytes,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_wrap_mode, stbir_filter filter, stbir_colorspace space,
+ void *alloc_context)
+{
+ return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,NULL,num_channels,alpha_channel,flags, STBIR_TYPE_FLOAT, filter, filter,
+ edge_wrap_mode, edge_wrap_mode, space);
+}
+
+
+STBIRDEF int stbir_resize( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ stbir_datatype datatype,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
+ stbir_filter filter_horizontal, stbir_filter filter_vertical,
+ stbir_colorspace space, void *alloc_context)
+{
+ return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
+ edge_mode_horizontal, edge_mode_vertical, space);
+}
+
+
+STBIRDEF int stbir_resize_subpixel(const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ stbir_datatype datatype,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
+ stbir_filter filter_horizontal, stbir_filter filter_vertical,
+ stbir_colorspace space, void *alloc_context,
+ float x_scale, float y_scale,
+ float x_offset, float y_offset)
+{
+ float transform[4];
+ transform[0] = x_scale;
+ transform[1] = y_scale;
+ transform[2] = x_offset;
+ transform[3] = y_offset;
+ return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ 0,0,1,1,transform,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
+ edge_mode_horizontal, edge_mode_vertical, space);
+}
+
+STBIRDEF int stbir_resize_region( const void *input_pixels , int input_w , int input_h , int input_stride_in_bytes,
+ void *output_pixels, int output_w, int output_h, int output_stride_in_bytes,
+ stbir_datatype datatype,
+ int num_channels, int alpha_channel, int flags,
+ stbir_edge edge_mode_horizontal, stbir_edge edge_mode_vertical,
+ stbir_filter filter_horizontal, stbir_filter filter_vertical,
+ stbir_colorspace space, void *alloc_context,
+ float s0, float t0, float s1, float t1)
+{
+ return stbir__resize_arbitrary(alloc_context, input_pixels, input_w, input_h, input_stride_in_bytes,
+ output_pixels, output_w, output_h, output_stride_in_bytes,
+ s0,t0,s1,t1,NULL,num_channels,alpha_channel,flags, datatype, filter_horizontal, filter_vertical,
+ edge_mode_horizontal, edge_mode_vertical, space);
+}
+
+#endif // STB_IMAGE_RESIZE_IMPLEMENTATION
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+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 OR COPYRIGHT HOLDERS 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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+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.
+------------------------------------------------------------------------------
+*/
diff --git a/third-party/stb/deprecated/stretch_test.c b/third-party/stb/deprecated/stretch_test.c
new file mode 100644
index 0000000..772237c
--- /dev/null
+++ b/third-party/stb/deprecated/stretch_test.c
@@ -0,0 +1,29 @@
+// check that stb_truetype compiles with no stb_rect_pack.h
+#define STB_TRUETYPE_IMPLEMENTATION
+#include "stb_truetype.h"
+
+#define STB_DS_IMPLEMENTATION
+#include "stb_ds.h"
+#include
+
+int main(int arg, char **argv)
+{
+ int i;
+ int *arr = NULL;
+
+ for (i=0; i < 1000000; ++i)
+ arrput(arr, i);
+
+ assert(arrlen(arr) == 1000000);
+ for (i=0; i < 1000000; ++i)
+ assert(arr[i] == i);
+
+ arrfree(arr);
+ arr = NULL;
+
+ for (i=0; i < 1000; ++i)
+ arrput(arr, 1000);
+ assert(arrlen(arr) == 1000000);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/third-party/stb/deprecated/stretchy_buffer.h b/third-party/stb/deprecated/stretchy_buffer.h
new file mode 100644
index 0000000..c0880ff
--- /dev/null
+++ b/third-party/stb/deprecated/stretchy_buffer.h
@@ -0,0 +1,263 @@
+// stretchy_buffer.h - v1.04 - public domain - nothings.org/stb
+// a vector<>-like dynamic array for C
+//
+// version history:
+// 1.04 - fix warning
+// 1.03 - compile as C++ maybe
+// 1.02 - tweaks to syntax for no good reason
+// 1.01 - added a "common uses" documentation section
+// 1.0 - fixed bug in the version I posted prematurely
+// 0.9 - rewrite to try to avoid strict-aliasing optimization
+// issues, but won't compile as C++
+//
+// Will probably not work correctly with strict-aliasing optimizations.
+//
+// The idea:
+//
+// This implements an approximation to C++ vector<> for C, in that it
+// provides a generic definition for dynamic arrays which you can
+// still access in a typesafe way using arr[i] or *(arr+i). However,
+// it is simply a convenience wrapper around the common idiom of
+// of keeping a set of variables (in a struct or globals) which store
+// - pointer to array
+// - the length of the "in-use" part of the array
+// - the current size of the allocated array
+//
+// I find it to be the single most useful non-built-in-structure when
+// programming in C (hash tables a close second), but to be clear
+// it lacks many of the capabilities of C++ vector<>: there is no
+// range checking, the object address isn't stable (see next section
+// for details), the set of methods available is small (although
+// the file stb.h has another implementation of stretchy buffers
+// called 'stb_arr' which provides more methods, e.g. for insertion
+// and deletion).
+//
+// How to use:
+//
+// Unlike other stb header file libraries, there is no need to
+// define an _IMPLEMENTATION symbol. Every #include creates as
+// much implementation is needed.
+//
+// stretchy_buffer.h does not define any types, so you do not
+// need to #include it to before defining data types that are
+// stretchy buffers, only in files that *manipulate* stretchy
+// buffers.
+//
+// If you want a stretchy buffer aka dynamic array containing
+// objects of TYPE, declare such an array as:
+//
+// TYPE *myarray = NULL;
+//
+// (There is no typesafe way to distinguish between stretchy
+// buffers and regular arrays/pointers; this is necessary to
+// make ordinary array indexing work on these objects.)
+//
+// Unlike C++ vector<>, the stretchy_buffer has the same
+// semantics as an object that you manually malloc and realloc.
+// The pointer may relocate every time you add a new object
+// to it, so you:
+//
+// 1. can't take long-term pointers to elements of the array
+// 2. have to return the pointer from functions which might expand it
+// (either as a return value or by storing it to a ptr-to-ptr)
+//
+// Now you can do the following things with this array:
+//
+// sb_free(TYPE *a) free the array
+// sb_count(TYPE *a) the number of elements in the array
+// sb_push(TYPE *a, TYPE v) adds v on the end of the array, a la push_back
+// sb_add(TYPE *a, int n) adds n uninitialized elements at end of array & returns pointer to first added
+// sb_last(TYPE *a) returns an lvalue of the last item in the array
+// a[n] access the nth (counting from 0) element of the array
+//
+// #define STRETCHY_BUFFER_NO_SHORT_NAMES to only export
+// names of the form 'stb_sb_' if you have a name that would
+// otherwise collide.
+//
+// Note that these are all macros and many of them evaluate
+// their arguments more than once, so the arguments should
+// be side-effect-free.
+//
+// Note that 'TYPE *a' in sb_push and sb_add must be lvalues
+// so that the library can overwrite the existing pointer if
+// the object has to be reallocated.
+//
+// In an out-of-memory condition, the code will try to
+// set up a null-pointer or otherwise-invalid-pointer
+// exception to happen later. It's possible optimizing
+// compilers could detect this write-to-null statically
+// and optimize away some of the code, but it should only
+// be along the failure path. Nevertheless, for more security
+// in the face of such compilers, #define STRETCHY_BUFFER_OUT_OF_MEMORY
+// to a statement such as assert(0) or exit(1) or something
+// to force a failure when out-of-memory occurs.
+//
+// Common use:
+//
+// The main application for this is when building a list of
+// things with an unknown quantity, either due to loading from
+// a file or through a process which produces an unpredictable
+// number.
+//
+// My most common idiom is something like:
+//
+// SomeStruct *arr = NULL;
+// while (something)
+// {
+// SomeStruct new_one;
+// new_one.whatever = whatever;
+// new_one.whatup = whatup;
+// new_one.foobar = barfoo;
+// sb_push(arr, new_one);
+// }
+//
+// and various closely-related factorings of that. For example,
+// you might have several functions to create/init new SomeStructs,
+// and if you use the above idiom, you might prefer to make them
+// return structs rather than take non-const-pointers-to-structs,
+// so you can do things like:
+//
+// SomeStruct *arr = NULL;
+// while (something)
+// {
+// if (case_A) {
+// sb_push(arr, some_func1());
+// } else if (case_B) {
+// sb_push(arr, some_func2());
+// } else {
+// sb_push(arr, some_func3());
+// }
+// }
+//
+// Note that the above relies on the fact that sb_push doesn't
+// evaluate its second argument more than once. The macros do
+// evaluate the *array* argument multiple times, and numeric
+// arguments may be evaluated multiple times, but you can rely
+// on the second argument of sb_push being evaluated only once.
+//
+// Of course, you don't have to store bare objects in the array;
+// if you need the objects to have stable pointers, store an array
+// of pointers instead:
+//
+// SomeStruct **arr = NULL;
+// while (something)
+// {
+// SomeStruct *new_one = malloc(sizeof(*new_one));
+// new_one->whatever = whatever;
+// new_one->whatup = whatup;
+// new_one->foobar = barfoo;
+// sb_push(arr, new_one);
+// }
+//
+// How it works:
+//
+// A long-standing tradition in things like malloc implementations
+// is to store extra data before the beginning of the block returned
+// to the user. The stretchy buffer implementation here uses the
+// same trick; the current-count and current-allocation-size are
+// stored before the beginning of the array returned to the user.
+// (This means you can't directly free() the pointer, because the
+// allocated pointer is different from the type-safe pointer provided
+// to the user.)
+//
+// The details are trivial and implementation is straightforward;
+// the main trick is in realizing in the first place that it's
+// possible to do this in a generic, type-safe way in C.
+//
+// Contributors:
+//
+// Timothy Wright (github:ZenToad)
+//
+// LICENSE
+//
+// See end of file for license information.
+
+#ifndef STB_STRETCHY_BUFFER_H_INCLUDED
+#define STB_STRETCHY_BUFFER_H_INCLUDED
+
+#ifndef NO_STRETCHY_BUFFER_SHORT_NAMES
+#define sb_free stb_sb_free
+#define sb_push stb_sb_push
+#define sb_count stb_sb_count
+#define sb_add stb_sb_add
+#define sb_last stb_sb_last
+#endif
+
+#define stb_sb_free(a) ((a) ? free(stb__sbraw(a)),0 : 0)
+#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v))
+#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0)
+#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)])
+#define stb_sb_last(a) ((a)[stb__sbn(a)-1])
+
+#define stb__sbraw(a) ((int *) (void *) (a) - 2)
+#define stb__sbm(a) stb__sbraw(a)[0]
+#define stb__sbn(a) stb__sbraw(a)[1]
+
+#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a))
+#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0)
+#define stb__sbgrow(a,n) (*((void **)&(a)) = stb__sbgrowf((a), (n), sizeof(*(a))))
+
+#include
+
+static void * stb__sbgrowf(void *arr, int increment, int itemsize)
+{
+ int dbl_cur = arr ? 2*stb__sbm(arr) : 0;
+ int min_needed = stb_sb_count(arr) + increment;
+ int m = dbl_cur > min_needed ? dbl_cur : min_needed;
+ int *p = (int *) realloc(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int)*2);
+ if (p) {
+ if (!arr)
+ p[1] = 0;
+ p[0] = m;
+ return p+2;
+ } else {
+ #ifdef STRETCHY_BUFFER_OUT_OF_MEMORY
+ STRETCHY_BUFFER_OUT_OF_MEMORY ;
+ #endif
+ return (void *) (2*sizeof(int)); // try to force a NULL pointer exception later
+ }
+}
+#endif // STB_STRETCHY_BUFFER_H_INCLUDED
+
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+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 OR COPYRIGHT HOLDERS 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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+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.
+------------------------------------------------------------------------------
+*/
diff --git a/third-party/stb/deprecated/stretchy_buffer.txt b/third-party/stb/deprecated/stretchy_buffer.txt
new file mode 100644
index 0000000..dcd747e
--- /dev/null
+++ b/third-party/stb/deprecated/stretchy_buffer.txt
@@ -0,0 +1,28 @@
+// stretchy buffer // init: NULL // free: sbfree() // push_back: sbpush() // size: sbcount() //
+#define sbfree(a) ((a) ? free(stb__sbraw(a)),0 : 0)
+#define sbpush(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v))
+#define sbcount(a) ((a) ? stb__sbn(a) : 0)
+#define sbadd(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)])
+#define sblast(a) ((a)[stb__sbn(a)-1])
+
+#include
+#define stb__sbraw(a) ((int *) (a) - 2)
+#define stb__sbm(a) stb__sbraw(a)[0]
+#define stb__sbn(a) stb__sbraw(a)[1]
+
+#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+n >= stb__sbm(a))
+#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0)
+#define stb__sbgrow(a,n) stb__sbgrowf((void **) &(a), (n), sizeof(*(a)))
+
+static void stb__sbgrowf(void **arr, int increment, int itemsize)
+{
+ int m = *arr ? 2*stb__sbm(*arr)+increment : increment+1;
+ void *p = realloc(*arr ? stb__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2);
+ assert(p);
+ if (p) {
+ if (!*arr) ((int *) p)[1] = 0;
+ *arr = (void *) ((int *) p + 2);
+ stb__sbm(*arr) = m;
+ }
+}
+
diff --git a/third-party/stb/docs/other_libs.md b/third-party/stb/docs/other_libs.md
new file mode 100644
index 0000000..62f379c
--- /dev/null
+++ b/third-party/stb/docs/other_libs.md
@@ -0,0 +1 @@
+Moved to https://github.com/nothings/single_file_libs
\ No newline at end of file
diff --git a/third-party/stb/docs/stb_howto.txt b/third-party/stb/docs/stb_howto.txt
new file mode 100644
index 0000000..a969b54
--- /dev/null
+++ b/third-party/stb/docs/stb_howto.txt
@@ -0,0 +1,185 @@
+Lessons learned about how to make a header-file library
+V1.0
+September 2013 Sean Barrett
+
+Things to do in an stb-style header-file library,
+and rationales:
+
+
+1. #define LIBRARYNAME_IMPLEMENTATION
+
+Use a symbol like the above to control creating
+the implementation. (I used a far-less-clear name
+in my first header-file library; it became
+clear that was a mistake once I had multiple
+libraries.)
+
+Include a "header-file" section with header-file
+guards and declarations for all the functions,
+but only guard the implementation with LIBRARYNAME_IMPLEMENTATION,
+not the header-file guard. That way, if client's
+header file X includes your header file for
+declarations, they can still include header file X
+in the source file that creates the implementation;
+if you guard the implementation too, then the first
+include (before the #define) creates the declarations,
+and the second one (after the #define) does nothing.
+
+
+2. AVOID DEPENDENCIES
+
+Don't rely on anything other than the C standard libraries.
+
+(If you're creating a library specifically to leverage/wrap
+some other library, then obviously you can rely on that
+library. But if that library is public domain, you might
+be better off directly embedding the source, to reduce
+dependencies for your clients. But of course now you have
+to update whenever that library updates.)
+
+If you use stdlib, consider wrapping all stdlib calls in
+macros, and then conditionally define those macros to the
+stdlib function, allowing the user to replace them.
+
+For functions with side effects, like memory allocations,
+consider letting the user pass in a context and pass
+that in to the macros. (The stdlib versions will ignore
+the parameter.) Otherwise, users may have to use global
+or thread-local variables to achieve the same effect.
+
+
+3. AVOID MALLOC
+
+You can't always do this, but when you can, embedded developers
+will appreciate it. I almost never bother avoiding, as it's
+too much work (and in some cases is pretty infeasible;
+see http://nothings.org/gamedev/font_rendering_malloc.txt ).
+But it's definitely something one of the things I've gotten
+the most pushback on from potential users.
+
+
+4. ALLOW STATIC IMPLEMENTATION
+
+Have a #define which makes function declarations and
+function definitions static. This makes the implementation
+private to the source file that creates it. This allows
+people to use your library multiple times in their project
+without collision. (This is only necessary if your library
+has configuration macros or global state, or if your
+library has multiple versions that are not backwards
+compatible. I've run into both of those cases.)
+
+
+5. MAKE ACCESSIBLE FROM C
+
+Making your code accessible from C instead of C++ (i.e.
+either coding in C, or using extern "C") makes it more
+straightforward to be used in C and in other languages,
+which often only have support for C bindings, not C++.
+(One of the earliest results I found in googling for
+stb_image was a Haskell wrapper.) Otherwise, people
+have to wrap it in another set of function calls, and
+the whole point here is to make it convenient for people
+to use, isn't it? (See below.)
+
+I prefer to code entirely in C, so the source file that
+instantiates the implementation can be C itself, for
+those crazy people out there who are programming in C.
+But it's probably not a big hardship for a C programmer
+to create a single C++ source file to instantiate your
+library.
+
+
+6. NAMESPACE PRIVATE FUNCTIONS
+
+Try to avoid having names in your source code that
+will cause conflicts with identical names in client
+code. You can do this either by namespacing in C++,
+or prefixing with your library name in C.
+
+In C, generally, I use the same prefix for API
+functions and private symbols, such as "stbtt_"
+for stb_truetype; but private functions (and
+static globals) use a second underscore as
+in "stbtt__" to further minimize the chance of
+additional collisions in the unlikely but not
+impossible event that users write wrapper
+functions that have names of the form "stbtt_".
+(Consider the user that has used "stbtt_foo"
+*successfully*, and then upgrades to a new
+version of your library which has a new private
+function named either "stbtt_foo" or "stbtt__foo".)
+
+Note that the double-underscore is reserved for
+use by the compiler, but (1) there is nothing
+reserved for "middleware", i.e. libraries
+desiring to avoid conflicts with user symbols
+have no other good options, and (2) in practice
+no compilers use double-underscore in the middle
+rather than the beginning/end. (Unfortunately,
+there is at least one videogame-console compiler that
+will warn about double-underscores by default.)
+
+
+7. EASY-TO-COMPLY LICENSE
+
+I make my libraries public domain. You don't have to.
+But my goal in releasing stb-style libraries is to
+reduce friction for potential users as much as
+possible. That means:
+
+ a. easy to build (what this file is mostly about)
+ b. easy to invoke (which requires good API design)
+ c. easy to deploy (which is about licensing)
+
+I choose to place all my libraries in the public
+domain, abjuring copyright, rather than license
+the libraries. This has some benefits and some
+drawbacks.
+
+Any license which is "viral" to modifications
+causes worries for lawyers, even if their programmers
+aren't modifying it.
+
+Any license which requires crediting in documentation
+adds friction which can add up. Valve used to have
+a page with a list of all of these on their web site,
+and it was insane, and obviously nobody ever looked
+at it so why would you care whether your credit appeared
+there?
+
+Permissive licenses like zlib and BSD license are
+perfectly reasonable, but they are very wordy and
+have only two benefits over public domain: legally-mandated
+attribution and liability-control. I do not believe these
+are worth the excessive verbosity and user-unfriendliness
+these licenses induce, especially in the single-file
+case where those licenses tend to be at the top of
+the file, the first thing you see. (To the specific
+points, I have had no trouble receiving attribution
+for my libraries; liability in the face of no explicit
+disclaimer of liability is an open question.)
+
+However, public domain has frictions of its own, because
+public domain declarations aren't necessary recognized
+in the USA and some other locations. For that reason,
+I recommend a declaration along these lines:
+
+// This software is dual-licensed to the public domain and under the following
+// license: you are granted a perpetual, irrevocable license to copy, modify,
+// publish, and distribute this file as you see fit.
+
+I typically place this declaration at the end of the initial
+comment block of the file and just say 'public domain'
+at the top.
+
+I have had people say they couldn't use one of my
+libraries because it was only "public domain" and didn't
+have the additional fallback clause, who asked if
+I could dual-license it under a traditional license.
+
+My answer: they can create a derivative work by
+modifying one character, and then license that however
+they like. (Indeed, *adding* the zlib or BSD license
+would be such a modification!) Unfortunately, their
+lawyers reportedly didn't like that answer. :(
diff --git a/third-party/stb/docs/stb_voxel_render_interview.md b/third-party/stb/docs/stb_voxel_render_interview.md
new file mode 100644
index 0000000..7071466
--- /dev/null
+++ b/third-party/stb/docs/stb_voxel_render_interview.md
@@ -0,0 +1,173 @@
+# An interview with STB about stb_voxel_render.h
+
+**Q:**
+I suppose you really like Minecraft?
+
+**A:**
+Not really. I mean, I do own it and play it some, and
+I do watch YouTube videos of other people playing it
+once in a while, but I'm not saying it's that great.
+
+But I do love voxels. I've been playing with voxel rendering
+since the mid-late 90's when we were still doing software
+rendering and thinking maybe polygons weren't the answer.
+Once GPUs came along that kind of died off, at least until
+Minecraft brought it back to attention.
+
+**Q:**
+Do you expect people will make a lot of Minecraft clones
+with this?
+
+**A:**
+I hope not!
+
+For one thing, it's a terrible idea for the
+developer. Remember before Minecraft was on the Xbox 360,
+there were a ton of "indie" clones (some maybe making
+decent money even), but then the real Minecraft came out
+and just crushed them (as far as I know). It's just not
+something you really want to compete with.
+
+The reason I made this library is because I'd like
+to see more games with Minecraft's *art style*, not
+necessary its *gameplay*.
+
+I can understand the urge to clone the gameplay. When
+you have a world made of voxels/blocks, there are a
+few things that become incredibly easy to do that would
+otherwise be very hard (at least for an indie) to do in 3D.
+One thing is that procedural generation becomes much easier.
+Another is that destructible environments are easy. Another
+is that you have a world where your average user can build
+stuff that they find satisfactory.
+
+Minecraft is at a sort of local maximum, a sweet spot, where
+it leverages all of those easy-to-dos. And so I'm sure it's
+hard to look at the space of 'games using voxels' and move
+away from that local maximum, to give up some of that.
+But I think that's what people should do.
+
+**Q:**
+So what else can people do with stb_voxel_render?
+
+**A:**
+All of those benefits I mentioned above are still valid even
+if you stay away from the sweet spot. You can make a 3D roguelike
+without player-creation/destruction that uses procedural generation.
+You could make a shooter with pre-designed maps but destructible
+environments.
+
+And I'm sure there are other possible benefits to using voxels/blocks.
+Hopefully this will make it easier for people to explore the space.
+
+The library has a pretty wide range of features to allow
+people to come up with some distinctive looks. For example,
+the art style of Continue?9876543210 was one of the inspirations
+for trying to make the multitexturing capabilities flexible.
+I'm terrible at art, so this isn't really something I can
+come up with myself, but I tried to put in flexible
+technology that could be used multiple ways.
+
+One thing I did intentionally was try to make it possible to
+make nicer looking ground terrain, using the half-height
+slopes and "weird slopes". There are Minecraft mods with
+drivable cars and they just go up these blocky slopes and,
+like, what? So I wanted you to be able to make smoother
+terrain, either just for the look, or for vehicles etc.
+Also, you can spatially cross-fade between two ground textures for
+that classic bad dirt/grass transition that has shipped
+in plenty of professional games. Of course, you could
+just use a separate non-voxel ground renderer for all of
+this. But this way, you can seamlessly integrate everything
+else with it. E.g. in your authoring tool (or procedural
+generation) you can make smooth ground and then cut a
+sharp-edged hole in it for a building's basement or whatever.
+
+Another thing you can do is work at a very different scale.
+In Minecraft, a person is just under 2 blocks tall. In
+Ace of Spades, a person is just under 3 blocks tall. Why
+not 4 or 6? Well, partly because you just need a lot more
+voxels; if a meter is 2 voxels in Mineraft and 4 voxels in
+your game, and you draw the same number of voxels due to
+hardware limits, then your game has half the view distance
+of Minecraft. Since stb_voxel_render is designed to keep
+the meshes small and render efficiently, you can push the
+view distance out further than Minecraft--or use a similar
+view distance and a higher voxel resolution. You could also
+stop making infinite worlds and work at entirely different
+scales; where Minecraft is 1 voxel per meter, you could
+have 20 voxels per meter and make a small arena that's
+50 meters wide and 5 meters tall.
+
+Back when the voxel game Voxatron was announced, the weekend
+after the trailer came out I wrote my own little GPU-accelerated
+version of the engine and thought that was pretty cool. I've
+been tempted many times to extract that and release it
+as a library, but
+I don't want to steal Voxatron's thunder so I've avoided
+it. You could use this engine to do the same kind of thing,
+although it won't be as efficient as an engine dedicated to
+that style of thing would be.
+
+**Q:**
+What one thing would you really like to see somebody do?
+
+**A:**
+Before Unity, 3D has seemed deeply problematic in the indie
+space. Software like GameMaker has tried to support 3D but
+it seems like little of note has been done with it.
+
+Minecraft has shown that people can build worlds with the
+Minecraft toolset far more easily than we've ever seen from those
+other tools. Obviously people have done great things with
+Unity, but those people are much closer to professional
+developers; typically they still need real 3D modelling
+and all of that stuff.
+
+So what I'd really like to see is someone build some kind
+of voxel-game-construction-set. Start with stb_voxel_render,
+maybe expose all the flexibility of stb_voxel_render (so
+people can do different things). Thrown in lua or something
+else for scripting, make some kind of editor that feels
+at least as good as Minecraft and Infinifactory, and see
+where that gets you.
+
+**Q:**
+Why'd you make this library?
+
+**A:**
+Mainly as a way of releasing this technology I've been working
+on since 2011 and seemed unlikely to ever ship myself. In 2011
+I was playing the voxel shooter Ace of Spades. One of the maps
+that we played on was a partial port of Broville (which is the
+first Minecraft map in stb_voxel_render release trailer). I'd
+made a bunch of procedural level generators for the game, and
+I started trying to make a city generator inspired by Broville.
+
+But I realized it would be a lot of work, and of very little
+value (most of my maps didn't get much play because people
+preferred to play on maps where they could charge straight
+at the enemies and shoot them as fast as possible). So I
+wrote my own voxel engine and started working on a procedural
+city game. But I got bogged down after I finally got the road
+generator working and never got anywhere with building
+generation or gameplay.
+
+stb_voxel_render is actually a complete rewrite from scratch,
+but it's based a lot on what I learned from that previous work.
+
+**Q:**
+About the release video... how long did that take to edit?
+
+**A:**
+About seven or eight hours. I had the first version done in
+maybe six or seven hours, but then I realized I'd left out
+one clip, and when I went back to add it I also gussied up
+a couple other moments in the video. But there was something
+basically identical to it that was done in around six.
+
+**Q:**
+Ok, that's it. Thanks, me.
+
+**A:**
+Thanks *me!*
diff --git a/third-party/stb/docs/why_public_domain.md b/third-party/stb/docs/why_public_domain.md
new file mode 100644
index 0000000..fd3f887
--- /dev/null
+++ b/third-party/stb/docs/why_public_domain.md
@@ -0,0 +1,117 @@
+My collected rationales for placing these libraries
+in the public domain:
+
+1. Public domain vs. viral licenses
+
+ Why is this library public domain?
+ Because more people will use it. Because it's not viral, people are
+ not obligated to give back, so you could argue that it hurts the
+ development of it, and then because it doesn't develop as well it's
+ not as good, and then because it's not as good, in the long run
+ maybe fewer people will use it. I have total respect for that
+ opinion, but I just don't believe it myself for most software.
+
+2. Public domain vs. attribution-required licenses
+
+ The primary difference between public domain and, say, a Creative Commons
+ commercial / non-share-alike / attribution license is solely the
+ requirement for attribution. (Similarly the BSD license and such.)
+ While I would *appreciate* acknowledgement and attribution, I believe
+ that it is foolish to place a legal encumberment (i.e. a license) on
+ the software *solely* to get attribution.
+
+ In other words, I'm arguing that PD is superior to the BSD license and
+ the Creative Commons 'Attribution' license. If the license offers
+ anything besides attribution -- as does, e.g., CC NonCommercial-ShareAlike,
+ or the GPL -- that's a separate discussion.
+
+3. Other aspects of BSD-style licenses besides attribution
+
+ Permissive licenses like zlib and BSD license are perfectly reasonable
+ in their requirements, but they are very wordy and
+ have only two benefits over public domain: legally-mandated
+ attribution and liability-control. I do not believe these
+ are worth the excessive verbosity and user-unfriendliness
+ these licenses induce, especially in the single-file
+ case where those licenses tend to be at the top of
+ the file, the first thing you see.
+
+ To the specific points, I have had no trouble receiving
+ attribution for my libraries; liability in the face of
+ no explicit disclaimer of liability is an open question,
+ but one I have a lot of difficulty imagining there being
+ any actual doubt about in court. Sometimes I explicitly
+ note in my libraries that I make no guarantees about them
+ being fit for purpose, but it's pretty absurd to do this;
+ as a whole, it comes across as "here is a library to decode
+ vorbis audio files, but it may not actually work and if
+ you have problems it's not my fault, but also please
+ report bugs so I can fix them"--so dumb!
+
+4. full discussion from stb_howto.txt on what YOU should do for YOUR libs
+
+```
+EASY-TO-COMPLY LICENSE
+
+I make my libraries public domain. You don't have to.
+But my goal in releasing stb-style libraries is to
+reduce friction for potential users as much as
+possible. That means:
+
+ a. easy to build (what this file is mostly about)
+ b. easy to invoke (which requires good API design)
+ c. easy to deploy (which is about licensing)
+
+I choose to place all my libraries in the public
+domain, abjuring copyright, rather than license
+the libraries. This has some benefits and some
+drawbacks.
+
+Any license which is "viral" to modifications
+causes worries for lawyers, even if their programmers
+aren't modifying it.
+
+Any license which requires crediting in documentation
+adds friction which can add up. Valve has a huge list
+(http://nothings.org/remote/ThirdPartyLegalNotices_steam_2019.html)
+of all of these included in each game they ship,
+and it's insane, and obviously nobody ever looks
+at it so why would you care whether your credit
+appeared there?
+
+Permissive licenses like zlib and BSD license are
+perfectly reasonable, but they are very wordy and
+have only two benefits over public domain: legally-mandated
+attribution and liability-control. I do not believe these
+are worth the excessive verbosity and user-unfriendliness
+these licenses induce, especially in the single-file
+case where those licenses tend to be at the top of
+the file, the first thing you see. (To the specific
+points, I have had no trouble receiving attribution
+for my libraries; liability in the face of no explicit
+disclaimer of liability is an open question.)
+
+However, public domain has frictions of its own, because
+public domain declarations aren't necessary recognized
+in the USA and some other locations. For that reason,
+I recommend a declaration along these lines:
+
+// This software is dual-licensed to the public domain and under the following
+// license: you are granted a perpetual, irrevocable license to copy, modify,
+// publish, and distribute this file as you see fit.
+
+I typically place this declaration at the end of the initial
+comment block of the file and just say 'public domain'
+at the top.
+
+I have had people say they couldn't use one of my
+libraries because it was only "public domain" and didn't
+have the additional fallback clause, who asked if
+I could dual-license it under a traditional license.
+
+My answer: they can create a derivative work by
+modifying one character, and then license that however
+they like. (Indeed, *adding* the zlib or BSD license
+would be such a modification!) Unfortunately, their
+lawyers reportedly didn't like that answer. :(
+```
diff --git a/third-party/stb/stb_c_lexer.h b/third-party/stb/stb_c_lexer.h
new file mode 100644
index 0000000..bf89dca
--- /dev/null
+++ b/third-party/stb/stb_c_lexer.h
@@ -0,0 +1,940 @@
+// stb_c_lexer.h - v0.12 - public domain Sean Barrett 2013
+// lexer for making little C-like languages with recursive-descent parsers
+//
+// This file provides both the interface and the implementation.
+// To instantiate the implementation,
+// #define STB_C_LEXER_IMPLEMENTATION
+// in *ONE* source file, before #including this file.
+//
+// The default configuration is fairly close to a C lexer, although
+// suffixes on integer constants are not handled (you can override this).
+//
+// History:
+// 0.12 fix compilation bug for NUL support; better support separate inclusion
+// 0.11 fix clang static analysis warning
+// 0.10 fix warnings
+// 0.09 hex floats, no-stdlib fixes
+// 0.08 fix bad pointer comparison
+// 0.07 fix mishandling of hexadecimal constants parsed by strtol
+// 0.06 fix missing next character after ending quote mark (Andreas Fredriksson)
+// 0.05 refixed get_location because github version had lost the fix
+// 0.04 fix octal parsing bug
+// 0.03 added STB_C_LEX_DISCARD_PREPROCESSOR option
+// refactor API to simplify (only one struct instead of two)
+// change literal enum names to have 'lit' at the end
+// 0.02 first public release
+//
+// Status:
+// - haven't tested compiling as C++
+// - haven't tested the float parsing path
+// - haven't tested the non-default-config paths (e.g. non-stdlib)
+// - only tested default-config paths by eyeballing output of self-parse
+//
+// - haven't implemented multiline strings
+// - haven't implemented octal/hex character constants
+// - haven't implemented support for unicode CLEX_char
+// - need to expand error reporting so you don't just get "CLEX_parse_error"
+//
+// Contributors:
+// Arpad Goretity (bugfix)
+// Alan Hickman (hex floats)
+//
+// LICENSE
+//
+// See end of file for license information.
+
+#ifdef STB_C_LEXER_IMPLEMENTATION
+#ifndef STB_C_LEXER_DEFINITIONS
+// to change the default parsing rules, copy the following lines
+// into your C/C++ file *before* including this, and then replace
+// the Y's with N's for the ones you don't want. This needs to be
+// set to the same values for every place in your program where
+// stb_c_lexer.h is included.
+// --BEGIN--
+
+#if defined(Y) || defined(N)
+#error "Can only use stb_c_lexer in contexts where the preprocessor symbols 'Y' and 'N' are not defined"
+#endif
+
+#define STB_C_LEX_C_DECIMAL_INTS Y // "0|[1-9][0-9]*" CLEX_intlit
+#define STB_C_LEX_C_HEX_INTS Y // "0x[0-9a-fA-F]+" CLEX_intlit
+#define STB_C_LEX_C_OCTAL_INTS Y // "[0-7]+" CLEX_intlit
+#define STB_C_LEX_C_DECIMAL_FLOATS Y // "[0-9]*(.[0-9]*([eE][-+]?[0-9]+)?) CLEX_floatlit
+#define STB_C_LEX_C99_HEX_FLOATS N // "0x{hex}+(.{hex}*)?[pP][-+]?{hex}+ CLEX_floatlit
+#define STB_C_LEX_C_IDENTIFIERS Y // "[_a-zA-Z][_a-zA-Z0-9]*" CLEX_id
+#define STB_C_LEX_C_DQ_STRINGS Y // double-quote-delimited strings with escapes CLEX_dqstring
+#define STB_C_LEX_C_SQ_STRINGS N // single-quote-delimited strings with escapes CLEX_ssstring
+#define STB_C_LEX_C_CHARS Y // single-quote-delimited character with escape CLEX_charlits
+#define STB_C_LEX_C_COMMENTS Y // "/* comment */"
+#define STB_C_LEX_CPP_COMMENTS Y // "// comment to end of line\n"
+#define STB_C_LEX_C_COMPARISONS Y // "==" CLEX_eq "!=" CLEX_noteq "<=" CLEX_lesseq ">=" CLEX_greatereq
+#define STB_C_LEX_C_LOGICAL Y // "&&" CLEX_andand "||" CLEX_oror
+#define STB_C_LEX_C_SHIFTS Y // "<<" CLEX_shl ">>" CLEX_shr
+#define STB_C_LEX_C_INCREMENTS Y // "++" CLEX_plusplus "--" CLEX_minusminus
+#define STB_C_LEX_C_ARROW Y // "->" CLEX_arrow
+#define STB_C_LEX_EQUAL_ARROW N // "=>" CLEX_eqarrow
+#define STB_C_LEX_C_BITWISEEQ Y // "&=" CLEX_andeq "|=" CLEX_oreq "^=" CLEX_xoreq
+#define STB_C_LEX_C_ARITHEQ Y // "+=" CLEX_pluseq "-=" CLEX_minuseq
+ // "*=" CLEX_muleq "/=" CLEX_diveq "%=" CLEX_modeq
+ // if both STB_C_LEX_SHIFTS & STB_C_LEX_ARITHEQ:
+ // "<<=" CLEX_shleq ">>=" CLEX_shreq
+
+#define STB_C_LEX_PARSE_SUFFIXES N // letters after numbers are parsed as part of those numbers, and must be in suffix list below
+#define STB_C_LEX_DECIMAL_SUFFIXES "" // decimal integer suffixes e.g. "uUlL" -- these are returned as-is in string storage
+#define STB_C_LEX_HEX_SUFFIXES "" // e.g. "uUlL"
+#define STB_C_LEX_OCTAL_SUFFIXES "" // e.g. "uUlL"
+#define STB_C_LEX_FLOAT_SUFFIXES "" //
+
+#define STB_C_LEX_0_IS_EOF N // if Y, ends parsing at '\0'; if N, returns '\0' as token
+#define STB_C_LEX_INTEGERS_AS_DOUBLES N // parses integers as doubles so they can be larger than 'int', but only if STB_C_LEX_STDLIB==N
+#define STB_C_LEX_MULTILINE_DSTRINGS N // allow newlines in double-quoted strings
+#define STB_C_LEX_MULTILINE_SSTRINGS N // allow newlines in single-quoted strings
+#define STB_C_LEX_USE_STDLIB Y // use strtod,strtol for parsing #s; otherwise inaccurate hack
+#define STB_C_LEX_DOLLAR_IDENTIFIER Y // allow $ as an identifier character
+#define STB_C_LEX_FLOAT_NO_DECIMAL Y // allow floats that have no decimal point if they have an exponent
+
+#define STB_C_LEX_DEFINE_ALL_TOKEN_NAMES N // if Y, all CLEX_ token names are defined, even if never returned
+ // leaving it as N should help you catch config bugs
+
+#define STB_C_LEX_DISCARD_PREPROCESSOR Y // discard C-preprocessor directives (e.g. after prepocess
+ // still have #line, #pragma, etc)
+
+//#define STB_C_LEX_ISWHITE(str) ... // return length in bytes of whitespace characters if first char is whitespace
+
+#define STB_C_LEXER_DEFINITIONS // This line prevents the header file from replacing your definitions
+// --END--
+#endif
+#endif
+
+#ifndef INCLUDE_STB_C_LEXER_H
+#define INCLUDE_STB_C_LEXER_H
+
+typedef struct
+{
+ // lexer variables
+ char *input_stream;
+ char *eof;
+ char *parse_point;
+ char *string_storage;
+ int string_storage_len;
+
+ // lexer parse location for error messages
+ char *where_firstchar;
+ char *where_lastchar;
+
+ // lexer token variables
+ long token;
+ double real_number;
+ long int_number;
+ char *string;
+ int string_len;
+} stb_lexer;
+
+typedef struct
+{
+ int line_number;
+ int line_offset;
+} stb_lex_location;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void stb_c_lexer_init(stb_lexer *lexer, const char *input_stream, const char *input_stream_end, char *string_store, int store_length);
+// this function initialize the 'lexer' structure
+// Input:
+// - input_stream points to the file to parse, loaded into memory
+// - input_stream_end points to the end of the file, or NULL if you use 0-for-EOF
+// - string_store is storage the lexer can use for storing parsed strings and identifiers
+// - store_length is the length of that storage
+
+extern int stb_c_lexer_get_token(stb_lexer *lexer);
+// this function returns non-zero if a token is parsed, or 0 if at EOF
+// Output:
+// - lexer->token is the token ID, which is unicode code point for a single-char token, < 0 for a multichar or eof or error
+// - lexer->real_number is a double constant value for CLEX_floatlit, or CLEX_intlit if STB_C_LEX_INTEGERS_AS_DOUBLES
+// - lexer->int_number is an integer constant for CLEX_intlit if !STB_C_LEX_INTEGERS_AS_DOUBLES, or character for CLEX_charlit
+// - lexer->string is a 0-terminated string for CLEX_dqstring or CLEX_sqstring or CLEX_identifier
+// - lexer->string_len is the byte length of lexer->string
+
+extern void stb_c_lexer_get_location(const stb_lexer *lexer, const char *where, stb_lex_location *loc);
+// this inefficient function returns the line number and character offset of a
+// given location in the file as returned by stb_lex_token. Because it's inefficient,
+// you should only call it for errors, not for every token.
+// For error messages of invalid tokens, you typically want the location of the start
+// of the token (which caused the token to be invalid). For bugs involving legit
+// tokens, you can report the first or the range.
+// Output:
+// - loc->line_number is the line number in the file, counting from 1, of the location
+// - loc->line_offset is the char-offset in the line, counting from 0, of the location
+
+
+#ifdef __cplusplus
+}
+#endif
+
+enum
+{
+ CLEX_eof = 256,
+ CLEX_parse_error,
+ CLEX_intlit ,
+ CLEX_floatlit ,
+ CLEX_id ,
+ CLEX_dqstring ,
+ CLEX_sqstring ,
+ CLEX_charlit ,
+ CLEX_eq ,
+ CLEX_noteq ,
+ CLEX_lesseq ,
+ CLEX_greatereq ,
+ CLEX_andand ,
+ CLEX_oror ,
+ CLEX_shl ,
+ CLEX_shr ,
+ CLEX_plusplus ,
+ CLEX_minusminus ,
+ CLEX_pluseq ,
+ CLEX_minuseq ,
+ CLEX_muleq ,
+ CLEX_diveq ,
+ CLEX_modeq ,
+ CLEX_andeq ,
+ CLEX_oreq ,
+ CLEX_xoreq ,
+ CLEX_arrow ,
+ CLEX_eqarrow ,
+ CLEX_shleq, CLEX_shreq,
+
+ CLEX_first_unused_token
+
+};
+#endif // INCLUDE_STB_C_LEXER_H
+
+#ifdef STB_C_LEXER_IMPLEMENTATION
+
+// Hacky definitions so we can easily #if on them
+#define Y(x) 1
+#define N(x) 0
+
+#if STB_C_LEX_INTEGERS_AS_DOUBLES(x)
+typedef double stb__clex_int;
+#define intfield real_number
+#define STB__clex_int_as_double
+#else
+typedef long stb__clex_int;
+#define intfield int_number
+#endif
+
+// Convert these config options to simple conditional #defines so we can more
+// easily test them once we've change the meaning of Y/N
+
+#if STB_C_LEX_PARSE_SUFFIXES(x)
+#define STB__clex_parse_suffixes
+#endif
+
+#if STB_C_LEX_C99_HEX_FLOATS(x)
+#define STB__clex_hex_floats
+#endif
+
+#if STB_C_LEX_C_HEX_INTS(x)
+#define STB__clex_hex_ints
+#endif
+
+#if STB_C_LEX_C_DECIMAL_INTS(x)
+#define STB__clex_decimal_ints
+#endif
+
+#if STB_C_LEX_C_OCTAL_INTS(x)
+#define STB__clex_octal_ints
+#endif
+
+#if STB_C_LEX_C_DECIMAL_FLOATS(x)
+#define STB__clex_decimal_floats
+#endif
+
+#if STB_C_LEX_DISCARD_PREPROCESSOR(x)
+#define STB__clex_discard_preprocessor
+#endif
+
+#if STB_C_LEX_USE_STDLIB(x) && (!defined(STB__clex_hex_floats) || __STDC_VERSION__ >= 199901L)
+#define STB__CLEX_use_stdlib
+#include
+#endif
+
+// Now for the rest of the file we'll use the basic definition where
+// where Y expands to its contents and N expands to nothing
+#undef Y
+#define Y(a) a
+#undef N
+#define N(a)
+
+// API function
+void stb_c_lexer_init(stb_lexer *lexer, const char *input_stream, const char *input_stream_end, char *string_store, int store_length)
+{
+ lexer->input_stream = (char *) input_stream;
+ lexer->eof = (char *) input_stream_end;
+ lexer->parse_point = (char *) input_stream;
+ lexer->string_storage = string_store;
+ lexer->string_storage_len = store_length;
+}
+
+// API function
+void stb_c_lexer_get_location(const stb_lexer *lexer, const char *where, stb_lex_location *loc)
+{
+ char *p = lexer->input_stream;
+ int line_number = 1;
+ int char_offset = 0;
+ while (*p && p < where) {
+ if (*p == '\n' || *p == '\r') {
+ p += (p[0]+p[1] == '\r'+'\n' ? 2 : 1); // skip newline
+ line_number += 1;
+ char_offset = 0;
+ } else {
+ ++p;
+ ++char_offset;
+ }
+ }
+ loc->line_number = line_number;
+ loc->line_offset = char_offset;
+}
+
+// main helper function for returning a parsed token
+static int stb__clex_token(stb_lexer *lexer, int token, char *start, char *end)
+{
+ lexer->token = token;
+ lexer->where_firstchar = start;
+ lexer->where_lastchar = end;
+ lexer->parse_point = end+1;
+ return 1;
+}
+
+// helper function for returning eof
+static int stb__clex_eof(stb_lexer *lexer)
+{
+ lexer->token = CLEX_eof;
+ return 0;
+}
+
+static int stb__clex_iswhite(int x)
+{
+ return x == ' ' || x == '\t' || x == '\r' || x == '\n' || x == '\f';
+}
+
+static const char *stb__strchr(const char *str, int ch)
+{
+ for (; *str; ++str)
+ if (*str == ch)
+ return str;
+ return 0;
+}
+
+// parse suffixes at the end of a number
+static int stb__clex_parse_suffixes(stb_lexer *lexer, long tokenid, char *start, char *cur, const char *suffixes)
+{
+ #ifdef STB__clex_parse_suffixes
+ lexer->string = lexer->string_storage;
+ lexer->string_len = 0;
+
+ while ((*cur >= 'a' && *cur <= 'z') || (*cur >= 'A' && *cur <= 'Z')) {
+ if (stb__strchr(suffixes, *cur) == 0)
+ return stb__clex_token(lexer, CLEX_parse_error, start, cur);
+ if (lexer->string_len+1 >= lexer->string_storage_len)
+ return stb__clex_token(lexer, CLEX_parse_error, start, cur);
+ lexer->string[lexer->string_len++] = *cur++;
+ }
+ #else
+ suffixes = suffixes; // attempt to suppress warnings
+ #endif
+ return stb__clex_token(lexer, tokenid, start, cur-1);
+}
+
+#ifndef STB__CLEX_use_stdlib
+static double stb__clex_pow(double base, unsigned int exponent)
+{
+ double value=1;
+ for ( ; exponent; exponent >>= 1) {
+ if (exponent & 1)
+ value *= base;
+ base *= base;
+ }
+ return value;
+}
+
+static double stb__clex_parse_float(char *p, char **q)
+{
+ char *s = p;
+ double value=0;
+ int base=10;
+ int exponent=0;
+
+#ifdef STB__clex_hex_floats
+ if (*p == '0') {
+ if (p[1] == 'x' || p[1] == 'X') {
+ base=16;
+ p += 2;
+ }
+ }
+#endif
+
+ for (;;) {
+ if (*p >= '0' && *p <= '9')
+ value = value*base + (*p++ - '0');
+#ifdef STB__clex_hex_floats
+ else if (base == 16 && *p >= 'a' && *p <= 'f')
+ value = value*base + 10 + (*p++ - 'a');
+ else if (base == 16 && *p >= 'A' && *p <= 'F')
+ value = value*base + 10 + (*p++ - 'A');
+#endif
+ else
+ break;
+ }
+
+ if (*p == '.') {
+ double pow, addend = 0;
+ ++p;
+ for (pow=1; ; pow*=base) {
+ if (*p >= '0' && *p <= '9')
+ addend = addend*base + (*p++ - '0');
+#ifdef STB__clex_hex_floats
+ else if (base == 16 && *p >= 'a' && *p <= 'f')
+ addend = addend*base + 10 + (*p++ - 'a');
+ else if (base == 16 && *p >= 'A' && *p <= 'F')
+ addend = addend*base + 10 + (*p++ - 'A');
+#endif
+ else
+ break;
+ }
+ value += addend / pow;
+ }
+#ifdef STB__clex_hex_floats
+ if (base == 16) {
+ // exponent required for hex float literal
+ if (*p != 'p' && *p != 'P') {
+ *q = s;
+ return 0;
+ }
+ exponent = 1;
+ } else
+#endif
+ exponent = (*p == 'e' || *p == 'E');
+
+ if (exponent) {
+ int sign = p[1] == '-';
+ unsigned int exponent=0;
+ double power=1;
+ ++p;
+ if (*p == '-' || *p == '+')
+ ++p;
+ while (*p >= '0' && *p <= '9')
+ exponent = exponent*10 + (*p++ - '0');
+
+#ifdef STB__clex_hex_floats
+ if (base == 16)
+ power = stb__clex_pow(2, exponent);
+ else
+#endif
+ power = stb__clex_pow(10, exponent);
+ if (sign)
+ value /= power;
+ else
+ value *= power;
+ }
+ *q = p;
+ return value;
+}
+#endif
+
+static int stb__clex_parse_char(char *p, char **q)
+{
+ if (*p == '\\') {
+ *q = p+2; // tentatively guess we'll parse two characters
+ switch(p[1]) {
+ case '\\': return '\\';
+ case '\'': return '\'';
+ case '"': return '"';
+ case 't': return '\t';
+ case 'f': return '\f';
+ case 'n': return '\n';
+ case 'r': return '\r';
+ case '0': return '\0'; // @TODO ocatal constants
+ case 'x': case 'X': return -1; // @TODO hex constants
+ case 'u': return -1; // @TODO unicode constants
+ }
+ }
+ *q = p+1;
+ return (unsigned char) *p;
+}
+
+static int stb__clex_parse_string(stb_lexer *lexer, char *p, int type)
+{
+ char *start = p;
+ char delim = *p++; // grab the " or ' for later matching
+ char *out = lexer->string_storage;
+ char *outend = lexer->string_storage + lexer->string_storage_len;
+ while (*p != delim) {
+ int n;
+ if (*p == '\\') {
+ char *q;
+ n = stb__clex_parse_char(p, &q);
+ if (n < 0)
+ return stb__clex_token(lexer, CLEX_parse_error, start, q);
+ p = q;
+ } else {
+ // @OPTIMIZE: could speed this up by looping-while-not-backslash
+ n = (unsigned char) *p++;
+ }
+ if (out+1 > outend)
+ return stb__clex_token(lexer, CLEX_parse_error, start, p);
+ // @TODO expand unicode escapes to UTF8
+ *out++ = (char) n;
+ }
+ *out = 0;
+ lexer->string = lexer->string_storage;
+ lexer->string_len = (int) (out - lexer->string_storage);
+ return stb__clex_token(lexer, type, start, p);
+}
+
+int stb_c_lexer_get_token(stb_lexer *lexer)
+{
+ char *p = lexer->parse_point;
+
+ // skip whitespace and comments
+ for (;;) {
+ #ifdef STB_C_LEX_ISWHITE
+ while (p != lexer->stream_end) {
+ int n;
+ n = STB_C_LEX_ISWHITE(p);
+ if (n == 0) break;
+ if (lexer->eof && lexer->eof - lexer->parse_point < n)
+ return stb__clex_token(tok, CLEX_parse_error, p,lexer->eof-1);
+ p += n;
+ }
+ #else
+ while (p != lexer->eof && stb__clex_iswhite(*p))
+ ++p;
+ #endif
+
+ STB_C_LEX_CPP_COMMENTS(
+ if (p != lexer->eof && p[0] == '/' && p[1] == '/') {
+ while (p != lexer->eof && *p != '\r' && *p != '\n')
+ ++p;
+ continue;
+ }
+ )
+
+ STB_C_LEX_C_COMMENTS(
+ if (p != lexer->eof && p[0] == '/' && p[1] == '*') {
+ char *start = p;
+ p += 2;
+ while (p != lexer->eof && (p[0] != '*' || p[1] != '/'))
+ ++p;
+ if (p == lexer->eof)
+ return stb__clex_token(lexer, CLEX_parse_error, start, p-1);
+ p += 2;
+ continue;
+ }
+ )
+
+ #ifdef STB__clex_discard_preprocessor
+ // @TODO this discards everything after a '#', regardless
+ // of where in the line the # is, rather than requiring it
+ // be at the start. (because this parser doesn't otherwise
+ // check for line breaks!)
+ if (p != lexer->eof && p[0] == '#') {
+ while (p != lexer->eof && *p != '\r' && *p != '\n')
+ ++p;
+ continue;
+ }
+ #endif
+
+ break;
+ }
+
+ if (p == lexer->eof)
+ return stb__clex_eof(lexer);
+
+ switch (*p) {
+ default:
+ if ( (*p >= 'a' && *p <= 'z')
+ || (*p >= 'A' && *p <= 'Z')
+ || *p == '_' || (unsigned char) *p >= 128 // >= 128 is UTF8 char
+ STB_C_LEX_DOLLAR_IDENTIFIER( || *p == '$' ) )
+ {
+ int n = 0;
+ lexer->string = lexer->string_storage;
+ lexer->string_len = n;
+ do {
+ if (n+1 >= lexer->string_storage_len)
+ return stb__clex_token(lexer, CLEX_parse_error, p, p+n);
+ lexer->string[n] = p[n];
+ ++n;
+ } while (
+ (p[n] >= 'a' && p[n] <= 'z')
+ || (p[n] >= 'A' && p[n] <= 'Z')
+ || (p[n] >= '0' && p[n] <= '9') // allow digits in middle of identifier
+ || p[n] == '_' || (unsigned char) p[n] >= 128
+ STB_C_LEX_DOLLAR_IDENTIFIER( || p[n] == '$' )
+ );
+ lexer->string[n] = 0;
+ return stb__clex_token(lexer, CLEX_id, p, p+n-1);
+ }
+
+ // check for EOF
+ STB_C_LEX_0_IS_EOF(
+ if (*p == 0)
+ return stb__clex_eof(lexer);
+ )
+
+ single_char:
+ // not an identifier, return the character as itself
+ return stb__clex_token(lexer, *p, p, p);
+
+ case '+':
+ if (p+1 != lexer->eof) {
+ STB_C_LEX_C_INCREMENTS(if (p[1] == '+') return stb__clex_token(lexer, CLEX_plusplus, p,p+1);)
+ STB_C_LEX_C_ARITHEQ( if (p[1] == '=') return stb__clex_token(lexer, CLEX_pluseq , p,p+1);)
+ }
+ goto single_char;
+ case '-':
+ if (p+1 != lexer->eof) {
+ STB_C_LEX_C_INCREMENTS(if (p[1] == '-') return stb__clex_token(lexer, CLEX_minusminus, p,p+1);)
+ STB_C_LEX_C_ARITHEQ( if (p[1] == '=') return stb__clex_token(lexer, CLEX_minuseq , p,p+1);)
+ STB_C_LEX_C_ARROW( if (p[1] == '>') return stb__clex_token(lexer, CLEX_arrow , p,p+1);)
+ }
+ goto single_char;
+ case '&':
+ if (p+1 != lexer->eof) {
+ STB_C_LEX_C_LOGICAL( if (p[1] == '&') return stb__clex_token(lexer, CLEX_andand, p,p+1);)
+ STB_C_LEX_C_BITWISEEQ(if (p[1] == '=') return stb__clex_token(lexer, CLEX_andeq , p,p+1);)
+ }
+ goto single_char;
+ case '|':
+ if (p+1 != lexer->eof) {
+ STB_C_LEX_C_LOGICAL( if (p[1] == '|') return stb__clex_token(lexer, CLEX_oror, p,p+1);)
+ STB_C_LEX_C_BITWISEEQ(if (p[1] == '=') return stb__clex_token(lexer, CLEX_oreq, p,p+1);)
+ }
+ goto single_char;
+ case '=':
+ if (p+1 != lexer->eof) {
+ STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_eq, p,p+1);)
+ STB_C_LEX_EQUAL_ARROW( if (p[1] == '>') return stb__clex_token(lexer, CLEX_eqarrow, p,p+1);)
+ }
+ goto single_char;
+ case '!':
+ STB_C_LEX_C_COMPARISONS(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_noteq, p,p+1);)
+ goto single_char;
+ case '^':
+ STB_C_LEX_C_BITWISEEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_xoreq, p,p+1));
+ goto single_char;
+ case '%':
+ STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_modeq, p,p+1));
+ goto single_char;
+ case '*':
+ STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_muleq, p,p+1));
+ goto single_char;
+ case '/':
+ STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_diveq, p,p+1));
+ goto single_char;
+ case '<':
+ if (p+1 != lexer->eof) {
+ STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_lesseq, p,p+1);)
+ STB_C_LEX_C_SHIFTS( if (p[1] == '<') {
+ STB_C_LEX_C_ARITHEQ(if (p+2 != lexer->eof && p[2] == '=')
+ return stb__clex_token(lexer, CLEX_shleq, p,p+2);)
+ return stb__clex_token(lexer, CLEX_shl, p,p+1);
+ }
+ )
+ }
+ goto single_char;
+ case '>':
+ if (p+1 != lexer->eof) {
+ STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_greatereq, p,p+1);)
+ STB_C_LEX_C_SHIFTS( if (p[1] == '>') {
+ STB_C_LEX_C_ARITHEQ(if (p+2 != lexer->eof && p[2] == '=')
+ return stb__clex_token(lexer, CLEX_shreq, p,p+2);)
+ return stb__clex_token(lexer, CLEX_shr, p,p+1);
+ }
+ )
+ }
+ goto single_char;
+
+ case '"':
+ STB_C_LEX_C_DQ_STRINGS(return stb__clex_parse_string(lexer, p, CLEX_dqstring);)
+ goto single_char;
+ case '\'':
+ STB_C_LEX_C_SQ_STRINGS(return stb__clex_parse_string(lexer, p, CLEX_sqstring);)
+ STB_C_LEX_C_CHARS(
+ {
+ char *start = p;
+ lexer->int_number = stb__clex_parse_char(p+1, &p);
+ if (lexer->int_number < 0)
+ return stb__clex_token(lexer, CLEX_parse_error, start,start);
+ if (p == lexer->eof || *p != '\'')
+ return stb__clex_token(lexer, CLEX_parse_error, start,p);
+ return stb__clex_token(lexer, CLEX_charlit, start, p+1);
+ })
+ goto single_char;
+
+ case '0':
+ #if defined(STB__clex_hex_ints) || defined(STB__clex_hex_floats)
+ if (p+1 != lexer->eof) {
+ if (p[1] == 'x' || p[1] == 'X') {
+ char *q;
+
+ #ifdef STB__clex_hex_floats
+ for (q=p+2;
+ q != lexer->eof && ((*q >= '0' && *q <= '9') || (*q >= 'a' && *q <= 'f') || (*q >= 'A' && *q <= 'F'));
+ ++q);
+ if (q != lexer->eof) {
+ if (*q == '.' STB_C_LEX_FLOAT_NO_DECIMAL(|| *q == 'p' || *q == 'P')) {
+ #ifdef STB__CLEX_use_stdlib
+ lexer->real_number = strtod((char *) p, (char**) &q);
+ #else
+ lexer->real_number = stb__clex_parse_float(p, &q);
+ #endif
+
+ if (p == q)
+ return stb__clex_token(lexer, CLEX_parse_error, p,q);
+ return stb__clex_parse_suffixes(lexer, CLEX_floatlit, p,q, STB_C_LEX_FLOAT_SUFFIXES);
+
+ }
+ }
+ #endif // STB__CLEX_hex_floats
+
+ #ifdef STB__clex_hex_ints
+ #ifdef STB__CLEX_use_stdlib
+ lexer->int_number = strtol((char *) p, (char **) &q, 16);
+ #else
+ {
+ stb__clex_int n=0;
+ for (q=p+2; q != lexer->eof; ++q) {
+ if (*q >= '0' && *q <= '9')
+ n = n*16 + (*q - '0');
+ else if (*q >= 'a' && *q <= 'f')
+ n = n*16 + (*q - 'a') + 10;
+ else if (*q >= 'A' && *q <= 'F')
+ n = n*16 + (*q - 'A') + 10;
+ else
+ break;
+ }
+ lexer->int_number = n;
+ }
+ #endif
+ if (q == p+2)
+ return stb__clex_token(lexer, CLEX_parse_error, p-2,p-1);
+ return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_HEX_SUFFIXES);
+ #endif
+ }
+ }
+ #endif // defined(STB__clex_hex_ints) || defined(STB__clex_hex_floats)
+ // can't test for octal because we might parse '0.0' as float or as '0' '.' '0',
+ // so have to do float first
+
+ /* FALL THROUGH */
+ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
+
+ #ifdef STB__clex_decimal_floats
+ {
+ char *q = p;
+ while (q != lexer->eof && (*q >= '0' && *q <= '9'))
+ ++q;
+ if (q != lexer->eof) {
+ if (*q == '.' STB_C_LEX_FLOAT_NO_DECIMAL(|| *q == 'e' || *q == 'E')) {
+ #ifdef STB__CLEX_use_stdlib
+ lexer->real_number = strtod((char *) p, (char**) &q);
+ #else
+ lexer->real_number = stb__clex_parse_float(p, &q);
+ #endif
+
+ return stb__clex_parse_suffixes(lexer, CLEX_floatlit, p,q, STB_C_LEX_FLOAT_SUFFIXES);
+
+ }
+ }
+ }
+ #endif // STB__clex_decimal_floats
+
+ #ifdef STB__clex_octal_ints
+ if (p[0] == '0') {
+ char *q = p;
+ #ifdef STB__CLEX_use_stdlib
+ lexer->int_number = strtol((char *) p, (char **) &q, 8);
+ #else
+ stb__clex_int n=0;
+ while (q != lexer->eof) {
+ if (*q >= '0' && *q <= '7')
+ n = n*8 + (*q - '0');
+ else
+ break;
+ ++q;
+ }
+ if (q != lexer->eof && (*q == '8' || *q=='9'))
+ return stb__clex_token(lexer, CLEX_parse_error, p, q);
+ lexer->int_number = n;
+ #endif
+ return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_OCTAL_SUFFIXES);
+ }
+ #endif // STB__clex_octal_ints
+
+ #ifdef STB__clex_decimal_ints
+ {
+ char *q = p;
+ #ifdef STB__CLEX_use_stdlib
+ lexer->int_number = strtol((char *) p, (char **) &q, 10);
+ #else
+ stb__clex_int n=0;
+ while (q != lexer->eof) {
+ if (*q >= '0' && *q <= '9')
+ n = n*10 + (*q - '0');
+ else
+ break;
+ ++q;
+ }
+ lexer->int_number = n;
+ #endif
+ return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_OCTAL_SUFFIXES);
+ }
+ #endif // STB__clex_decimal_ints
+ goto single_char;
+ }
+}
+#endif // STB_C_LEXER_IMPLEMENTATION
+
+#ifdef STB_C_LEXER_SELF_TEST
+#define _CRT_SECURE_NO_WARNINGS
+#include
+#include
+
+static void print_token(stb_lexer *lexer)
+{
+ switch (lexer->token) {
+ case CLEX_id : printf("_%s", lexer->string); break;
+ case CLEX_eq : printf("=="); break;
+ case CLEX_noteq : printf("!="); break;
+ case CLEX_lesseq : printf("<="); break;
+ case CLEX_greatereq : printf(">="); break;
+ case CLEX_andand : printf("&&"); break;
+ case CLEX_oror : printf("||"); break;
+ case CLEX_shl : printf("<<"); break;
+ case CLEX_shr : printf(">>"); break;
+ case CLEX_plusplus : printf("++"); break;
+ case CLEX_minusminus: printf("--"); break;
+ case CLEX_arrow : printf("->"); break;
+ case CLEX_andeq : printf("&="); break;
+ case CLEX_oreq : printf("|="); break;
+ case CLEX_xoreq : printf("^="); break;
+ case CLEX_pluseq : printf("+="); break;
+ case CLEX_minuseq : printf("-="); break;
+ case CLEX_muleq : printf("*="); break;
+ case CLEX_diveq : printf("/="); break;
+ case CLEX_modeq : printf("%%="); break;
+ case CLEX_shleq : printf("<<="); break;
+ case CLEX_shreq : printf(">>="); break;
+ case CLEX_eqarrow : printf("=>"); break;
+ case CLEX_dqstring : printf("\"%s\"", lexer->string); break;
+ case CLEX_sqstring : printf("'\"%s\"'", lexer->string); break;
+ case CLEX_charlit : printf("'%s'", lexer->string); break;
+ #if defined(STB__clex_int_as_double) && !defined(STB__CLEX_use_stdlib)
+ case CLEX_intlit : printf("#%g", lexer->real_number); break;
+ #else
+ case CLEX_intlit : printf("#%ld", lexer->int_number); break;
+ #endif
+ case CLEX_floatlit : printf("%g", lexer->real_number); break;
+ default:
+ if (lexer->token >= 0 && lexer->token < 256)
+ printf("%c", (int) lexer->token);
+ else {
+ printf("<<>>\n", lexer->token);
+ }
+ break;
+ }
+}
+
+/* Force a test
+of parsing
+multiline comments */
+
+/*/ comment /*/
+/**/ extern /**/
+
+void dummy(void)
+{
+ double some_floats[] = {
+ 1.0501, -10.4e12, 5E+10,
+#if 0 // not supported in C++ or C-pre-99, so don't try to compile it, but let our parser test it
+ 0x1.0p+24, 0xff.FP-8, 0x1p-23,
+#endif
+ 4.
+ };
+ (void) sizeof(some_floats);
+ (void) some_floats[1];
+
+ printf("test %d",1); // https://github.com/nothings/stb/issues/13
+}
+
+int main(int argc, char **argv)
+{
+ FILE *f = fopen("stb_c_lexer.h","rb");
+ char *text = (char *) malloc(1 << 20);
+ int len = f ? (int) fread(text, 1, 1<<20, f) : -1;
+ stb_lexer lex;
+ if (len < 0) {
+ fprintf(stderr, "Error opening file\n");
+ free(text);
+ fclose(f);
+ return 1;
+ }
+ fclose(f);
+
+ stb_c_lexer_init(&lex, text, text+len, (char *) malloc(0x10000), 0x10000);
+ while (stb_c_lexer_get_token(&lex)) {
+ if (lex.token == CLEX_parse_error) {
+ printf("\n<<>>\n");
+ break;
+ }
+ print_token(&lex);
+ printf(" ");
+ }
+ return 0;
+}
+#endif
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+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 OR COPYRIGHT HOLDERS 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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+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.
+------------------------------------------------------------------------------
+*/
diff --git a/third-party/stb/stb_connected_components.h b/third-party/stb/stb_connected_components.h
new file mode 100644
index 0000000..f762f65
--- /dev/null
+++ b/third-party/stb/stb_connected_components.h
@@ -0,0 +1,1049 @@
+// stb_connected_components - v0.96 - public domain connected components on grids
+// http://github.com/nothings/stb
+//
+// Finds connected components on 2D grids for testing reachability between
+// two points, with fast updates when changing reachability (e.g. on one machine
+// it was typically 0.2ms w/ 1024x1024 grid). Each grid square must be "open" or
+// "closed" (traversable or untraversable), and grid squares are only connected
+// to their orthogonal neighbors, not diagonally.
+//
+// In one source file, create the implementation by doing something like this:
+//
+// #define STBCC_GRID_COUNT_X_LOG2 10
+// #define STBCC_GRID_COUNT_Y_LOG2 10
+// #define STB_CONNECTED_COMPONENTS_IMPLEMENTATION
+// #include "stb_connected_components.h"
+//
+// The above creates an implementation that can run on maps up to 1024x1024.
+// Map sizes must be a multiple of (1<<(LOG2/2)) on each axis (e.g. 32 if LOG2=10,
+// 16 if LOG2=8, etc.) (You can just pad your map with untraversable space.)
+//
+// MEMORY USAGE
+//
+// Uses about 6-7 bytes per grid square (e.g. 7MB for a 1024x1024 grid).
+// Uses a single worst-case allocation which you pass in.
+//
+// PERFORMANCE
+//
+// On a core i7-2700K at 3.5 Ghz, for a particular 1024x1024 map (map_03.png):
+//
+// Creating map : 44.85 ms
+// Making one square traversable: 0.27 ms (average over 29,448 calls)
+// Making one square untraversable: 0.23 ms (average over 30,123 calls)
+// Reachability query: 0.00001 ms (average over 4,000,000 calls)
+//
+// On non-degenerate maps update time is O(N^0.5), but on degenerate maps like
+// checkerboards or 50% random, update time is O(N^0.75) (~2ms on above machine).
+//
+// CHANGELOG
+//
+// 0.96 (2019-03-04) Fix warnings
+// 0.95 (2016-10-16) Bugfix if multiple clumps in one cluster connect to same clump in another
+// 0.94 (2016-04-17) Bugfix & optimize worst case (checkerboard & random)
+// 0.93 (2016-04-16) Reduce memory by 10x for 1Kx1K map; small speedup
+// 0.92 (2016-04-16) Compute sqrt(N) cluster size by default
+// 0.91 (2016-04-15) Initial release
+//
+// TODO:
+// - better API documentation
+// - more comments
+// - try re-integrating naive algorithm & compare performance
+// - more optimized batching (current approach still recomputes local clumps many times)
+// - function for setting a grid of squares at once (just use batching)
+//
+// LICENSE
+//
+// See end of file for license information.
+//
+// ALGORITHM
+//
+// The NxN grid map is split into sqrt(N) x sqrt(N) blocks called
+// "clusters". Each cluster independently computes a set of connected
+// components within that cluster (ignoring all connectivity out of
+// that cluster) using a union-find disjoint set forest. This produces a bunch
+// of locally connected components called "clumps". Each clump is (a) connected
+// within its cluster, (b) does not directly connect to any other clumps in the
+// cluster (though it may connect to them by paths that lead outside the cluster,
+// but those are ignored at this step), and (c) maintains an adjacency list of
+// all clumps in adjacent clusters that it _is_ connected to. Then a second
+// union-find disjoint set forest is used to compute connected clumps
+// globally, across the whole map. Reachability is then computed by
+// finding which clump each input point belongs to, and checking whether
+// those clumps are in the same "global" connected component.
+//
+// The above data structure can be updated efficiently; on a change
+// of a single grid square on the map, only one cluster changes its
+// purely-local state, so only one cluster needs its clumps fully
+// recomputed. Clumps in adjacent clusters need their adjacency lists
+// updated: first to remove all references to the old clumps in the
+// rebuilt cluster, then to add new references to the new clumps. Both
+// of these operations can use the existing "find which clump each input
+// point belongs to" query to compute that adjacency information rapidly.
+
+#ifndef INCLUDE_STB_CONNECTED_COMPONENTS_H
+#define INCLUDE_STB_CONNECTED_COMPONENTS_H
+
+#include
+
+typedef struct st_stbcc_grid stbcc_grid;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// initialization
+//
+
+// you allocate the grid data structure to this size (note that it will be very big!!!)
+extern size_t stbcc_grid_sizeof(void);
+
+// initialize the grid, value of map[] is 0 = traversable, non-0 is solid
+extern void stbcc_init_grid(stbcc_grid *g, unsigned char *map, int w, int h);
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// main functionality
+//
+
+// update a grid square state, 0 = traversable, non-0 is solid
+// i can add a batch-update if it's needed
+extern void stbcc_update_grid(stbcc_grid *g, int x, int y, int solid);
+
+// query if two grid squares are reachable from each other
+extern int stbcc_query_grid_node_connection(stbcc_grid *g, int x1, int y1, int x2, int y2);
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// bonus functions
+//
+
+// wrap multiple stbcc_update_grid calls in these function to compute
+// multiple updates more efficiently; cannot make queries inside batch
+extern void stbcc_update_batch_begin(stbcc_grid *g);
+extern void stbcc_update_batch_end(stbcc_grid *g);
+
+// query the grid data structure for whether a given square is open or not
+extern int stbcc_query_grid_open(stbcc_grid *g, int x, int y);
+
+// get a unique id for the connected component this is in; it's not necessarily
+// small, you'll need a hash table or something to remap it (or just use
+extern unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y);
+#define STBCC_NULL_UNIQUE_ID 0xffffffff // returned for closed map squares
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // INCLUDE_STB_CONNECTED_COMPONENTS_H
+
+#ifdef STB_CONNECTED_COMPONENTS_IMPLEMENTATION
+
+#include
+#include // memset
+
+#if !defined(STBCC_GRID_COUNT_X_LOG2) || !defined(STBCC_GRID_COUNT_Y_LOG2)
+ #error "You must define STBCC_GRID_COUNT_X_LOG2 and STBCC_GRID_COUNT_Y_LOG2 to define the max grid supported."
+#endif
+
+#define STBCC__GRID_COUNT_X (1 << STBCC_GRID_COUNT_X_LOG2)
+#define STBCC__GRID_COUNT_Y (1 << STBCC_GRID_COUNT_Y_LOG2)
+
+#define STBCC__MAP_STRIDE (1 << (STBCC_GRID_COUNT_X_LOG2-3))
+
+#ifndef STBCC_CLUSTER_SIZE_X_LOG2
+ #define STBCC_CLUSTER_SIZE_X_LOG2 (STBCC_GRID_COUNT_X_LOG2/2) // log2(sqrt(2^N)) = 1/2 * log2(2^N)) = 1/2 * N
+ #if STBCC_CLUSTER_SIZE_X_LOG2 > 6
+ #undef STBCC_CLUSTER_SIZE_X_LOG2
+ #define STBCC_CLUSTER_SIZE_X_LOG2 6
+ #endif
+#endif
+
+#ifndef STBCC_CLUSTER_SIZE_Y_LOG2
+ #define STBCC_CLUSTER_SIZE_Y_LOG2 (STBCC_GRID_COUNT_Y_LOG2/2)
+ #if STBCC_CLUSTER_SIZE_Y_LOG2 > 6
+ #undef STBCC_CLUSTER_SIZE_Y_LOG2
+ #define STBCC_CLUSTER_SIZE_Y_LOG2 6
+ #endif
+#endif
+
+#define STBCC__CLUSTER_SIZE_X (1 << STBCC_CLUSTER_SIZE_X_LOG2)
+#define STBCC__CLUSTER_SIZE_Y (1 << STBCC_CLUSTER_SIZE_Y_LOG2)
+
+#define STBCC__CLUSTER_COUNT_X_LOG2 (STBCC_GRID_COUNT_X_LOG2 - STBCC_CLUSTER_SIZE_X_LOG2)
+#define STBCC__CLUSTER_COUNT_Y_LOG2 (STBCC_GRID_COUNT_Y_LOG2 - STBCC_CLUSTER_SIZE_Y_LOG2)
+
+#define STBCC__CLUSTER_COUNT_X (1 << STBCC__CLUSTER_COUNT_X_LOG2)
+#define STBCC__CLUSTER_COUNT_Y (1 << STBCC__CLUSTER_COUNT_Y_LOG2)
+
+#if STBCC__CLUSTER_SIZE_X >= STBCC__GRID_COUNT_X || STBCC__CLUSTER_SIZE_Y >= STBCC__GRID_COUNT_Y
+ #error "STBCC_CLUSTER_SIZE_X/Y_LOG2 must be smaller than STBCC_GRID_COUNT_X/Y_LOG2"
+#endif
+
+// worst case # of clumps per cluster
+#define STBCC__MAX_CLUMPS_PER_CLUSTER_LOG2 (STBCC_CLUSTER_SIZE_X_LOG2 + STBCC_CLUSTER_SIZE_Y_LOG2-1)
+#define STBCC__MAX_CLUMPS_PER_CLUSTER (1 << STBCC__MAX_CLUMPS_PER_CLUSTER_LOG2)
+#define STBCC__MAX_CLUMPS (STBCC__MAX_CLUMPS_PER_CLUSTER * STBCC__CLUSTER_COUNT_X * STBCC__CLUSTER_COUNT_Y)
+#define STBCC__NULL_CLUMPID STBCC__MAX_CLUMPS_PER_CLUSTER
+
+#define STBCC__CLUSTER_X_FOR_COORD_X(x) ((x) >> STBCC_CLUSTER_SIZE_X_LOG2)
+#define STBCC__CLUSTER_Y_FOR_COORD_Y(y) ((y) >> STBCC_CLUSTER_SIZE_Y_LOG2)
+
+#define STBCC__MAP_BYTE_MASK(x,y) (1 << ((x) & 7))
+#define STBCC__MAP_BYTE(g,x,y) ((g)->map[y][(x) >> 3])
+#define STBCC__MAP_OPEN(g,x,y) (STBCC__MAP_BYTE(g,x,y) & STBCC__MAP_BYTE_MASK(x,y))
+
+typedef unsigned short stbcc__clumpid;
+typedef unsigned char stbcc__verify_max_clumps[STBCC__MAX_CLUMPS_PER_CLUSTER < (1 << (8*sizeof(stbcc__clumpid))) ? 1 : -1];
+
+#define STBCC__MAX_EXITS_PER_CLUSTER (STBCC__CLUSTER_SIZE_X + STBCC__CLUSTER_SIZE_Y) // 64 for 32x32
+#define STBCC__MAX_EXITS_PER_CLUMP (STBCC__CLUSTER_SIZE_X + STBCC__CLUSTER_SIZE_Y) // 64 for 32x32
+#define STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER (STBCC__MAX_EXITS_PER_CLUMP)
+
+// 2^19 * 2^6 => 2^25 exits => 2^26 => 64MB for 1024x1024
+
+// Logic for above on 4x4 grid:
+//
+// Many clumps: One clump:
+// + + + +
+// +X.X. +XX.X+
+// .X.X+ .XXX
+// +X.X. XXX.
+// .X.X+ +X.XX+
+// + + + +
+//
+// 8 exits either way
+
+typedef unsigned char stbcc__verify_max_exits[STBCC__MAX_EXITS_PER_CLUMP <= 256];
+
+typedef struct
+{
+ unsigned short clump_index:12;
+ signed short cluster_dx:2;
+ signed short cluster_dy:2;
+} stbcc__relative_clumpid;
+
+typedef union
+{
+ struct {
+ unsigned int clump_index:12;
+ unsigned int cluster_x:10;
+ unsigned int cluster_y:10;
+ } f;
+ unsigned int c;
+} stbcc__global_clumpid;
+
+// rebuilt cluster 3,4
+
+// what changes in cluster 2,4
+
+typedef struct
+{
+ stbcc__global_clumpid global_label; // 4
+ unsigned char num_adjacent; // 1
+ unsigned char max_adjacent; // 1
+ unsigned char adjacent_clump_list_index; // 1
+ unsigned char reserved;
+} stbcc__clump; // 8
+
+#define STBCC__CLUSTER_ADJACENCY_COUNT (STBCC__MAX_EXITS_PER_CLUSTER*2)
+typedef struct
+{
+ short num_clumps;
+ unsigned char num_edge_clumps;
+ unsigned char rebuild_adjacency;
+ stbcc__clump clump[STBCC__MAX_CLUMPS_PER_CLUSTER]; // 8 * 2^9 = 4KB
+ stbcc__relative_clumpid adjacency_storage[STBCC__CLUSTER_ADJACENCY_COUNT]; // 256 bytes
+} stbcc__cluster;
+
+struct st_stbcc_grid
+{
+ int w,h,cw,ch;
+ int in_batched_update;
+ //unsigned char cluster_dirty[STBCC__CLUSTER_COUNT_Y][STBCC__CLUSTER_COUNT_X]; // could bitpack, but: 1K x 1K => 1KB
+ unsigned char map[STBCC__GRID_COUNT_Y][STBCC__MAP_STRIDE]; // 1K x 1K => 1K x 128 => 128KB
+ stbcc__clumpid clump_for_node[STBCC__GRID_COUNT_Y][STBCC__GRID_COUNT_X]; // 1K x 1K x 2 = 2MB
+ stbcc__cluster cluster[STBCC__CLUSTER_COUNT_Y][STBCC__CLUSTER_COUNT_X]; // 1K x 4.5KB = 4.5MB
+};
+
+int stbcc_query_grid_node_connection(stbcc_grid *g, int x1, int y1, int x2, int y2)
+{
+ stbcc__global_clumpid label1, label2;
+ stbcc__clumpid c1 = g->clump_for_node[y1][x1];
+ stbcc__clumpid c2 = g->clump_for_node[y2][x2];
+ int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1);
+ int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1);
+ int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2);
+ int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2);
+ assert(!g->in_batched_update);
+ if (c1 == STBCC__NULL_CLUMPID || c2 == STBCC__NULL_CLUMPID)
+ return 0;
+ label1 = g->cluster[cy1][cx1].clump[c1].global_label;
+ label2 = g->cluster[cy2][cx2].clump[c2].global_label;
+ if (label1.c == label2.c)
+ return 1;
+ return 0;
+}
+
+int stbcc_query_grid_open(stbcc_grid *g, int x, int y)
+{
+ return STBCC__MAP_OPEN(g, x, y) != 0;
+}
+
+unsigned int stbcc_get_unique_id(stbcc_grid *g, int x, int y)
+{
+ stbcc__clumpid c = g->clump_for_node[y][x];
+ int cx = STBCC__CLUSTER_X_FOR_COORD_X(x);
+ int cy = STBCC__CLUSTER_Y_FOR_COORD_Y(y);
+ assert(!g->in_batched_update);
+ if (c == STBCC__NULL_CLUMPID) return STBCC_NULL_UNIQUE_ID;
+ return g->cluster[cy][cx].clump[c].global_label.c;
+}
+
+typedef struct
+{
+ unsigned char x,y;
+} stbcc__tinypoint;
+
+typedef struct
+{
+ stbcc__tinypoint parent[STBCC__CLUSTER_SIZE_Y][STBCC__CLUSTER_SIZE_X]; // 32x32 => 2KB
+ stbcc__clumpid label[STBCC__CLUSTER_SIZE_Y][STBCC__CLUSTER_SIZE_X];
+} stbcc__cluster_build_info;
+
+static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy);
+static void stbcc__remove_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy);
+static void stbcc__add_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy);
+
+static stbcc__global_clumpid stbcc__clump_find(stbcc_grid *g, stbcc__global_clumpid n)
+{
+ stbcc__global_clumpid q;
+ stbcc__clump *c = &g->cluster[n.f.cluster_y][n.f.cluster_x].clump[n.f.clump_index];
+
+ if (c->global_label.c == n.c)
+ return n;
+
+ q = stbcc__clump_find(g, c->global_label);
+ c->global_label = q;
+ return q;
+}
+
+typedef struct
+{
+ unsigned int cluster_x;
+ unsigned int cluster_y;
+ unsigned int clump_index;
+} stbcc__unpacked_clumpid;
+
+static void stbcc__clump_union(stbcc_grid *g, stbcc__unpacked_clumpid m, int x, int y, int idx)
+{
+ stbcc__clump *mc = &g->cluster[m.cluster_y][m.cluster_x].clump[m.clump_index];
+ stbcc__clump *nc = &g->cluster[y][x].clump[idx];
+ stbcc__global_clumpid mp = stbcc__clump_find(g, mc->global_label);
+ stbcc__global_clumpid np = stbcc__clump_find(g, nc->global_label);
+
+ if (mp.c == np.c)
+ return;
+
+ g->cluster[mp.f.cluster_y][mp.f.cluster_x].clump[mp.f.clump_index].global_label = np;
+}
+
+static void stbcc__build_connected_components_for_clumps(stbcc_grid *g)
+{
+ int i,j,k,h;
+
+ for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) {
+ for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) {
+ stbcc__cluster *cluster = &g->cluster[j][i];
+ for (k=0; k < (int) cluster->num_edge_clumps; ++k) {
+ stbcc__global_clumpid m;
+ m.f.clump_index = k;
+ m.f.cluster_x = i;
+ m.f.cluster_y = j;
+ assert((int) m.f.clump_index == k && (int) m.f.cluster_x == i && (int) m.f.cluster_y == j);
+ cluster->clump[k].global_label = m;
+ }
+ }
+ }
+
+ for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) {
+ for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) {
+ stbcc__cluster *cluster = &g->cluster[j][i];
+ for (k=0; k < (int) cluster->num_edge_clumps; ++k) {
+ stbcc__clump *clump = &cluster->clump[k];
+ stbcc__unpacked_clumpid m;
+ stbcc__relative_clumpid *adj;
+ m.clump_index = k;
+ m.cluster_x = i;
+ m.cluster_y = j;
+ adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index];
+ for (h=0; h < clump->num_adjacent; ++h) {
+ unsigned int clump_index = adj[h].clump_index;
+ unsigned int x = adj[h].cluster_dx + i;
+ unsigned int y = adj[h].cluster_dy + j;
+ stbcc__clump_union(g, m, x, y, clump_index);
+ }
+ }
+ }
+ }
+
+ for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j) {
+ for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i) {
+ stbcc__cluster *cluster = &g->cluster[j][i];
+ for (k=0; k < (int) cluster->num_edge_clumps; ++k) {
+ stbcc__global_clumpid m;
+ m.f.clump_index = k;
+ m.f.cluster_x = i;
+ m.f.cluster_y = j;
+ stbcc__clump_find(g, m);
+ }
+ }
+ }
+}
+
+static void stbcc__build_all_connections_for_cluster(stbcc_grid *g, int cx, int cy)
+{
+ // in this particular case, we are fully non-incremental. that means we
+ // can discover the correct sizes for the arrays, but requires we build
+ // the data into temporary data structures, or just count the sizes, so
+ // for simplicity we do the latter
+ stbcc__cluster *cluster = &g->cluster[cy][cx];
+ unsigned char connected[STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER/8]; // 64 x 8 => 1KB
+ unsigned char num_adj[STBCC__MAX_CLUMPS_PER_CLUSTER] = { 0 };
+ int x = cx * STBCC__CLUSTER_SIZE_X;
+ int y = cy * STBCC__CLUSTER_SIZE_Y;
+ int step_x, step_y=0, i, j, k, n, m, dx, dy, total;
+ int extra;
+
+ g->cluster[cy][cx].rebuild_adjacency = 0;
+
+ total = 0;
+ for (m=0; m < 4; ++m) {
+ switch (m) {
+ case 0:
+ dx = 1, dy = 0;
+ step_x = 0, step_y = 1;
+ i = STBCC__CLUSTER_SIZE_X-1;
+ j = 0;
+ n = STBCC__CLUSTER_SIZE_Y;
+ break;
+ case 1:
+ dx = -1, dy = 0;
+ i = 0;
+ j = 0;
+ step_x = 0;
+ step_y = 1;
+ n = STBCC__CLUSTER_SIZE_Y;
+ break;
+ case 2:
+ dy = -1, dx = 0;
+ i = 0;
+ j = 0;
+ step_x = 1;
+ step_y = 0;
+ n = STBCC__CLUSTER_SIZE_X;
+ break;
+ case 3:
+ dy = 1, dx = 0;
+ i = 0;
+ j = STBCC__CLUSTER_SIZE_Y-1;
+ step_x = 1;
+ step_y = 0;
+ n = STBCC__CLUSTER_SIZE_X;
+ break;
+ }
+
+ if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch)
+ continue;
+
+ memset(connected, 0, sizeof(connected));
+ for (k=0; k < n; ++k) {
+ if (STBCC__MAP_OPEN(g, x+i, y+j) && STBCC__MAP_OPEN(g, x+i+dx, y+j+dy)) {
+ stbcc__clumpid src = g->clump_for_node[y+j][x+i];
+ stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx];
+ if (0 == (connected[src][dest>>3] & (1 << (dest & 7)))) {
+ connected[src][dest>>3] |= 1 << (dest & 7);
+ ++num_adj[src];
+ ++total;
+ }
+ }
+ i += step_x;
+ j += step_y;
+ }
+ }
+
+ assert(total <= STBCC__CLUSTER_ADJACENCY_COUNT);
+
+ // decide how to apportion unused adjacency slots; only clumps that lie
+ // on the edges of the cluster need adjacency slots, so divide them up
+ // evenly between those clumps
+
+ // we want:
+ // extra = (STBCC__CLUSTER_ADJACENCY_COUNT - total) / cluster->num_edge_clumps;
+ // but we efficiently approximate this without a divide, because
+ // ignoring edge-vs-non-edge with 'num_adj[i]*2' was faster than
+ // 'num_adj[i]+extra' with the divide
+ if (total + (cluster->num_edge_clumps<<2) <= STBCC__CLUSTER_ADJACENCY_COUNT)
+ extra = 4;
+ else if (total + (cluster->num_edge_clumps<<1) <= STBCC__CLUSTER_ADJACENCY_COUNT)
+ extra = 2;
+ else if (total + (cluster->num_edge_clumps<<0) <= STBCC__CLUSTER_ADJACENCY_COUNT)
+ extra = 1;
+ else
+ extra = 0;
+
+ total = 0;
+ for (i=0; i < (int) cluster->num_edge_clumps; ++i) {
+ int alloc = num_adj[i]+extra;
+ if (alloc > STBCC__MAX_EXITS_PER_CLUSTER)
+ alloc = STBCC__MAX_EXITS_PER_CLUSTER;
+ assert(total < 256); // must fit in byte
+ cluster->clump[i].adjacent_clump_list_index = (unsigned char) total;
+ cluster->clump[i].max_adjacent = alloc;
+ cluster->clump[i].num_adjacent = 0;
+ total += alloc;
+ }
+ assert(total <= STBCC__CLUSTER_ADJACENCY_COUNT);
+
+ stbcc__add_connections_to_adjacent_cluster(g, cx, cy, -1, 0);
+ stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 1, 0);
+ stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 0,-1);
+ stbcc__add_connections_to_adjacent_cluster(g, cx, cy, 0, 1);
+ // make sure all of the above succeeded.
+ assert(g->cluster[cy][cx].rebuild_adjacency == 0);
+}
+
+static void stbcc__add_connections_to_adjacent_cluster_with_rebuild(stbcc_grid *g, int cx, int cy, int dx, int dy)
+{
+ if (cx >= 0 && cx < g->cw && cy >= 0 && cy < g->ch) {
+ stbcc__add_connections_to_adjacent_cluster(g, cx, cy, dx, dy);
+ if (g->cluster[cy][cx].rebuild_adjacency)
+ stbcc__build_all_connections_for_cluster(g, cx, cy);
+ }
+}
+
+void stbcc_update_grid(stbcc_grid *g, int x, int y, int solid)
+{
+ int cx,cy;
+
+ if (!solid) {
+ if (STBCC__MAP_OPEN(g,x,y))
+ return;
+ } else {
+ if (!STBCC__MAP_OPEN(g,x,y))
+ return;
+ }
+
+ cx = STBCC__CLUSTER_X_FOR_COORD_X(x);
+ cy = STBCC__CLUSTER_Y_FOR_COORD_Y(y);
+
+ stbcc__remove_connections_to_adjacent_cluster(g, cx-1, cy, 1, 0);
+ stbcc__remove_connections_to_adjacent_cluster(g, cx+1, cy, -1, 0);
+ stbcc__remove_connections_to_adjacent_cluster(g, cx, cy-1, 0, 1);
+ stbcc__remove_connections_to_adjacent_cluster(g, cx, cy+1, 0,-1);
+
+ if (!solid)
+ STBCC__MAP_BYTE(g,x,y) |= STBCC__MAP_BYTE_MASK(x,y);
+ else
+ STBCC__MAP_BYTE(g,x,y) &= ~STBCC__MAP_BYTE_MASK(x,y);
+
+ stbcc__build_clumps_for_cluster(g, cx, cy);
+ stbcc__build_all_connections_for_cluster(g, cx, cy);
+
+ stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx-1, cy, 1, 0);
+ stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx+1, cy, -1, 0);
+ stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx, cy-1, 0, 1);
+ stbcc__add_connections_to_adjacent_cluster_with_rebuild(g, cx, cy+1, 0,-1);
+
+ if (!g->in_batched_update)
+ stbcc__build_connected_components_for_clumps(g);
+ #if 0
+ else
+ g->cluster_dirty[cy][cx] = 1;
+ #endif
+}
+
+void stbcc_update_batch_begin(stbcc_grid *g)
+{
+ assert(!g->in_batched_update);
+ g->in_batched_update = 1;
+}
+
+void stbcc_update_batch_end(stbcc_grid *g)
+{
+ assert(g->in_batched_update);
+ g->in_batched_update = 0;
+ stbcc__build_connected_components_for_clumps(g); // @OPTIMIZE: only do this if update was non-empty
+}
+
+size_t stbcc_grid_sizeof(void)
+{
+ return sizeof(stbcc_grid);
+}
+
+void stbcc_init_grid(stbcc_grid *g, unsigned char *map, int w, int h)
+{
+ int i,j,k;
+ assert(w % STBCC__CLUSTER_SIZE_X == 0);
+ assert(h % STBCC__CLUSTER_SIZE_Y == 0);
+ assert(w % 8 == 0);
+
+ g->w = w;
+ g->h = h;
+ g->cw = w >> STBCC_CLUSTER_SIZE_X_LOG2;
+ g->ch = h >> STBCC_CLUSTER_SIZE_Y_LOG2;
+ g->in_batched_update = 0;
+
+ #if 0
+ for (j=0; j < STBCC__CLUSTER_COUNT_Y; ++j)
+ for (i=0; i < STBCC__CLUSTER_COUNT_X; ++i)
+ g->cluster_dirty[j][i] = 0;
+ #endif
+
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; i += 8) {
+ unsigned char c = 0;
+ for (k=0; k < 8; ++k)
+ if (map[j*w + (i+k)] == 0)
+ c |= (1 << k);
+ g->map[j][i>>3] = c;
+ }
+ }
+
+ for (j=0; j < g->ch; ++j)
+ for (i=0; i < g->cw; ++i)
+ stbcc__build_clumps_for_cluster(g, i, j);
+
+ for (j=0; j < g->ch; ++j)
+ for (i=0; i < g->cw; ++i)
+ stbcc__build_all_connections_for_cluster(g, i, j);
+
+ stbcc__build_connected_components_for_clumps(g);
+
+ for (j=0; j < g->h; ++j)
+ for (i=0; i < g->w; ++i)
+ assert(g->clump_for_node[j][i] <= STBCC__NULL_CLUMPID);
+}
+
+
+static void stbcc__add_clump_connection(stbcc_grid *g, int x1, int y1, int x2, int y2)
+{
+ stbcc__cluster *cluster;
+ stbcc__clump *clump;
+
+ int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1);
+ int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1);
+ int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2);
+ int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2);
+
+ stbcc__clumpid c1 = g->clump_for_node[y1][x1];
+ stbcc__clumpid c2 = g->clump_for_node[y2][x2];
+
+ stbcc__relative_clumpid rc;
+
+ assert(cx1 != cx2 || cy1 != cy2);
+ assert(abs(cx1-cx2) + abs(cy1-cy2) == 1);
+
+ // add connection to c2 in c1
+
+ rc.clump_index = c2;
+ rc.cluster_dx = x2-x1;
+ rc.cluster_dy = y2-y1;
+
+ cluster = &g->cluster[cy1][cx1];
+ clump = &cluster->clump[c1];
+ assert(clump->num_adjacent <= clump->max_adjacent);
+ if (clump->num_adjacent == clump->max_adjacent)
+ g->cluster[cy1][cx1].rebuild_adjacency = 1;
+ else {
+ stbcc__relative_clumpid *adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index];
+ assert(clump->num_adjacent < STBCC__MAX_EXITS_PER_CLUMP);
+ assert(clump->adjacent_clump_list_index + clump->num_adjacent <= STBCC__CLUSTER_ADJACENCY_COUNT);
+ adj[clump->num_adjacent++] = rc;
+ }
+}
+
+static void stbcc__remove_clump_connection(stbcc_grid *g, int x1, int y1, int x2, int y2)
+{
+ stbcc__cluster *cluster;
+ stbcc__clump *clump;
+ stbcc__relative_clumpid *adj;
+ int i;
+
+ int cx1 = STBCC__CLUSTER_X_FOR_COORD_X(x1);
+ int cy1 = STBCC__CLUSTER_Y_FOR_COORD_Y(y1);
+ int cx2 = STBCC__CLUSTER_X_FOR_COORD_X(x2);
+ int cy2 = STBCC__CLUSTER_Y_FOR_COORD_Y(y2);
+
+ stbcc__clumpid c1 = g->clump_for_node[y1][x1];
+ stbcc__clumpid c2 = g->clump_for_node[y2][x2];
+
+ stbcc__relative_clumpid rc;
+
+ assert(cx1 != cx2 || cy1 != cy2);
+ assert(abs(cx1-cx2) + abs(cy1-cy2) == 1);
+
+ // add connection to c2 in c1
+
+ rc.clump_index = c2;
+ rc.cluster_dx = x2-x1;
+ rc.cluster_dy = y2-y1;
+
+ cluster = &g->cluster[cy1][cx1];
+ clump = &cluster->clump[c1];
+ adj = &cluster->adjacency_storage[clump->adjacent_clump_list_index];
+
+ for (i=0; i < clump->num_adjacent; ++i)
+ if (rc.clump_index == adj[i].clump_index &&
+ rc.cluster_dx == adj[i].cluster_dx &&
+ rc.cluster_dy == adj[i].cluster_dy)
+ break;
+
+ if (i < clump->num_adjacent)
+ adj[i] = adj[--clump->num_adjacent];
+ else
+ assert(0);
+}
+
+static void stbcc__add_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy)
+{
+ unsigned char connected[STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER/8] = { { 0 } };
+ int x = cx * STBCC__CLUSTER_SIZE_X;
+ int y = cy * STBCC__CLUSTER_SIZE_Y;
+ int step_x, step_y=0, i, j, k, n;
+
+ if (cx < 0 || cx >= g->cw || cy < 0 || cy >= g->ch)
+ return;
+
+ if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch)
+ return;
+
+ if (g->cluster[cy][cx].rebuild_adjacency)
+ return;
+
+ assert(abs(dx) + abs(dy) == 1);
+
+ if (dx == 1) {
+ i = STBCC__CLUSTER_SIZE_X-1;
+ j = 0;
+ step_x = 0;
+ step_y = 1;
+ n = STBCC__CLUSTER_SIZE_Y;
+ } else if (dx == -1) {
+ i = 0;
+ j = 0;
+ step_x = 0;
+ step_y = 1;
+ n = STBCC__CLUSTER_SIZE_Y;
+ } else if (dy == -1) {
+ i = 0;
+ j = 0;
+ step_x = 1;
+ step_y = 0;
+ n = STBCC__CLUSTER_SIZE_X;
+ } else if (dy == 1) {
+ i = 0;
+ j = STBCC__CLUSTER_SIZE_Y-1;
+ step_x = 1;
+ step_y = 0;
+ n = STBCC__CLUSTER_SIZE_X;
+ } else {
+ assert(0);
+ return;
+ }
+
+ for (k=0; k < n; ++k) {
+ if (STBCC__MAP_OPEN(g, x+i, y+j) && STBCC__MAP_OPEN(g, x+i+dx, y+j+dy)) {
+ stbcc__clumpid src = g->clump_for_node[y+j][x+i];
+ stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx];
+ if (0 == (connected[src][dest>>3] & (1 << (dest & 7)))) {
+ assert((dest>>3) < sizeof(connected));
+ connected[src][dest>>3] |= 1 << (dest & 7);
+ stbcc__add_clump_connection(g, x+i, y+j, x+i+dx, y+j+dy);
+ if (g->cluster[cy][cx].rebuild_adjacency)
+ break;
+ }
+ }
+ i += step_x;
+ j += step_y;
+ }
+}
+
+static void stbcc__remove_connections_to_adjacent_cluster(stbcc_grid *g, int cx, int cy, int dx, int dy)
+{
+ unsigned char disconnected[STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER][STBCC__MAX_EDGE_CLUMPS_PER_CLUSTER/8] = { { 0 } };
+ int x = cx * STBCC__CLUSTER_SIZE_X;
+ int y = cy * STBCC__CLUSTER_SIZE_Y;
+ int step_x, step_y=0, i, j, k, n;
+
+ if (cx < 0 || cx >= g->cw || cy < 0 || cy >= g->ch)
+ return;
+
+ if (cx+dx < 0 || cx+dx >= g->cw || cy+dy < 0 || cy+dy >= g->ch)
+ return;
+
+ assert(abs(dx) + abs(dy) == 1);
+
+ if (dx == 1) {
+ i = STBCC__CLUSTER_SIZE_X-1;
+ j = 0;
+ step_x = 0;
+ step_y = 1;
+ n = STBCC__CLUSTER_SIZE_Y;
+ } else if (dx == -1) {
+ i = 0;
+ j = 0;
+ step_x = 0;
+ step_y = 1;
+ n = STBCC__CLUSTER_SIZE_Y;
+ } else if (dy == -1) {
+ i = 0;
+ j = 0;
+ step_x = 1;
+ step_y = 0;
+ n = STBCC__CLUSTER_SIZE_X;
+ } else if (dy == 1) {
+ i = 0;
+ j = STBCC__CLUSTER_SIZE_Y-1;
+ step_x = 1;
+ step_y = 0;
+ n = STBCC__CLUSTER_SIZE_X;
+ } else {
+ assert(0);
+ return;
+ }
+
+ for (k=0; k < n; ++k) {
+ if (STBCC__MAP_OPEN(g, x+i, y+j) && STBCC__MAP_OPEN(g, x+i+dx, y+j+dy)) {
+ stbcc__clumpid src = g->clump_for_node[y+j][x+i];
+ stbcc__clumpid dest = g->clump_for_node[y+j+dy][x+i+dx];
+ if (0 == (disconnected[src][dest>>3] & (1 << (dest & 7)))) {
+ disconnected[src][dest>>3] |= 1 << (dest & 7);
+ stbcc__remove_clump_connection(g, x+i, y+j, x+i+dx, y+j+dy);
+ }
+ }
+ i += step_x;
+ j += step_y;
+ }
+}
+
+static stbcc__tinypoint stbcc__incluster_find(stbcc__cluster_build_info *cbi, int x, int y)
+{
+ stbcc__tinypoint p,q;
+ p = cbi->parent[y][x];
+ if (p.x == x && p.y == y)
+ return p;
+ q = stbcc__incluster_find(cbi, p.x, p.y);
+ cbi->parent[y][x] = q;
+ return q;
+}
+
+static void stbcc__incluster_union(stbcc__cluster_build_info *cbi, int x1, int y1, int x2, int y2)
+{
+ stbcc__tinypoint p = stbcc__incluster_find(cbi, x1,y1);
+ stbcc__tinypoint q = stbcc__incluster_find(cbi, x2,y2);
+
+ if (p.x == q.x && p.y == q.y)
+ return;
+
+ cbi->parent[p.y][p.x] = q;
+}
+
+static void stbcc__switch_root(stbcc__cluster_build_info *cbi, int x, int y, stbcc__tinypoint p)
+{
+ cbi->parent[p.y][p.x].x = x;
+ cbi->parent[p.y][p.x].y = y;
+ cbi->parent[y][x].x = x;
+ cbi->parent[y][x].y = y;
+}
+
+static void stbcc__build_clumps_for_cluster(stbcc_grid *g, int cx, int cy)
+{
+ stbcc__cluster *c;
+ stbcc__cluster_build_info cbi;
+ int label=0;
+ int i,j;
+ int x = cx * STBCC__CLUSTER_SIZE_X;
+ int y = cy * STBCC__CLUSTER_SIZE_Y;
+
+ // set initial disjoint set forest state
+ for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) {
+ for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) {
+ cbi.parent[j][i].x = i;
+ cbi.parent[j][i].y = j;
+ }
+ }
+
+ // join all sets that are connected
+ for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) {
+ // check down only if not on bottom row
+ if (j < STBCC__CLUSTER_SIZE_Y-1)
+ for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i)
+ if (STBCC__MAP_OPEN(g,x+i,y+j) && STBCC__MAP_OPEN(g,x+i ,y+j+1))
+ stbcc__incluster_union(&cbi, i,j, i,j+1);
+ // check right for everything but rightmost column
+ for (i=0; i < STBCC__CLUSTER_SIZE_X-1; ++i)
+ if (STBCC__MAP_OPEN(g,x+i,y+j) && STBCC__MAP_OPEN(g,x+i+1,y+j ))
+ stbcc__incluster_union(&cbi, i,j, i+1,j);
+ }
+
+ // label all non-empty clumps along edges so that all edge clumps are first
+ // in list; this means in degenerate case we can skip traversing non-edge clumps.
+ // because in the first pass we only label leaders, we swap the leader to the
+ // edge first
+
+ // first put solid labels on all the edges; these will get overwritten if they're open
+ for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j)
+ cbi.label[j][0] = cbi.label[j][STBCC__CLUSTER_SIZE_X-1] = STBCC__NULL_CLUMPID;
+ for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i)
+ cbi.label[0][i] = cbi.label[STBCC__CLUSTER_SIZE_Y-1][i] = STBCC__NULL_CLUMPID;
+
+ for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) {
+ i = 0;
+ if (STBCC__MAP_OPEN(g, x+i, y+j)) {
+ stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
+ if (p.x == i && p.y == j)
+ // if this is the leader, give it a label
+ cbi.label[j][i] = label++;
+ else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) {
+ // if leader is in interior, promote this edge node to leader and label
+ stbcc__switch_root(&cbi, i, j, p);
+ cbi.label[j][i] = label++;
+ }
+ // else if leader is on edge, do nothing (it'll get labelled when we reach it)
+ }
+ i = STBCC__CLUSTER_SIZE_X-1;
+ if (STBCC__MAP_OPEN(g, x+i, y+j)) {
+ stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
+ if (p.x == i && p.y == j)
+ cbi.label[j][i] = label++;
+ else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) {
+ stbcc__switch_root(&cbi, i, j, p);
+ cbi.label[j][i] = label++;
+ }
+ }
+ }
+
+ for (i=1; i < STBCC__CLUSTER_SIZE_Y-1; ++i) {
+ j = 0;
+ if (STBCC__MAP_OPEN(g, x+i, y+j)) {
+ stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
+ if (p.x == i && p.y == j)
+ cbi.label[j][i] = label++;
+ else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) {
+ stbcc__switch_root(&cbi, i, j, p);
+ cbi.label[j][i] = label++;
+ }
+ }
+ j = STBCC__CLUSTER_SIZE_Y-1;
+ if (STBCC__MAP_OPEN(g, x+i, y+j)) {
+ stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
+ if (p.x == i && p.y == j)
+ cbi.label[j][i] = label++;
+ else if (!(p.x == 0 || p.x == STBCC__CLUSTER_SIZE_X-1 || p.y == 0 || p.y == STBCC__CLUSTER_SIZE_Y-1)) {
+ stbcc__switch_root(&cbi, i, j, p);
+ cbi.label[j][i] = label++;
+ }
+ }
+ }
+
+ c = &g->cluster[cy][cx];
+ c->num_edge_clumps = label;
+
+ // label any internal clusters
+ for (j=1; j < STBCC__CLUSTER_SIZE_Y-1; ++j) {
+ for (i=1; i < STBCC__CLUSTER_SIZE_X-1; ++i) {
+ stbcc__tinypoint p = cbi.parent[j][i];
+ if (p.x == i && p.y == j) {
+ if (STBCC__MAP_OPEN(g,x+i,y+j))
+ cbi.label[j][i] = label++;
+ else
+ cbi.label[j][i] = STBCC__NULL_CLUMPID;
+ }
+ }
+ }
+
+ // label all other nodes
+ for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j) {
+ for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) {
+ stbcc__tinypoint p = stbcc__incluster_find(&cbi, i,j);
+ if (p.x != i || p.y != j) {
+ if (STBCC__MAP_OPEN(g,x+i,y+j))
+ cbi.label[j][i] = cbi.label[p.y][p.x];
+ }
+ if (STBCC__MAP_OPEN(g,x+i,y+j))
+ assert(cbi.label[j][i] != STBCC__NULL_CLUMPID);
+ }
+ }
+
+ c->num_clumps = label;
+
+ for (i=0; i < label; ++i) {
+ c->clump[i].num_adjacent = 0;
+ c->clump[i].max_adjacent = 0;
+ }
+
+ for (j=0; j < STBCC__CLUSTER_SIZE_Y; ++j)
+ for (i=0; i < STBCC__CLUSTER_SIZE_X; ++i) {
+ g->clump_for_node[y+j][x+i] = cbi.label[j][i]; // @OPTIMIZE: remove cbi.label entirely
+ assert(g->clump_for_node[y+j][x+i] <= STBCC__NULL_CLUMPID);
+ }
+
+ // set the global label for all interior clumps since they can't have connections,
+ // so we don't have to do this on the global pass (brings from O(N) to O(N^0.75))
+ for (i=(int) c->num_edge_clumps; i < (int) c->num_clumps; ++i) {
+ stbcc__global_clumpid gc;
+ gc.f.cluster_x = cx;
+ gc.f.cluster_y = cy;
+ gc.f.clump_index = i;
+ c->clump[i].global_label = gc;
+ }
+
+ c->rebuild_adjacency = 1; // flag that it has no valid adjacency data
+}
+
+#endif // STB_CONNECTED_COMPONENTS_IMPLEMENTATION
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+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 OR COPYRIGHT HOLDERS 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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+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.
+------------------------------------------------------------------------------
+*/
diff --git a/third-party/stb/stb_divide.h b/third-party/stb/stb_divide.h
new file mode 100644
index 0000000..6a51e3f
--- /dev/null
+++ b/third-party/stb/stb_divide.h
@@ -0,0 +1,433 @@
+// stb_divide.h - v0.94 - public domain - Sean Barrett, Feb 2010
+// Three kinds of divide/modulus of signed integers.
+//
+// HISTORY
+//
+// v0.94 Fix integer overflow issues
+// v0.93 2020-02-02 Write useful exit() value from main()
+// v0.92 2019-02-25 Fix warning
+// v0.91 2010-02-27 Fix euclidean division by INT_MIN for non-truncating C
+// Check result with 64-bit math to catch such cases
+// v0.90 2010-02-24 First public release
+//
+// USAGE
+//
+// In *ONE* source file, put:
+//
+// #define STB_DIVIDE_IMPLEMENTATION
+// // #define C_INTEGER_DIVISION_TRUNCATES // see Note 1
+// // #define C_INTEGER_DIVISION_FLOORS // see Note 2
+// #include "stb_divide.h"
+//
+// Other source files should just include stb_divide.h
+//
+// Note 1: On platforms/compilers that you know signed C division
+// truncates, you can #define C_INTEGER_DIVISION_TRUNCATES.
+//
+// Note 2: On platforms/compilers that you know signed C division
+// floors (rounds to negative infinity), you can #define
+// C_INTEGER_DIVISION_FLOORS.
+//
+// You can #define STB_DIVIDE_TEST in which case the implementation
+// will generate a main() and compiling the result will create a
+// program that tests the implementation. Run it with no arguments
+// and any output indicates an error; run it with any argument and
+// it will also print the test results. Define STB_DIVIDE_TEST_64
+// to a 64-bit integer type to avoid overflows in the result-checking
+// which give false negatives.
+//
+// ABOUT
+//
+// This file provides three different consistent divide/mod pairs
+// implemented on top of arbitrary C/C++ division, including correct
+// handling of overflow of intermediate calculations:
+//
+// trunc: a/b truncates to 0, a%b has same sign as a
+// floor: a/b truncates to -inf, a%b has same sign as b
+// eucl: a/b truncates to sign(b)*inf, a%b is non-negative
+//
+// Not necessarily optimal; I tried to keep it generally efficient,
+// but there may be better ways.
+//
+// Briefly, for those who are not familiar with the problem, we note
+// the reason these divides exist and are interesting:
+//
+// 'trunc' is easy to implement in hardware (strip the signs,
+// compute, reapply the signs), thus is commonly defined
+// by many languages (including C99)
+//
+// 'floor' is simple to define and better behaved than trunc;
+// for example it divides integers into fixed-size buckets
+// without an extra-wide bucket at 0, and for a fixed
+// divisor N there are only |N| possible moduli.
+//
+// 'eucl' guarantees fixed-sized buckets *and* a non-negative
+// modulus and defines division to be whatever is needed
+// to achieve that result.
+//
+// See "The Euclidean definition of the functions div and mod"
+// by Raymond Boute (1992), or "Division and Modulus for Computer
+// Scientists" by Daan Leijen (2001)
+//
+// We assume of the built-in C division:
+// (a) modulus is the remainder for the corresponding division
+// (b) a/b truncates if a and b are the same sign
+//
+// Property (a) requires (a/b)*b + (a%b)==a, and is required by C.
+// Property (b) seems to be true of all hardware but is *not* satisfied
+// by the euclidean division operator we define, so it's possibly not
+// always true. If any such platform turns up, we can add more cases.
+// (Possibly only stb_div_trunc currently relies on property (b).)
+//
+// LICENSE
+//
+// See end of file for license information.
+
+
+#ifndef INCLUDE_STB_DIVIDE_H
+#define INCLUDE_STB_DIVIDE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int stb_div_trunc(int value_to_be_divided, int value_to_divide_by);
+extern int stb_div_floor(int value_to_be_divided, int value_to_divide_by);
+extern int stb_div_eucl (int value_to_be_divided, int value_to_divide_by);
+extern int stb_mod_trunc(int value_to_be_divided, int value_to_divide_by);
+extern int stb_mod_floor(int value_to_be_divided, int value_to_divide_by);
+extern int stb_mod_eucl (int value_to_be_divided, int value_to_divide_by);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef STB_DIVIDE_IMPLEMENTATION
+
+#if defined(__STDC_VERSION) && __STDC_VERSION__ >= 19901
+ #ifndef C_INTEGER_DIVISION_TRUNCATES
+ #define C_INTEGER_DIVISION_TRUNCATES
+ #endif
+#endif
+
+#ifndef INT_MIN
+#include // if you have no limits.h, #define INT_MIN yourself
+#endif
+
+// the following macros are designed to allow testing
+// other platforms by simulating them
+#ifndef STB_DIVIDE_TEST_FLOOR
+ #define stb__div(a,b) ((a)/(b))
+ #define stb__mod(a,b) ((a)%(b))
+#else
+ // implement floor-style divide on trunc platform
+ #ifndef C_INTEGER_DIVISION_TRUNCATES
+ #error "floor test requires truncating division"
+ #endif
+ #undef C_INTEGER_DIVISION_TRUNCATES
+ int stb__div(int v1, int v2)
+ {
+ int q = v1/v2, r = v1%v2;
+ if ((r > 0 && v2 < 0) || (r < 0 && v2 > 0))
+ return q-1;
+ else
+ return q;
+ }
+
+ int stb__mod(int v1, int v2)
+ {
+ int r = v1%v2;
+ if ((r > 0 && v2 < 0) || (r < 0 && v2 > 0))
+ return r+v2;
+ else
+ return r;
+ }
+#endif
+
+int stb_div_trunc(int v1, int v2)
+{
+ #ifdef C_INTEGER_DIVISION_TRUNCATES
+ return v1/v2;
+ #else
+ if (v1 >= 0 && v2 <= 0)
+ return -stb__div(-v1,v2); // both negative to avoid overflow
+ if (v1 <= 0 && v2 >= 0)
+ if (v1 != INT_MIN)
+ return -stb__div(v1,-v2); // both negative to avoid overflow
+ else
+ return -stb__div(v1+v2,-v2)-1; // push v1 away from wrap point
+ else
+ return v1/v2; // same sign, so expect truncation
+ #endif
+}
+
+int stb_div_floor(int v1, int v2)
+{
+ #ifdef C_INTEGER_DIVISION_FLOORS
+ return v1/v2;
+ #else
+ if (v1 >= 0 && v2 < 0) {
+ if (v2 + 1 >= INT_MIN + v1) // check if increasing v1's magnitude overflows
+ return -stb__div((v2+1)-v1,v2); // nope, so just compute it
+ else
+ return -stb__div(-v1,v2) + ((-v1)%v2 ? -1 : 0);
+ }
+ if (v1 < 0 && v2 >= 0) {
+ if (v1 != INT_MIN) {
+ if (v1 + 1 >= INT_MIN + v2) // check if increasing v1's magnitude overflows
+ return -stb__div((v1+1)-v2,-v2); // nope, so just compute it
+ else
+ return -stb__div(-v1,v2) + (stb__mod(v1,-v2) ? -1 : 0);
+ } else // it must be possible to compute -(v1+v2) without overflowing
+ return -stb__div(-(v1+v2),v2) + (stb__mod(-(v1+v2),v2) ? -2 : -1);
+ } else
+ return v1/v2; // same sign, so expect truncation
+ #endif
+}
+
+int stb_div_eucl(int v1, int v2)
+{
+ int q,r;
+ #ifdef C_INTEGER_DIVISION_TRUNCATES
+ q = v1/v2;
+ r = v1%v2;
+ #else
+ // handle every quadrant separately, since we can't rely on q and r flor
+ if (v1 >= 0)
+ if (v2 >= 0)
+ return stb__div(v1,v2);
+ else if (v2 != INT_MIN)
+ q = -stb__div(v1,-v2), r = stb__mod(v1,-v2);
+ else
+ q = 0, r = v1;
+ else if (v1 != INT_MIN)
+ if (v2 >= 0)
+ q = -stb__div(-v1,v2), r = -stb__mod(-v1,v2);
+ else if (v2 != INT_MIN)
+ q = stb__div(-v1,-v2), r = -stb__mod(-v1,-v2);
+ else // if v2 is INT_MIN, then we can't use -v2, but we can't divide by v2
+ q = 1, r = v1-q*v2;
+ else // if v1 is INT_MIN, we have to move away from overflow place
+ if (v2 >= 0)
+ q = -stb__div(-(v1+v2),v2)-1, r = -stb__mod(-(v1+v2),v2);
+ else if (v2 != INT_MIN)
+ q = stb__div(-(v1-v2),-v2)+1, r = -stb__mod(-(v1-v2),-v2);
+ else // for INT_MIN / INT_MIN, we need to be extra-careful to avoid overflow
+ q = 1, r = 0;
+ #endif
+ if (r >= 0)
+ return q;
+ else
+ return q + (v2 > 0 ? -1 : 1);
+}
+
+int stb_mod_trunc(int v1, int v2)
+{
+ #ifdef C_INTEGER_DIVISION_TRUNCATES
+ return v1%v2;
+ #else
+ if (v1 >= 0) { // modulus result should always be positive
+ int r = stb__mod(v1,v2);
+ if (r >= 0)
+ return r;
+ else
+ return r - (v2 < 0 ? v2 : -v2);
+ } else { // modulus result should always be negative
+ int r = stb__mod(v1,v2);
+ if (r <= 0)
+ return r;
+ else
+ return r + (v2 < 0 ? v2 : -v2);
+ }
+ #endif
+}
+
+int stb_mod_floor(int v1, int v2)
+{
+ #ifdef C_INTEGER_DIVISION_FLOORS
+ return v1%v2;
+ #else
+ if (v2 >= 0) { // result should always be positive
+ int r = stb__mod(v1,v2);
+ if (r >= 0)
+ return r;
+ else
+ return r + v2;
+ } else { // result should always be negative
+ int r = stb__mod(v1,v2);
+ if (r <= 0)
+ return r;
+ else
+ return r + v2;
+ }
+ #endif
+}
+
+int stb_mod_eucl(int v1, int v2)
+{
+ int r = stb__mod(v1,v2);
+
+ if (r >= 0)
+ return r;
+ else
+ return r - (v2 < 0 ? v2 : -v2); // negative abs() [to avoid overflow]
+}
+
+#ifdef STB_DIVIDE_TEST
+#include
+#include
+#include
+
+int show=0;
+int err=0;
+
+void stbdiv_check(int q, int r, int a, int b, char *type, int dir)
+{
+ if ((dir > 0 && r < 0) || (dir < 0 && r > 0)) {
+ fprintf(stderr, "FAILED: %s(%d,%d) remainder %d in wrong direction\n", type,a,b,r);
+ err++;
+ } else
+ if (b != INT_MIN) // can't compute abs(), but if b==INT_MIN all remainders are valid
+ if (r <= -abs(b) || r >= abs(b)) {
+ fprintf(stderr, "FAILED: %s(%d,%d) remainder %d out of range\n", type,a,b,r);
+ err++;
+ }
+ #ifdef STB_DIVIDE_TEST_64
+ {
+ STB_DIVIDE_TEST_64 q64 = q, r64=r, a64=a, b64=b;
+ if (q64*b64+r64 != a64) {
+ fprintf(stderr, "FAILED: %s(%d,%d) remainder %d doesn't match quotient %d\n", type,a,b,r,q);
+ err++;
+ }
+ }
+ #else
+ if (q*b+r != a) {
+ fprintf(stderr, "FAILED: %s(%d,%d) remainder %d doesn't match quotient %d\n", type,a,b,r,q);
+ err++;
+ }
+ #endif
+}
+
+void test(int a, int b)
+{
+ int q,r;
+ if (show) printf("(%+11d,%+d) | ", a,b);
+ q = stb_div_trunc(a,b), r = stb_mod_trunc(a,b);
+ if (show) printf("(%+11d,%+2d) ", q,r); stbdiv_check(q,r,a,b, "trunc",a);
+ q = stb_div_floor(a,b), r = stb_mod_floor(a,b);
+ if (show) printf("(%+11d,%+2d) ", q,r); stbdiv_check(q,r,a,b, "floor",b);
+ q = stb_div_eucl (a,b), r = stb_mod_eucl (a,b);
+ if (show) printf("(%+11d,%+2d)\n", q,r); stbdiv_check(q,r,a,b, "euclidean",1);
+}
+
+void testh(int a, int b)
+{
+ int q,r;
+ if (show) printf("(%08x,%08x) |\n", a,b);
+ q = stb_div_trunc(a,b), r = stb_mod_trunc(a,b); stbdiv_check(q,r,a,b, "trunc",a);
+ if (show) printf(" (%08x,%08x)", q,r);
+ q = stb_div_floor(a,b), r = stb_mod_floor(a,b); stbdiv_check(q,r,a,b, "floor",b);
+ if (show) printf(" (%08x,%08x)", q,r);
+ q = stb_div_eucl (a,b), r = stb_mod_eucl (a,b); stbdiv_check(q,r,a,b, "euclidean",1);
+ if (show) printf(" (%08x,%08x)\n ", q,r);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc > 1) show=1;
+
+ test(8,3);
+ test(8,-3);
+ test(-8,3);
+ test(-8,-3);
+ test(1,2);
+ test(1,-2);
+ test(-1,2);
+ test(-1,-2);
+ test(8,4);
+ test(8,-4);
+ test(-8,4);
+ test(-8,-4);
+
+ test(INT_MAX,1);
+ test(INT_MIN,1);
+ test(INT_MIN+1,1);
+ test(INT_MAX,-1);
+ //test(INT_MIN,-1); // this traps in MSVC, so we leave it untested
+ test(INT_MIN+1,-1);
+ test(INT_MIN,-2);
+ test(INT_MIN+1,2);
+ test(INT_MIN+1,-2);
+ test(INT_MAX,2);
+ test(INT_MAX,-2);
+ test(INT_MIN+1,2);
+ test(INT_MIN+1,-2);
+ test(INT_MIN,2);
+ test(INT_MIN,-2);
+ test(INT_MIN,7);
+ test(INT_MIN,-7);
+ test(INT_MIN+1,4);
+ test(INT_MIN+1,-4);
+
+ testh(-7, INT_MIN);
+ testh(-1, INT_MIN);
+ testh(1, INT_MIN);
+ testh(7, INT_MIN);
+
+ testh(INT_MAX-1, INT_MIN);
+ testh(INT_MAX, INT_MIN);
+ testh(INT_MIN, INT_MIN);
+ testh(INT_MIN+1, INT_MIN);
+
+ testh(INT_MAX-1, INT_MAX);
+ testh(INT_MAX , INT_MAX);
+ testh(INT_MIN , INT_MAX);
+ testh(INT_MIN+1, INT_MAX);
+
+ return err > 0 ? 1 : 0;
+}
+#endif // STB_DIVIDE_TEST
+#endif // STB_DIVIDE_IMPLEMENTATION
+#endif // INCLUDE_STB_DIVIDE_H
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+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 OR COPYRIGHT HOLDERS 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.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+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.
+------------------------------------------------------------------------------
+*/
diff --git a/third-party/stb/stb_ds.h b/third-party/stb/stb_ds.h
new file mode 100644
index 0000000..e84c82d
--- /dev/null
+++ b/third-party/stb/stb_ds.h
@@ -0,0 +1,1895 @@
+/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019
+
+ This is a single-header-file library that provides easy-to-use
+ dynamic arrays and hash tables for C (also works in C++).
+
+ For a gentle introduction:
+ http://nothings.org/stb_ds
+
+ To use this library, do this in *one* C or C++ file:
+ #define STB_DS_IMPLEMENTATION
+ #include "stb_ds.h"
+
+TABLE OF CONTENTS
+
+ Table of Contents
+ Compile-time options
+ License
+ Documentation
+ Notes
+ Notes - Dynamic arrays
+ Notes - Hash maps
+ Credits
+
+COMPILE-TIME OPTIONS
+
+ #define STBDS_NO_SHORT_NAMES
+
+ This flag needs to be set globally.
+
+ By default stb_ds exposes shorter function names that are not qualified
+ with the "stbds_" prefix. If these names conflict with the names in your
+ code, define this flag.
+
+ #define STBDS_SIPHASH_2_4
+
+ This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION.
+
+ By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for
+ 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force
+ stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes
+ hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on
+ 64-byte keys, and 10% slower on 256-byte keys on my test computer.
+
+ #define STBDS_REALLOC(context,ptr,size) better_realloc
+ #define STBDS_FREE(context,ptr) better_free
+
+ These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION.
+
+ By default stb_ds uses stdlib realloc() and free() for memory management. You can
+ substitute your own functions instead by defining these symbols. You must either
+ define both, or neither. Note that at the moment, 'context' will always be NULL.
+ @TODO add an array/hash initialization function that takes a memory context pointer.
+
+ #define STBDS_UNIT_TESTS
+
+ Defines a function stbds_unit_tests() that checks the functioning of the data structures.
+
+ Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x'
+ (or equivalentally '-std=c++11') when using anonymous structures as seen on the web
+ page or in STBDS_UNIT_TESTS.
+
+LICENSE
+
+ Placed in the public domain and also MIT licensed.
+ See end of file for detailed license information.
+
+DOCUMENTATION
+
+ Dynamic Arrays
+
+ Non-function interface:
+
+ Declare an empty dynamic array of type T
+ T* foo = NULL;
+
+ Access the i'th item of a dynamic array 'foo' of type T, T* foo:
+ foo[i]
+
+ Functions (actually macros)
+
+ arrfree:
+ void arrfree(T*);
+ Frees the array.
+
+ arrlen:
+ ptrdiff_t arrlen(T*);
+ Returns the number of elements in the array.
+
+ arrlenu:
+ size_t arrlenu(T*);
+ Returns the number of elements in the array as an unsigned type.
+
+ arrpop:
+ T arrpop(T* a)
+ Removes the final element of the array and returns it.
+
+ arrput:
+ T arrput(T* a, T b);
+ Appends the item b to the end of array a. Returns b.
+
+ arrins:
+ T arrins(T* a, int p, T b);
+ Inserts the item b into the middle of array a, into a[p],
+ moving the rest of the array over. Returns b.
+
+ arrinsn:
+ void arrinsn(T* a, int p, int n);
+ Inserts n uninitialized items into array a starting at a[p],
+ moving the rest of the array over.
+
+ arraddnptr:
+ T* arraddnptr(T* a, int n)
+ Appends n uninitialized items onto array at the end.
+ Returns a pointer to the first uninitialized item added.
+
+ arraddnindex:
+ size_t arraddnindex(T* a, int n)
+ Appends n uninitialized items onto array at the end.
+ Returns the index of the first uninitialized item added.
+
+ arrdel:
+ void arrdel(T* a, int p);
+ Deletes the element at a[p], moving the rest of the array over.
+
+ arrdeln:
+ void arrdeln(T* a, int p, int n);
+ Deletes n elements starting at a[p], moving the rest of the array over.
+
+ arrdelswap:
+ void arrdelswap(T* a, int p);
+ Deletes the element at a[p], replacing it with the element from
+ the end of the array. O(1) performance.
+
+ arrsetlen:
+ void arrsetlen(T* a, int n);
+ Changes the length of the array to n. Allocates uninitialized
+ slots at the end if necessary.
+
+ arrsetcap:
+ size_t arrsetcap(T* a, int n);
+ Sets the length of allocated storage to at least n. It will not
+ change the length of the array.
+
+ arrcap:
+ size_t arrcap(T* a);
+ Returns the number of total elements the array can contain without
+ needing to be reallocated.
+
+ Hash maps & String hash maps
+
+ Given T is a structure type: struct { TK key; TV value; }. Note that some
+ functions do not require TV value and can have other fields. For string
+ hash maps, TK must be 'char *'.
+
+ Special interface:
+
+ stbds_rand_seed:
+ void stbds_rand_seed(size_t seed);
+ For security against adversarially chosen data, you should seed the
+ library with a strong random number. Or at least seed it with time().
+
+ stbds_hash_string:
+ size_t stbds_hash_string(char *str, size_t seed);
+ Returns a hash value for a string.
+
+ stbds_hash_bytes:
+ size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
+ These functions hash an arbitrary number of bytes. The function
+ uses a custom hash for 4- and 8-byte data, and a weakened version
+ of SipHash for everything else. On 64-bit platforms you can get
+ specification-compliant SipHash-2-4 on all data by defining
+ STBDS_SIPHASH_2_4, at a significant cost in speed.
+
+ Non-function interface:
+
+ Declare an empty hash map of type T
+ T* foo = NULL;
+
+ Access the i'th entry in a hash table T* foo:
+ foo[i]
+
+ Function interface (actually macros):
+
+ hmfree
+ shfree
+ void hmfree(T*);
+ void shfree(T*);
+ Frees the hashmap and sets the pointer to NULL.
+
+ hmlen
+ shlen
+ ptrdiff_t hmlen(T*)
+ ptrdiff_t shlen(T*)
+ Returns the number of elements in the hashmap.
+
+ hmlenu
+ shlenu
+ size_t hmlenu(T*)
+ size_t shlenu(T*)
+ Returns the number of elements in the hashmap.
+
+ hmgeti
+ shgeti
+ hmgeti_ts
+ ptrdiff_t hmgeti(T*, TK key)
+ ptrdiff_t shgeti(T*, char* key)
+ ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar)
+ Returns the index in the hashmap which has the key 'key', or -1
+ if the key is not present.
+
+ hmget
+ hmget_ts
+ shget
+ TV hmget(T*, TK key)
+ TV shget(T*, char* key)
+ TV hmget_ts(T*, TK key, ptrdiff_t tempvar)
+ Returns the value corresponding to 'key' in the hashmap.
+ The structure must have a 'value' field
+
+ hmgets
+ shgets
+ T hmgets(T*, TK key)
+ T shgets(T*, char* key)
+ Returns the structure corresponding to 'key' in the hashmap.
+
+ hmgetp
+ shgetp
+ hmgetp_ts
+ hmgetp_null
+ shgetp_null
+ T* hmgetp(T*, TK key)
+ T* shgetp(T*, char* key)
+ T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar)
+ T* hmgetp_null(T*, TK key)
+ T* shgetp_null(T*, char *key)
+ Returns a pointer to the structure corresponding to 'key' in
+ the hashmap. Functions ending in "_null" return NULL if the key
+ is not present in the hashmap; the others return a pointer to a
+ structure holding the default value (but not the searched-for key).
+
+ hmdefault
+ shdefault
+ TV hmdefault(T*, TV value)
+ TV shdefault(T*, TV value)
+ Sets the default value for the hashmap, the value which will be
+ returned by hmget/shget if the key is not present.
+
+ hmdefaults
+ shdefaults
+ TV hmdefaults(T*, T item)
+ TV shdefaults(T*, T item)
+ Sets the default struct for the hashmap, the contents which will be
+ returned by hmgets/shgets if the key is not present.
+
+ hmput
+ shput
+ TV hmput(T*, TK key, TV value)
+ TV shput(T*, char* key, TV value)
+ Inserts a pair into the hashmap. If the key is already
+ present in the hashmap, updates its value.
+
+ hmputs
+ shputs
+ T hmputs(T*, T item)
+ T shputs(T*, T item)
+ Inserts a struct with T.key into the hashmap. If the struct is already
+ present in the hashmap, updates it.
+
+ hmdel
+ shdel
+ int hmdel(T*, TK key)
+ int shdel(T*, char* key)
+ If 'key' is in the hashmap, deletes its entry and returns 1.
+ Otherwise returns 0.
+
+ Function interface (actually macros) for strings only:
+
+ sh_new_strdup
+ void sh_new_strdup(T*);
+ Overwrites the existing pointer with a newly allocated
+ string hashmap which will automatically allocate and free
+ each string key using realloc/free
+
+ sh_new_arena
+ void sh_new_arena(T*);
+ Overwrites the existing pointer with a newly allocated
+ string hashmap which will automatically allocate each string
+ key to a string arena. Every string key ever used by this
+ hash table remains in the arena until the arena is freed.
+ Additionally, any key which is deleted and reinserted will
+ be allocated multiple times in the string arena.
+
+NOTES
+
+ * These data structures are realloc'd when they grow, and the macro
+ "functions" write to the provided pointer. This means: (a) the pointer
+ must be an lvalue, and (b) the pointer to the data structure is not
+ stable, and you must maintain it the same as you would a realloc'd
+ pointer. For example, if you pass a pointer to a dynamic array to a
+ function which updates it, the function must return back the new
+ pointer to the caller. This is the price of trying to do this in C.
+
+ * The following are the only functions that are thread-safe on a single data
+ structure, i.e. can be run in multiple threads simultaneously on the same
+ data structure
+ hmlen shlen
+ hmlenu shlenu
+ hmget_ts shget_ts
+ hmgeti_ts shgeti_ts
+ hmgets_ts shgets_ts
+
+ * You iterate over the contents of a dynamic array and a hashmap in exactly
+ the same way, using arrlen/hmlen/shlen:
+
+ for (i=0; i < arrlen(foo); ++i)
+ ... foo[i] ...
+
+ * All operations except arrins/arrdel are O(1) amortized, but individual
+ operations can be slow, so these data structures may not be suitable
+ for real time use. Dynamic arrays double in capacity as needed, so
+ elements are copied an average of once. Hash tables double/halve
+ their size as needed, with appropriate hysteresis to maintain O(1)
+ performance.
+
+NOTES - DYNAMIC ARRAY
+
+ * If you know how long a dynamic array is going to be in advance, you can avoid
+ extra memory allocations by using arrsetlen to allocate it to that length in
+ advance and use foo[n] while filling it out, or arrsetcap to allocate the memory
+ for that length and use arrput/arrpush as normal.
+
+ * Unlike some other versions of the dynamic array, this version should
+ be safe to use with strict-aliasing optimizations.
+
+NOTES - HASH MAP
+
+ * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel
+ and variants, the key must be an lvalue (so the macro can take the address of it).
+ Extensions are used that eliminate this requirement if you're using C99 and later
+ in GCC or clang, or if you're using C++ in GCC. But note that this can make your
+ code less portable.
+
+ * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'.
+
+ * The iteration order of your data in the hashmap is determined solely by the
+ order of insertions and deletions. In particular, if you never delete, new
+ keys are always added at the end of the array. This will be consistent
+ across all platforms and versions of the library. However, you should not
+ attempt to serialize the internal hash table, as the hash is not consistent
+ between different platforms, and may change with future versions of the library.
+
+ * Use sh_new_arena() for string hashmaps that you never delete from. Initialize
+ with NULL if you're managing the memory for your strings, or your strings are
+ never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup().
+ @TODO: make an arena variant that garbage collects the strings with a trivial
+ copy collector into a new arena whenever the table shrinks / rebuilds. Since
+ current arena recommendation is to only use arena if it never deletes, then
+ this can just replace current arena implementation.
+
+ * If adversarial input is a serious concern and you're on a 64-bit platform,
+ enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass
+ a strong random number to stbds_rand_seed.
+
+ * The default value for the hash table is stored in foo[-1], so if you
+ use code like 'hmget(T,k)->value = 5' you can accidentally overwrite
+ the value stored by hmdefault if 'k' is not present.
+
+CREDITS
+
+ Sean Barrett -- library, idea for dynamic array API/implementation
+ Per Vognsen -- idea for hash table API/implementation
+ Rafael Sachetto -- arrpop()
+ github:HeroicKatora -- arraddn() reworking
+
+ Bugfixes:
+ Andy Durdin
+ Shane Liesegang
+ Vinh Truong
+ Andreas Molzer
+ github:hashitaku
+ github:srdjanstipic
+ Macoy Madson
+ Andreas Vennstrom
+ Tobias Mansfield-Williams
+*/
+
+#ifdef STBDS_UNIT_TESTS
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#ifndef INCLUDE_STB_DS_H
+#define INCLUDE_STB_DS_H
+
+#include
+#include
+
+#ifndef STBDS_NO_SHORT_NAMES
+#define arrlen stbds_arrlen
+#define arrlenu stbds_arrlenu
+#define arrput stbds_arrput
+#define arrpush stbds_arrput
+#define arrpop stbds_arrpop
+#define arrfree stbds_arrfree
+#define arraddn stbds_arraddn // deprecated, use one of the following instead:
+#define arraddnptr stbds_arraddnptr
+#define arraddnindex stbds_arraddnindex
+#define arrsetlen stbds_arrsetlen
+#define arrlast stbds_arrlast
+#define arrins stbds_arrins
+#define arrinsn stbds_arrinsn
+#define arrdel stbds_arrdel
+#define arrdeln stbds_arrdeln
+#define arrdelswap stbds_arrdelswap
+#define arrcap stbds_arrcap
+#define arrsetcap stbds_arrsetcap
+
+#define hmput stbds_hmput
+#define hmputs stbds_hmputs
+#define hmget stbds_hmget
+#define hmget_ts stbds_hmget_ts
+#define hmgets stbds_hmgets
+#define hmgetp stbds_hmgetp
+#define hmgetp_ts stbds_hmgetp_ts
+#define hmgetp_null stbds_hmgetp_null
+#define hmgeti stbds_hmgeti
+#define hmgeti_ts stbds_hmgeti_ts
+#define hmdel stbds_hmdel
+#define hmlen stbds_hmlen
+#define hmlenu stbds_hmlenu
+#define hmfree stbds_hmfree
+#define hmdefault stbds_hmdefault
+#define hmdefaults stbds_hmdefaults
+
+#define shput stbds_shput
+#define shputi stbds_shputi
+#define shputs stbds_shputs
+#define shget stbds_shget
+#define shgeti stbds_shgeti
+#define shgets stbds_shgets
+#define shgetp stbds_shgetp
+#define shgetp_null stbds_shgetp_null
+#define shdel stbds_shdel
+#define shlen stbds_shlen
+#define shlenu stbds_shlenu
+#define shfree stbds_shfree
+#define shdefault stbds_shdefault
+#define shdefaults stbds_shdefaults
+#define sh_new_arena stbds_sh_new_arena
+#define sh_new_strdup stbds_sh_new_strdup
+
+#define stralloc stbds_stralloc
+#define strreset stbds_strreset
+#endif
+
+#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE)
+#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither."
+#endif
+#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE)
+#include
+#define STBDS_REALLOC(c,p,s) realloc(p,s)
+#define STBDS_FREE(c,p) free(p)
+#endif
+
+#ifdef _MSC_VER
+#define STBDS_NOTUSED(v) (void)(v)
+#else
+#define STBDS_NOTUSED(v) (void)sizeof(v)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// for security against attackers, seed the library with a random number, at least time() but stronger is better
+extern void stbds_rand_seed(size_t seed);
+
+// these are the hash functions used internally if you want to test them or use them for other purposes
+extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
+extern size_t stbds_hash_string(char *str, size_t seed);
+
+// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'.
+typedef struct stbds_string_arena stbds_string_arena;
+extern char * stbds_stralloc(stbds_string_arena *a, char *str);
+extern void stbds_strreset(stbds_string_arena *a);
+
+// have to #define STBDS_UNIT_TESTS to call this
+extern void stbds_unit_tests(void);
+
+///////////////
+//
+// Everything below here is implementation details
+//
+
+extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap);
+extern void stbds_arrfreef(void *a);
+extern void stbds_hmfree_func(void *p, size_t elemsize);
+extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
+extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode);
+extern void * stbds_hmput_default(void *a, size_t elemsize);
+extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
+extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode);
+extern void * stbds_shmode_func(size_t elemsize, int mode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+#define STBDS_HAS_TYPEOF
+#ifdef __cplusplus
+//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang
+#endif
+#endif
+
+#if !defined(__cplusplus)
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+#define STBDS_HAS_LITERAL_ARRAY
+#endif
+#endif
+
+// this macro takes the address of the argument, but on gcc/clang can accept rvalues
+#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF)
+ #if __clang__
+ #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value
+ #else
+ #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value
+ #endif
+#else
+#define STBDS_ADDRESSOF(typevar, value) &(value)
+#endif
+
+#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var))
+
+#define stbds_header(t) ((stbds_array_header *) (t) - 1)
+#define stbds_temp(t) stbds_header(t)->temp
+#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table)
+
+#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n))
+#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0)
+#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0)
+#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0)
+#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0)
+#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v))
+#define stbds_arrpush stbds_arrput // synonym
+#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length])
+#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead:
+#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a))
+#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a))
+#define stbds_arraddnoff stbds_arraddnindex
+#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1])
+#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL)
+#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1)
+#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n))
+#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1)
+#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i))))
+#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v))
+
+#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \
+ ? (stbds_arrgrow(a,n,0),0) : 0)
+
+#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c)))
+
+#define stbds_hmput(t, k, v) \
+ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \
+ (t)[stbds_temp((t)-1)].key = (k), \
+ (t)[stbds_temp((t)-1)].value = (v))
+
+#define stbds_hmputs(t, s) \
+ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \
+ (t)[stbds_temp((t)-1)] = (s))
+
+#define stbds_hmgeti(t,k) \
+ ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \
+ stbds_temp((t)-1))
+
+#define stbds_hmgeti_ts(t,k,temp) \
+ ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \
+ (temp))
+
+#define stbds_hmgetp(t, k) \
+ ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)])
+
+#define stbds_hmgetp_ts(t, k, temp) \
+ ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp])
+
+#define stbds_hmdel(t,k) \
+ (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0)
+
+#define stbds_hmdefault(t, v) \
+ ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v))
+
+#define stbds_hmdefaults(t, s) \
+ ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s))
+
+#define stbds_hmfree(p) \
+ ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL)
+
+#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k))
+#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value)
+#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value)
+#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0)
+#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0)
+#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)])
+
+#define stbds_shput(t, k, v) \
+ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \
+ (t)[stbds_temp((t)-1)].value = (v))
+
+#define stbds_shputi(t, k, v) \
+ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \
+ (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1))
+
+#define stbds_shputs(t, s) \
+ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \
+ (t)[stbds_temp((t)-1)] = (s), \
+ (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally
+
+#define stbds_pshput(t, p) \
+ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \
+ (t)[stbds_temp((t)-1)] = (p))
+
+#define stbds_shgeti(t,k) \
+ ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \
+ stbds_temp((t)-1))
+
+#define stbds_pshgeti(t,k) \
+ ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \
+ stbds_temp((t)-1))
+
+#define stbds_shgetp(t, k) \
+ ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)])
+
+#define stbds_pshget(t, k) \
+ ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)])
+
+#define stbds_shdel(t,k) \
+ (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0)
+#define stbds_pshdel(t,k) \
+ (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0)
+
+#define stbds_sh_new_arena(t) \
+ ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA))
+#define stbds_sh_new_strdup(t) \
+ ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP))
+
+#define stbds_shdefault(t, v) stbds_hmdefault(t,v)
+#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s)
+
+#define stbds_shfree stbds_hmfree
+#define stbds_shlenu stbds_hmlenu
+
+#define stbds_shgets(t, k) (*stbds_shgetp(t,k))
+#define stbds_shget(t, k) (stbds_shgetp(t,k)->value)
+#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)])
+#define stbds_shlen stbds_hmlen
+
+typedef struct
+{
+ size_t length;
+ size_t capacity;
+ void * hash_table;
+ ptrdiff_t temp;
+} stbds_array_header;
+
+typedef struct stbds_string_block
+{
+ struct stbds_string_block *next;
+ char storage[8];
+} stbds_string_block;
+
+struct stbds_string_arena
+{
+ stbds_string_block *storage;
+ size_t remaining;
+ unsigned char block;
+ unsigned char mode; // this isn't used by the string arena itself
+};
+
+#define STBDS_HM_BINARY 0
+#define STBDS_HM_STRING 1
+
+enum
+{
+ STBDS_SH_NONE,
+ STBDS_SH_DEFAULT,
+ STBDS_SH_STRDUP,
+ STBDS_SH_ARENA
+};
+
+#ifdef __cplusplus
+// in C we use implicit assignment from these void*-returning functions to T*.
+// in C++ these templates make the same code work
+template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) {
+ return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap);
+}
+template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) {
+ return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode);
+}
+template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) {
+ return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode);
+}
+template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) {
+ return (T*)stbds_hmput_default((void *)a, elemsize);
+}
+template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) {
+ return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode);
+}
+template