pax_global_header00006660000000000000000000000064152315004760014515gustar00rootroot0000000000000052 comment=c080341ab2bc72c0574ffb5dc36203096b5465cb jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/000077500000000000000000000000001523150047600177755ustar00rootroot00000000000000jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/.gitignore000066400000000000000000000000471523150047600217660ustar00rootroot00000000000000*.exe jollygood cores/ objs/ /shaders/ jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/ChangeLog000066400000000000000000000053131523150047600215510ustar00rootroot00000000000000Version 2.0.1 ------------- - Build improvements - Fix possible segfaults when using core-allocated video buffers - Update miniz to 3.1.2 - Add 64DD detection - Show Vulkan renderer option only when actually enabled at build time Version 2.0.0 ------------- - Rewrite OSD and text rendering completely, add OSD background effects - Add pause function and hotkey - Define inputs by core, not by system, requiring inputs to be reconfigured - Add DIP Switch support - Add BIOS check and supported systems list to debug logs - Improve media swapping (CDs, Disks) - Fix inverted analog axes on macOS - Add experimental, but quite functional Vulkan renderer - Add license dump to see core licenses on the console Version 1.4.0 ------------- - Port to SDL3 - macOS compatibility fixes - Linux KMS improvements - Add Neo Geo CD detection Version 1.3.0 ------------- - Code and build cleanups - Allow pixel format changes during runtime - Modify rumble callback to allow stopping the rumble - Add N64 ROM detection - Rework input configuration to better handle triggers on diverse gamepads Version 1.2.2 ------------- - Build cleanup - Add NSF and My Vision detection - Add hotkey toggling for use with emulated keyboards Version 1.2.1 ------------- - Use calloc vs malloc in tconfig - Build improvements - Code quality improvement in path construction code - Add PC-FX detection - Improve core detection to allow loading the best installed core per system - Allow the emulated system to be specified on the command line Version 1.2.0 ------------- - Input configuration bugfixes - Fix bug with OSD text being captured in screenshots - Add OSD for OpenGL Compatibility Profile - Set OpenGL Core Profile version minimum from 3.3 to 3.1 - Code quality improvements in ezmenu - Improve handling of directory path creation and discovery - Support environment variables for core and asset directory search paths - Switch from parg to musl's getopt implemention for command line parsing - Clean up and improve command line parsing code Version 1.1.0 ------------- - Allow statically linking to Jolly Good cores to create standalone binaries - Use path relative to the current working directory when loading local assets - Build improvements - General code quality improvements - Prevent the menu system from being on the wrong level after input config Version 1.0.2 ------------- - Update to latest miniz - Change default Neo Geo emulator name - Differentiate first-boot resets from hard resets Version 1.0.1 ------------- - Documentation updates - Add detection for .neo format Neo Geo romsets - Fix potential buffer overflow in MD5 digest conversion to string Version 1.0.0 ------------- - Initial tagged release jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/LICENSE000066400000000000000000000027431523150047600210100ustar00rootroot00000000000000Copyright (c) 2020-2025 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/Makefile000066400000000000000000000175611523150047600214470ustar00rootroot00000000000000SOURCEDIR := $(abspath $(patsubst %/,%,$(dir $(abspath $(lastword \ $(MAKEFILE_LIST)))))) BUILD_STATIC ?= 0 ENABLE_VULKAN ?= 0 USE_EXTERNAL_MD5 ?= 0 LIBS_REQUIRES := DOCS := ChangeLog LICENSE README LINKER = $(CC) LIBS = -lm # Object dirs MKDIRS := deps include $(SOURCEDIR)/mk/common.mk FLAGS := -std=c99 $(WARNINGS_DEF_C) DEFINES := SHADERS_OUT := shaders SHADERS_SRC := $(wildcard $(SOURCEDIR)/src/shaders/*.fs \ $(SOURCEDIR)/src/shaders/*.vs) SHADERS := $(subst $(SOURCEDIR)/src/,,$(SHADERS_SRC)) VK_SHADERS := $(wildcard $(SOURCEDIR)/src/shaders/*.vert \ $(SOURCEDIR)/src/shaders/*.frag) VK_SHADERS_BASE = $(basename $(addprefix $(SOURCEDIR)/src/,$@)) VK_SHADERS_TARGET := $(addsuffix .spv,$(subst $(SOURCEDIR)/src/,,$(VK_SHADERS))) ifneq ($(BUILD_STATIC), 0) include $(BUILD_STATIC)/jg-static.mk BASE := $(NAME:%-jg=%) EXE := $(NAME)/$(NAME)$(BIN_EXT) CLEAN := $(NAME) TARGET := $(EXE) INSTALLDIR := $(NAME) SHARE_DEST := $(BASE) DEFINES += -DJGRF_STATIC LIBS += -L$(BUILD_STATIC) -l$(BASE)-jg $(LIBS_STATIC) ifneq ($(findstring $(CFLAGS_PTHREAD),$(LIBS_STATIC)),) PTHREAD := $(CFLAGS_PTHREAD) else PTHREAD := endif ifneq ($(ASSETS),) ASSETS_PATH := $(ASSETS:%=$(BUILD_STATIC)/%) ASSETS_TARGET := $(ASSETS:%=$(NAME)/%) TARGET += $(ASSETS_TARGET) endif DESKTOP_TARGET := $(NAME)/$(NAME).desktop ifneq ($(ICONS),) ICONS_DIR := $(BUILD_STATIC)/icons ICONS_NAME := $(BASE) ICONS_PATH := $(ICONS:%=$(ICONS_DIR)/%) else ICONS_DIR := $(SOURCEDIR)/icons ICONS_NAME := jollygood ICONS_PATH := $(wildcard $(ICONS_DIR)/*.png) \ $(ICONS_DIR)/$(ICONS_NAME).svg ICONS_BASE := $(notdir $(ICONS_PATH)) ICONS := $(subst $(ICONS_NAME),$(BASE),$(ICONS_BASE)) endif ICONS_SRC = $(subst icons/$(BASE),icons/$(ICONS_NAME),$@) ICONS_CPY = $(subst $(NAME)/icons,$(ICONS_DIR),$(ICONS_SRC)) ICONS_TARGET := $(ICONS:%=$(NAME)/icons/%) SHADERS_BASE := $(notdir $(SHADERS)) ifneq ($(ENABLE_VULKAN), 0) SHADERS_BASE += $(notdir $(VK_SHADERS_TARGET)) SHADERS_SRC += $(VK_SHADERS_TARGET) endif SHADERS_TARGET := $(SHADERS_BASE:%=$(NAME)/shaders/%) TARGET += $(DESKTOP_TARGET) $(ICONS_TARGET) $(SHADERS_TARGET) else NAME := jollygood BASE := $(NAME) EXE := $(NAME)$(BIN_EXT) CLEAN := $(EXE) TARGET := $(EXE) INSTALLDIR := $(SOURCEDIR) SHARE_DEST := jgrf PTHREAD := endif CSRCS := jgrf.c \ audio.c \ cheats.c \ cli.c \ detect.c \ dips.c \ input.c \ menu.c \ osd.c \ paths.c \ settings.c \ video.c \ video_gl.c \ deps/lodepng.c \ deps/musl_getopt.c \ deps/musl_memmem.c \ deps/parson.c \ deps/tconfig.c \ deps/wave_writer.c INCLUDES = -I$(DEPDIR) $(CFLAGS_JG) $(CFLAGS_EPOXY) $(CFLAGS_MINIZ) \ $(CFLAGS_SDL3) $(CFLAGS_SPEEXDSP) LIBS += $(LIBS_EPOXY) $(LIBS_MINIZ) $(LIBS_SDL3) $(LIBS_SPEEXDSP) ICONS_INSTALL_DIR = $(DATAROOTDIR)/icons/hicolor/$${i}x$${i}/apps SHADER_INSTALL_DIR := $(DATADIR)/jollygood/$(SHARE_DEST)/shaders TARGET += $(SHADERS) ifeq ($(USE_EXTERNAL_MD5), 0) CSRCS += deps/md5.c else DEFINES += -DHAVE_OPENSSL INCLUDES += $(CFLAGS_LIBCRYPTO) LIBS += $(LIBS_LIBCRYPTO) endif ifneq ($(ENABLE_VULKAN), 0) CSRCS += video_vk.c DEFINES += -DJGRF_VULKAN INCLUDES += $(CFLAGS_VULKAN) LIBS += $(LIBS_VULKAN) TARGET += glslang endif ifneq ($(PLATFORM), Darwin) LIBS += -Wl,--no-undefined endif ifneq ($(PLATFORM), Windows) DEFINES += -DDATADIR="\"$(DATADIR)\"" -DLIBDIR="\"$(LIBDIR)\"" FLAGS += -fvisibility=hidden endif # List of object files OBJS := $(patsubst %,$(OBJDIR)/%,$(CSRCS:.c=.o)) $(OBJS_MINIZ) # Compiler command COMPILE_C = $(strip $(CC) $(CFLAGS) $(PTHREAD) $(CPPFLAGS) $(1) -c $< -o $@) # Dependencies command BUILD_DEPS = $(call COMPILE_C, $(FLAGS)) # Glslang command BUILD_GLSLANG = $(GLSLANG) --quiet -V $(VK_SHADERS_BASE) -o $@ # Core command BUILD_MAIN = $(call COMPILE_C, $(FLAGS) $(DEFINES) $(INCLUDES)) .PHONY: $(PHONY) glslang install-bin install-data install-man all: $(TARGET) $(OBJDIR)/%.o: $(SOURCEDIR)/src/%.c $(PREREQ) $(call COMPILE_INFO,$(BUILD_MAIN)) @$(BUILD_MAIN) $(OBJDIR)/deps/%.o: $(DEPDIR)/%.c $(PREREQ) $(call COMPILE_INFO,$(BUILD_DEPS)) @$(BUILD_DEPS) $(EXE): $(OBJS) ifneq ($(BUILD_STATIC), 0) @mkdir -p $(NAME) endif $(strip $(LINKER) -o $@ $^ $(LDFLAGS) $(LIBS)) $(SHADERS_OUT)/.tag: @mkdir -p -- $(SHADERS_OUT) @touch $@ $(SHADERS): $(SHADERS_SRC) $(SHADERS_OUT)/.tag @cp $(addprefix $(SOURCEDIR)/src/,$@) $@ glslang: $(VK_SHADERS_TARGET) $(VK_SHADERS_TARGET): $(VK_SHADERS) $(SHADERS_OUT)/.tag $(call COMPILE_INFO,$(BUILD_GLSLANG)) @$(BUILD_GLSLANG) ifneq ($(BUILD_STATIC), 0) ifneq ($(ASSETS),) $(ASSETS_TARGET): $(ASSETS_PATH) @mkdir -p $(dir $@) @cp $(subst $(NAME),$(BUILD_STATIC),$@) $@ endif $(DESKTOP_TARGET): $(BUILD_STATIC)/$(NAME).desktop @mkdir -p $(NAME) @cp $< $@ $(ICONS_TARGET): $(ICONS_PATH) @mkdir -p $(NAME)/icons @cp $(ICONS_CPY) $(NAME)/icons/$(notdir $@) $(SHADERS_TARGET): $(SHADERS) @mkdir -p $(NAME)/shaders @cp $(subst $(NAME)/,,$@) $(NAME)/shaders endif clean: rm -rf $(OBJDIR) $(SHADERS_OUT) $(CLEAN) install-bin: all @mkdir -p $(DESTDIR)$(BINDIR) cp $(EXE) $(DESTDIR)$(BINDIR) install-data: all @mkdir -p $(DESTDIR)$(SHADER_INSTALL_DIR) @mkdir -p $(DESTDIR)$(DATAROOTDIR)/applications @mkdir -p $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/scalable/apps @mkdir -p $(DESTDIR)$(DATAROOTDIR)/pixmaps cp $(INSTALLDIR)/$(NAME).desktop $(DESTDIR)$(DATAROOTDIR)/applications cp $(SHADERS_OUT)/default.vs $(DESTDIR)$(SHADER_INSTALL_DIR) cp $(SHADERS_OUT)/default.fs $(DESTDIR)$(SHADER_INSTALL_DIR) cp $(SHADERS_OUT)/aann.fs $(DESTDIR)$(SHADER_INSTALL_DIR) cp $(SHADERS_OUT)/crt-yee64.fs $(DESTDIR)$(SHADER_INSTALL_DIR) cp $(SHADERS_OUT)/crtea.fs $(DESTDIR)$(SHADER_INSTALL_DIR) cp $(SHADERS_OUT)/lcd.fs $(DESTDIR)$(SHADER_INSTALL_DIR) cp $(SHADERS_OUT)/sharp-bilinear.fs $(DESTDIR)$(SHADER_INSTALL_DIR) ifneq ($(ENABLE_VULKAN), 0) for s in $(notdir $(VK_SHADERS_TARGET)); do \ cp $(SHADERS_OUT)/$$s $(DESTDIR)$(SHADER_INSTALL_DIR); \ done endif for i in 32 48 64 96 128 256 512 1024; do \ mkdir -p $(DESTDIR)$(ICONS_INSTALL_DIR); \ if test "$$i" = '96' || test "$$i" = '1024'; then \ cp $(INSTALLDIR)/icons/$(BASE)$$i.png \ $(DESTDIR)$(DATADIR)/jollygood/$(SHARE_DEST)/; \ fi; \ cp $(INSTALLDIR)/icons/$(BASE)$$i.png \ $(DESTDIR)$(ICONS_INSTALL_DIR)/$(BASE).png; \ done cp $(INSTALLDIR)/icons/$(BASE).svg $(DESTDIR)$(DATAROOTDIR)/pixmaps/ cp $(INSTALLDIR)/icons/$(BASE).svg \ $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/scalable/apps/ ifneq ($(BUILD_STATIC), 0) ifneq ($(ASSETS),) @mkdir -p $(DESTDIR)$(DATADIR)/jollygood/$(SHARE_DEST) for a in $(ASSETS_TARGET); do \ dir="$${a%/*}"; \ if test "$$dir" != "$(NAME)"; then \ dest="$(DATADIR)/jollygood/$(SHARE_DEST)/$${dir#*/}"; \ mkdir -p $(DESTDIR)$$dest; \ cp $$a $(DESTDIR)$$dest; \ else \ cp $$a $(DESTDIR)$(DATADIR)/jollygood/$(SHARE_DEST); \ fi; \ done endif endif install-man: all @mkdir -p $(DESTDIR)$(MANDIR)/man6 cp $(SOURCEDIR)/jollygood.6 $(DESTDIR)$(MANDIR)/man6 install: install-bin install-data install-docs install-man install-strip: install $(STRIP) $(DESTDIR)$(BINDIR)/$(NAME)$(BIN_EXT) uninstall: rm -f $(DESTDIR)$(BINDIR)/$(NAME)$(BIN_EXT) rm -rf $(DESTDIR)$(DOCDIR) rm -rf $(DESTDIR)$(DATADIR)/jollygood/$(SHARE_DEST) rm -f $(DESTDIR)$(DATAROOTDIR)/applications/$(NAME).desktop rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/32x32/apps/$(BASE).png rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/48x48/apps/$(BASE).png rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/64x64/apps/$(BASE).png rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/96x96/apps/$(BASE).png rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/128x128/apps/$(BASE).png rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/256x256/apps/$(BASE).png rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/512x512/apps/$(BASE).png rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/1024x1024/apps/$(BASE).png rm -f $(DESTDIR)$(DATAROOTDIR)/icons/hicolor/scalable/apps/$(BASE).svg rm -f $(DESTDIR)$(DATAROOTDIR)/pixmaps/$(BASE).svg rm -f $(DESTDIR)$(MANDIR)/man6/jollygood.6 jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/README000066400000000000000000000247171523150047600206700ustar00rootroot00000000000000The Jolly Good Reference Frontend --------------------------------- The Jolly Good Reference Frontend is the simplest possible frontend to The Jolly Good API. It may be used to run emulators built as shared objects, or as a "white-label" frontend for statically linked standalone emulators. This repository lives at https://gitlab.com/jgemu/jgrf Compiling --------- Make sure you have The Jolly Good API's header files installed. If you did not install them, you will be required to include their path in CFLAGS. You will also require pkgconf/pkg-config, and development packages for SDL3, SpeexDSP, and libepoxy. For the experimental Vulkan renderer, you will need additional libraries and tools (vulkan-headers, glslangValidator, etc). Options: ENABLE_VULKAN - Set non-zero to build the experimental Vulkan renderer USE_EXTERNAL_MD5 - Set non-zero to use an external libcrypto USE_EXTERNAL_MINIZ - Set non-zero to use an external miniz BUILD_STATIC - Set to the path of a core built with a static JG archive to build a standalone binary Linux: make macOS: make BSD: gmake Windows (MSYS2): make Cross Compile: (For example compiling on Linux for MinGW) CC=x86_64-w64-mingw32-cc \ PKG_CONFIG=x86_64-w64-mingw32-pkg-config \ STRIP=x86_64-w64-mingw32-strip \ make The build will output an executable named "jollygood". The program may be run locally, or installed system-wide using the "install" target. For command line usage instructions, execute the binary with no arguments or the -h switch. Configuration (Generic frontend) -------------------------------- Configuration files are located in: ~/.config/jollygood The following filenames are used: settings.ini - General settings corename_input.ini - Input definitions for a specific emulator core corename.ini - Core-specific overrides to general settings - Emulator settings listed in [corename] section User data is located in: ~/.local/share/jollygood The User data directory contains the following subdirectories: assets/corename/ - User assets such as sample data for Nestopia JG bios/ - Directory the frontend will load BIOS images from cheats/corename/ - Directory the frontend will load Cheat files from save/corename/ - SRAM, Memory Card, or other types of save data screenshots/ - Screenshots with a timestamp and random number state/corename/ - Save States Cores may be used locally by placing their build output directories into a "cores/" directory created in the jgrf source directory: mkdir cores cp -r /path/to/core cores/ Cores that exist locally will be preferred over cores installed system-wide. Configuration (Statically linked standalone builds) --------------------------------------------------- Static builds act as a standalone emulator, where "corename" is the name of the emulator, ex. bsnes, jollycv, nestopia. Configuration files are located in: ~/.config/corename - Static builds The following filenames are used: corename_input.ini - Input definitions for a specific emulator core corename.ini - Core-specific settings - Emulator settings listed in [corename] section Static builds use combined settings in "corename.ini" only. User data is located in: ~/.local/share/corename The User data directory contains the following subdirectories: assets/ - User assets such as sample data for Nestopia JG bios/ - Directory the frontend will load BIOS images from cheats/ - Directory the frontend will load Cheat files from save/ - SRAM, Memory Card, or other types of save data screenshots/ - Screenshots with a timestamp and random number state/ - Save States Input ----- Frontend Key Definitions: ESC - Quit TAB - Open Menu ` - Fast-forward F1 - Soft Reset F2 - Hard Reset F5 - Save State (Slot 0) F6 - Save State (Slot 1) F7 - Load State (Slot 0) F8 - Load State (Slot 1) F9 - Screenshot F12 - Toggle Cheats On/Off F - Toggle Fullscreen/Windowed Mode M - Toggle Audio Playback (Mute/Unmute) Menu Key Definitions: ESC - Exit Menu TAB/LEFT - Back ENTER/RIGHT - Activate selected menu item UP/DOWN - Navigate Up and Down Input for games is not preconfigured. To configure a device: SHIFT+[Port Number] ex. SHIFT+1, SHIFT+2 To toggle hotkey processing when using an emulated keyboard: SHIFT+TAB Effort has been made to ensure emulator cores know what devices must be plugged in. Console output will indicate which emulated device is plugged into which emulated port. Axis and Button input is considered separate. Digital buttons cannot be assigned to emulated axes. This is a design choice. Use the ESC key to skip defining axes if you do not have a physical input devices with axes. Ideally, buy a budget USB gamepad with analog sticks/triggers if you want to play games that require analog input. The reverse, assigning physical axes to emulated digital buttons, is possible. Force Feedback data is sent to the physical device that handles the emulated device's axis definitions. Currently, this only exists in PlayStation and Game Boy games. In the case of the Game Boy, there is only a single controller, so force feedback data is sent to this controller. Settings -------- Below is an example of the General Settings configuration file, with defaults: [audio] ;N = Resampler Quality (0-10) rsqual = 3 [video] ;0 = OpenGL Core Profile, 1 = OpenGL ES, 2 = OpenGL Compatibility Profile ;3 = Vulkan api = 0 ;0 = Disable, 1 = Enable fullscreen = 0 ;N = Scale video output by N (1-8) scale = 3 ;0 = Nearest Neighbour, 1 = Linear, 2 = Sharp Bilinear, 3 = AANN, ;4 = CRT-Yee64, 5 = CRTea, 6 = LCD shader = 3 ; Settings for CRTea ;0 = Scanlines, 1 = Aperture Grille Lite, 2 = Aperture Grille, ;3 = Shadow Mask, 4 = Custom crtea_mode = 2 ; Settings for CRTea Custom Mode ;0 = Scanlines, 1 = Aperture Grille Lite, 2 = Aperture Grille, 3 = Shadow Mask crtea_masktype = 2 ;N = Mask Strength crtea_maskstr = 5 ;N = Scanline Strength crtea_scantr = 6 ;N = Sharpness crtea_sharpness = 7 ;N = CRT Screen Curve Strength crtea_curve = 2 ;N = CRT Corner Size crtea_corner = 3 ;N = Trinitron Curve Strength crtea_tcurve = 10 [input] ;N = Deadzone Size axis_deadzone = 5 [misc] ;0 = Disabled, 1 = Enabled cheatauto = 0 ;0 = Debug, 1 = Info, 2 = Warning, 3 = Error corelog = 1 ;0 = Debug, 1 = Info, 2 = Warning, 3 = Error frontendlog = 1 ;N = Text Scale textscale = 2 ;0 = 6x8, 1 = 8x8 font = 0 The above configuration settings may be overridden in a core specific configuration file. Additionally, you may add a section for Emulator Settings. The following example is for the JollyCV core: [jollycv] ;0 = Show Overscan, 1 = Mask Overscan mask_overscan = 0 ;0 = TeaTime, 1 = SYoung palette = 0 ;N = Resampler Quality (0-10) rsqual = 3 ;0 = NTSC, 1 = PAL region = 0 Environment Variables --------------------- XDG_CONFIG_HOME - The XDG base directory. This is the path is used to find the frontend and core configuration files. Set by default to $HOME/.config/jollygood/ XDG_DATA_HOME - The directory for user-specific data files. This path is used to find data files used by the cores. Set by default $HOME/.local/share/jollygood/ JOLLYGOOD_CORE_DIRS - A sequence of path prefixes to tell the frontend which directories to search for Jolly Good core modules. If set, these directories take precedence when searching for core modules to load. JOLLYGOOD_ASSET_DIRS - A sequence of path prefixes to tell the frontend which directories to search for asset-containing subdirectories. If set, these directories take precedence when searching for the location of core assets. To specify multiple JOLLYGOOD_CORE_DIRS or JOLLYGOOD_ASSET_DIRS values, use a colon (:) separator between the path names on a UNIX system, and use a semicolon (;) separator between path names on a Windows system. Cheats ------ When a core supports cheats, the cheat file must be named the same as the game, but with a file extension of ".json". The file must be located in the cheats directory for the core, for example: ~/.local/share/jollygood/cheats/corename/gamename.json - Generic frontend ~/.local/share/corename/cheats/gamename.json - Static builds The file must be formatted as such: {"cheats":[ {"codes":["CHEAT001"], "enabled":true, "desc":"Single-code Cheat"}, {"codes":["CHEAT002"], "enabled":false, "desc":"Disabled Single-code Cheat"}, {"codes":["CHEAT003", "CHEAT004"], "enabled":true, "desc":"Multi-code Cheat"}, // {"codes":["CHEAT005"], "enabled":true, "desc":"Commented Cheat"}, {"codes":["CHEAT006"], "enabled":true, "desc":"Final Cheat has no comma"} ]} The format for the cheat codes is different for each system, and some systems support multiple types of codes. All codes are represented as JSON strings. Copyright --------- The Jolly Good Reference Frontend Copyright (c) 2020-2026, Rupert Carmichael (BSD-3-Clause) See LICENSE AntiAliased Nearest Neighbor Fragment Shader Copyright (c) 2015 nyanpasu64 and wareya (MIT) CRT-Yee64 Fragment Shader Copyright (c) 2017 Lucas Melo (MIT) ezmenu Copyright (c) 2022 rofl0r (MIT) LodePNG Copyright (c) 2005-2020 Lode Vandevenne (Zlib) See deps/lodepng.c MD5 Message-Digest Algorithm (RFC 1321) Implementation Public Domain or BSD-like license without advertising clause See deps/md5.c miniz Copyright 2013-2014 RAD Game Tools and Valve Software (MIT) Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC (MIT) See deps/miniz/miniz.c (https://github.com/richgel999/miniz) musl getopt, memmem implementations Copyright © 2005-2023 Rich Felker, et al. (MIT) See deps/musl_getopt.c, deps/musl_memmem.c parson Copyright (c) 2012 - 2023 Krzysztof Gabis (MIT) See deps/parson.c tconfig Copyright (c) 2019 Justin Kinnaird (MIT) See deps/tconfig.c Wave Writer Public Domain (CC0-1.0) See deps/wave_writer.c jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/000077500000000000000000000000001523150047600207305ustar00rootroot00000000000000jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/ezmenu.h000066400000000000000000000102101523150047600223760ustar00rootroot00000000000000/* MIT License Copyright (C) 2022 rofl0r 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. */ /* a single-file library to implement a simple menu system in emulators. it mainly deals with scrolling/selection in a list of string items. the interfacing code supplies a list of strings, optional header and footer text, and sends up/down key movement user input. ezmenu then takes care of scrolling up and down, so the interface code simply has to print the vislines onto the screen using the fontsize specified in ezmenu_init(). if a user presses e.g. enter to activate a menu item, the interface code has to deal with the input itself and read vislines[vissel] to find out which entry was activated. */ #include #include struct ezmenu { /* internal list of lines, passed via ezmenu_setlines. if you use malloc()'d strings here you need to free them before repopulating the list with new lines. */ char **lines; /* these are the visible lines you need to render. */ char **vislines; char *header, *footer; /* internal bookkeeping for the amount of lines in "lines". */ int linecount; int sel; /* internal bookkeeping of which line of lines is selected */ int vissel; /* which line out of vislines is selected, for extern use */ int w, h; /* width and height of visible character grid, in cells */ /* whether to wrap around if user presses up on first item, or down on last */ int wraparound; int yscroll; /* difference between first line and first visible line */ int start; /* first non-header line */ int end; /* last non-footer line */ }; /* ezmenu only cares about down and up events, to scroll and select. any other event is handled by the caller, e.g. if button "a" was pressed, you gotta extract the text of vislines[vissel] and act upon it. */ enum ezmenu_input { EZM_DOWN, EZM_UP, }; static void ezmenu_init(struct ezmenu *m, int hres, int vres, int fontw, int fonth) { memset(m, 0, sizeof(*m)); m->w = hres/fontw; m->h = vres/fonth; m->vislines = calloc(m->h, sizeof(char*)); } static void ezmenu_setlines(struct ezmenu *m, char**lines, unsigned linecount) { m->lines = lines; m->linecount = linecount; m->sel = 0; } static void ezmenu_setheader(struct ezmenu *m, char* hdr) { m->header = hdr; m->vislines[0] = hdr; m->vislines[1] = ""; } static void ezmenu_setfooter(struct ezmenu *m, char* ftr) { m->footer = ftr; m->vislines[m->h-2] = ""; m->vislines[m->h-1] = ftr; } #ifndef MAX #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #endif static void ezmenu_update(struct ezmenu *m) { m->start = (!!m->header)*2; m->end = m->h - (!!m->footer)*2; int vis = m->end - m->start; int y, ly = m->yscroll = MAX(0, m->sel - vis/2); m->vissel = (m->sel-ly)+(!!m->header)*2; for(y = m->start; y < m->end; ++y) { m->vislines[y] = ly>=m->linecount?"":m->lines[ly++]; } } static void ezmenu_userinput(struct ezmenu *m, enum ezmenu_input inp) { static const int dir[] = {[EZM_DOWN] = 1, [EZM_UP] = -1}; m->sel += dir[inp]; if(m->sel < 0) { if(m->wraparound) m->sel = m->linecount-1; else m->sel = 0; } else if(m->sel >= m->linecount) { if(m->wraparound) m->sel = 0; else m->sel = m->linecount-1; } ezmenu_update(m); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/lodepng.c000066400000000000000000011410321523150047600225260ustar00rootroot00000000000000/* LodePNG version 20260119 Copyright (c) 2005-2026 Lode Vandevenne 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. */ /* The manual and changelog are in the header file "lodepng.h" Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. */ // Alterations: Made lodepng_convert_rgb static #include "lodepng.h" #ifdef LODEPNG_COMPILE_DISK #include /* LONG_MAX */ #include /* file handling */ #endif /* LODEPNG_COMPILE_DISK */ #ifdef LODEPNG_COMPILE_ALLOCATORS #include /* allocations */ #endif /* LODEPNG_COMPILE_ALLOCATORS */ #if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ #pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ #pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ #endif /*_MSC_VER */ const char* LODEPNG_VERSION_STRING = "20260119"; /* This source file is divided into the following large parts. The code sections with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. -Tools for C and common code for PNG and Zlib -C Code for Zlib (huffman, deflate, ...) -C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) -The C++ wrapper around all of the above */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /* // Tools for C, and common code for PNG and Zlib. // */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /*The malloc, realloc and free functions defined here with "lodepng_" in front of the name, so that you can easily change them to others related to your platform if needed. Everything else in the code calls these. Pass -DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out #define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and define them in your own project's source files without needing to change lodepng source code. Don't forget to remove "static" if you copypaste them from here.*/ #ifdef LODEPNG_COMPILE_ALLOCATORS static void* lodepng_malloc(size_t size) { #ifdef LODEPNG_MAX_ALLOC if(size > LODEPNG_MAX_ALLOC) return 0; #endif return malloc(size); } /* NOTE: when realloc returns NULL, it leaves the original memory untouched */ static void* lodepng_realloc(void* ptr, size_t new_size) { #ifdef LODEPNG_MAX_ALLOC if(new_size > LODEPNG_MAX_ALLOC) return 0; #endif return realloc(ptr, new_size); } static void lodepng_free(void* ptr) { free(ptr); } #else /*LODEPNG_COMPILE_ALLOCATORS*/ /* TODO: support giving additional void* payload to the custom allocators */ void* lodepng_malloc(size_t size); void* lodepng_realloc(void* ptr, size_t new_size); void lodepng_free(void* ptr); #endif /*LODEPNG_COMPILE_ALLOCATORS*/ /* convince the compiler to inline a function, for use when this measurably improves performance */ /* inline is not available in C90, but use it when supported by the compiler */ #if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) #define LODEPNG_INLINE inline #else #define LODEPNG_INLINE /* not available */ #endif /* restrict is not available in C90, but use it when supported by the compiler */ #if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) #define LODEPNG_RESTRICT __restrict #else #define LODEPNG_RESTRICT /* not available */ #endif /* Replacements for C library functions such as memcpy and strlen, to support platforms where a full C library is not available. The compiler can recognize them and compile to something as fast. */ static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, const void* LODEPNG_RESTRICT src, size_t size) { size_t i; for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; } static void lodepng_memset(void* LODEPNG_RESTRICT dst, int value, size_t num) { size_t i; for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; } /* does not check memory out of bounds, do not use on untrusted data */ static size_t lodepng_strlen(const char* a) { const char* orig = a; /* avoid warning about unused function in case of disabled COMPILE... macros */ (void)(&lodepng_strlen); while(*a) a++; return (size_t)(a - orig); } #define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) #if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) /* Safely check if adding two integers will overflow (no undefined behavior, compiler removing the code, etc...) and output result. */ static int lodepng_addofl(size_t a, size_t b, size_t* result) { *result = a + b; /* Unsigned addition is well defined and safe in C90 */ return *result < a; } #endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ #ifdef LODEPNG_COMPILE_DECODER /* Safely check if multiplying two integers will overflow (no undefined behavior, compiler removing the code, etc...) and output result. */ static int lodepng_mulofl(size_t a, size_t b, size_t* result) { *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ return (a != 0 && *result / a != b); } #ifdef LODEPNG_COMPILE_ZLIB /* Safely check if a + b > c, even if overflow could happen. */ static int lodepng_gtofl(size_t a, size_t b, size_t c) { size_t d; if(lodepng_addofl(a, b, &d)) return 1; return d > c; } #endif /*LODEPNG_COMPILE_ZLIB*/ #endif /*LODEPNG_COMPILE_DECODER*/ /* Often in case of an error a value is assigned to a variable and then it breaks out of a loop (to go to the cleanup phase of a function). This macro does that. It makes the error handling code shorter and more readable. Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); */ #define CERROR_BREAK(errorvar, code){\ errorvar = code;\ break;\ } /*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ #define ERROR_BREAK(code) CERROR_BREAK(error, code) /*Set error var to the error code, and return it.*/ #define CERROR_RETURN_ERROR(errorvar, code){\ errorvar = code;\ return code;\ } /*Try the code, if it returns error, also return the error.*/ #define CERROR_TRY_RETURN(call){\ unsigned error_ = call;\ if(error_) return error_;\ } /*Set error var to the error code, and return from the void function.*/ #define CERROR_RETURN(errorvar, code){\ errorvar = code;\ return;\ } /* About uivector, ucvector and string: -All of them wrap dynamic arrays or text strings in a similar way. -LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. -The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. -They're not used in the interface, only internally in this file as static functions. -As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. */ #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_ENCODER /*dynamic vector of unsigned ints*/ typedef struct uivector { unsigned* data; size_t size; /*size in number of unsigned longs*/ size_t allocsize; /*allocated size in bytes*/ } uivector; static void uivector_cleanup(void* p) { ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; lodepng_free(((uivector*)p)->data); ((uivector*)p)->data = NULL; } /*returns 1 if success, 0 if failure ==> nothing done*/ static unsigned uivector_resize(uivector* p, size_t size) { size_t allocsize = size * sizeof(unsigned); if(allocsize > p->allocsize) { size_t newsize = allocsize + (p->allocsize >> 1u); void* data = lodepng_realloc(p->data, newsize); if(data) { p->allocsize = newsize; p->data = (unsigned*)data; } else return 0; /*error: not enough memory*/ } p->size = size; return 1; /*success*/ } static void uivector_init(uivector* p) { p->data = NULL; p->size = p->allocsize = 0; } /*returns 1 if success, 0 if failure ==> nothing done*/ static unsigned uivector_push_back(uivector* p, unsigned c) { if(!uivector_resize(p, p->size + 1)) return 0; p->data[p->size - 1] = c; return 1; } #endif /*LODEPNG_COMPILE_ENCODER*/ #endif /*LODEPNG_COMPILE_ZLIB*/ /* /////////////////////////////////////////////////////////////////////////// */ /*dynamic vector of unsigned chars*/ typedef struct ucvector { unsigned char* data; size_t size; /*used size*/ size_t allocsize; /*allocated size*/ } ucvector; /*returns 1 if success, 0 if failure ==> nothing done*/ static unsigned ucvector_reserve(ucvector* p, size_t size) { if(size > p->allocsize) { size_t newsize = size + (p->allocsize >> 1u); void* data = lodepng_realloc(p->data, newsize); if(data) { p->allocsize = newsize; p->data = (unsigned char*)data; } else return 0; /*error: not enough memory*/ } return 1; /*success*/ } /*returns 1 if success, 0 if failure ==> nothing done*/ static unsigned ucvector_resize(ucvector* p, size_t size) { p->size = size; return ucvector_reserve(p, size); } static ucvector ucvector_init(unsigned char* buffer, size_t size) { ucvector v; v.data = buffer; v.allocsize = v.size = size; return v; } /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_PNG #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*also appends null termination character*/ static char* alloc_string_sized(const char* in, size_t insize) { char* out = (char*)lodepng_malloc(insize + 1); if(out) { lodepng_memcpy(out, in, insize); out[insize] = 0; } return out; } /* dynamically allocates a new string with a copy of the null terminated input text */ static char* alloc_string(const char* in) { return alloc_string_sized(in, lodepng_strlen(in)); } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ #endif /*LODEPNG_COMPILE_PNG*/ /* ////////////////////////////////////////////////////////////////////////// */ #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) static unsigned lodepng_read32bitInt(const unsigned char* buffer) { return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); } #endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ #if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) /*buffer must have at least 4 allocated bytes available*/ static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { buffer[0] = (unsigned char)((value >> 24) & 0xff); buffer[1] = (unsigned char)((value >> 16) & 0xff); buffer[2] = (unsigned char)((value >> 8) & 0xff); buffer[3] = (unsigned char)((value ) & 0xff); } #endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ /* ////////////////////////////////////////////////////////////////////////// */ /* / File IO / */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_DISK /* returns negative value on error. This should be pure C compatible, so no fstat. */ static long lodepng_filesize(FILE* file) { long size; if(fseek(file, 0, SEEK_END) != 0) return -1; size = ftell(file); /* It may give LONG_MAX as directory size, this is invalid for us. */ if(size == LONG_MAX) return -1; if(fseek(file, 0, SEEK_SET) != 0) return -1; return size; } /* Allocates the output buffer to the file size and reads the file into it. Returns error code.*/ static unsigned lodepng_load_file_(unsigned char** out, size_t* outsize, FILE* file) { long size = lodepng_filesize(file); if(size < 0) return 78; *outsize = (size_t)size; *out = (unsigned char*)lodepng_malloc((size_t)size); if(!(*out) && size > 0) return 83; /*the above malloc failed*/ if(fread(*out, 1, *outsize, file) != *outsize) return 78; return 0; /*ok*/ } unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { unsigned error; FILE* file = fopen(filename, "rb"); if(!file) return 78; error = lodepng_load_file_(out, outsize, file); fclose(file); return error; } /*write given buffer to the file, overwriting the file, it doesn't append to it.*/ unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { FILE* file = fopen(filename, "wb" ); if(!file) return 79; fwrite(buffer, 1, buffersize, file); fclose(file); return 0; } #endif /*LODEPNG_COMPILE_DISK*/ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /* // End of common code and tools. Begin of Zlib related code. // */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_ENCODER typedef struct { ucvector* data; unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ } LodePNGBitWriter; static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { writer->data = data; writer->bp = 0; } /*TODO: this ignores potential out of memory errors*/ #define WRITEBIT(writer, bit){\ /* append new byte */\ if(((writer->bp) & 7u) == 0) {\ if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ writer->data->data[writer->data->size - 1] = 0;\ }\ (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ ++writer->bp;\ } /* LSB of value is written first, and LSB of bytes is used first */ static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ WRITEBIT(writer, value); } else { /* TODO: increase output size only once here rather than in each WRITEBIT */ size_t i; for(i = 0; i != nbits; ++i) { WRITEBIT(writer, (unsigned char)((value >> i) & 1)); } } } /* This one is to use for adding huffman symbol, the value bits are written MSB first */ static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { size_t i; for(i = 0; i != nbits; ++i) { /* TODO: increase output size only once here rather than in each WRITEBIT */ WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); } } #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_DECODER typedef struct { const unsigned char* data; size_t size; /*size of data in bytes*/ size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ size_t bp; unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ } LodePNGBitReader; /* data size argument is in bytes. Returns error if size too large causing overflow */ static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { size_t temp; reader->data = data; reader->size = size; /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and trying to ensure 32 more bits*/ if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; reader->bp = 0; reader->buffer = 0; return 0; /*ok*/ } /* ensureBits functions: Ensures the reader can at least read nbits bits in one or more readBits calls, safely even if not enough bits are available. The nbits parameter is unused but is given for documentation purposes, error checking for amount of bits must be done beforehand. */ /*See ensureBits documentation above. This one ensures up to 9 bits */ static LODEPNG_INLINE void ensureBits9(LodePNGBitReader* reader, size_t nbits) { size_t start = reader->bp >> 3u; size_t size = reader->size; if(start + 1u < size) { reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); reader->buffer >>= (reader->bp & 7u); } else { reader->buffer = 0; if(start + 0u < size) reader->buffer = reader->data[start + 0]; reader->buffer >>= (reader->bp & 7u); } (void)nbits; } /*See ensureBits documentation above. This one ensures up to 17 bits */ static LODEPNG_INLINE void ensureBits17(LodePNGBitReader* reader, size_t nbits) { size_t start = reader->bp >> 3u; size_t size = reader->size; if(start + 2u < size) { reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u); reader->buffer >>= (reader->bp & 7u); } else { reader->buffer = 0; if(start + 0u < size) reader->buffer |= reader->data[start + 0]; if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); reader->buffer >>= (reader->bp & 7u); } (void)nbits; } /*See ensureBits documentation above. This one ensures up to 25 bits */ static LODEPNG_INLINE void ensureBits25(LodePNGBitReader* reader, size_t nbits) { size_t start = reader->bp >> 3u; size_t size = reader->size; if(start + 3u < size) { reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); reader->buffer >>= (reader->bp & 7u); } else { reader->buffer = 0; if(start + 0u < size) reader->buffer |= reader->data[start + 0]; if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); reader->buffer >>= (reader->bp & 7u); } (void)nbits; } /*See ensureBits documentation above. This one ensures up to 32 bits */ static LODEPNG_INLINE void ensureBits32(LodePNGBitReader* reader, size_t nbits) { size_t start = reader->bp >> 3u; size_t size = reader->size; if(start + 4u < size) { reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); reader->buffer >>= (reader->bp & 7u); reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); } else { reader->buffer = 0; if(start + 0u < size) reader->buffer |= reader->data[start + 0]; if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); reader->buffer >>= (reader->bp & 7u); } (void)nbits; } /* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ static LODEPNG_INLINE unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { /* The shift allows nbits to be only up to 31. */ return reader->buffer & ((1u << nbits) - 1u); } /* Must have enough bits available with ensureBits */ static LODEPNG_INLINE void advanceBits(LodePNGBitReader* reader, size_t nbits) { reader->buffer >>= nbits; reader->bp += nbits; } /* Must have enough bits available with ensureBits */ static LODEPNG_INLINE unsigned readBits(LodePNGBitReader* reader, size_t nbits) { unsigned result = peekBits(reader, nbits); advanceBits(reader, nbits); return result; } #endif /*LODEPNG_COMPILE_DECODER*/ static unsigned reverseBits(unsigned bits, unsigned num) { /*TODO: implement faster lookup table based version when needed*/ unsigned i, result = 0; for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; return result; } /* ////////////////////////////////////////////////////////////////////////// */ /* / Deflate - Huffman / */ /* ////////////////////////////////////////////////////////////////////////// */ #define FIRST_LENGTH_CODE_INDEX 257 #define LAST_LENGTH_CODE_INDEX 285 /*256 literals, the end code, some length codes, and 2 unused codes*/ #define NUM_DEFLATE_CODE_SYMBOLS 288 /*the distance codes have their own symbols, 30 used, 2 unused*/ #define NUM_DISTANCE_SYMBOLS 32 /*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ #define NUM_CODE_LENGTH_CODES 19 /*the base lengths represented by codes 257-285*/ static const unsigned LENGTHBASE[29] = {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}; /*the extra bits used by codes 257-285 (added to base length)*/ static const unsigned LENGTHEXTRA[29] = {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}; /*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ static const unsigned DISTANCEBASE[30] = {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}; /*the extra bits of backwards distances (added to base)*/ static const unsigned DISTANCEEXTRA[30] = {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}; /*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman tree of the dynamic huffman tree lengths is generated*/ static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; /* ////////////////////////////////////////////////////////////////////////// */ /* Huffman tree struct, containing multiple representations of the tree */ typedef struct HuffmanTree { unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ unsigned* lengths; /*the lengths of the huffman codes*/ unsigned maxbitlen; /*maximum number of bits a single code can get*/ unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ /* for reading only */ unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ } HuffmanTree; static void HuffmanTree_init(HuffmanTree* tree) { tree->codes = 0; tree->lengths = 0; tree->table_len = 0; tree->table_value = 0; } static void HuffmanTree_cleanup(HuffmanTree* tree) { lodepng_free(tree->codes); lodepng_free(tree->lengths); lodepng_free(tree->table_len); lodepng_free(tree->table_value); } /* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ /* values 8u and 9u work the fastest */ #define FIRSTBITS 9u /* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, which is possible in case of only 0 or 1 present symbols. */ #define INVALIDSYMBOL 65535u /* make table for huffman decoding */ static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; size_t i, numpresent, pointer, size; /*total table size*/ unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); if(!maxlens) return 83; /*alloc fail*/ /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); for(i = 0; i < tree->numcodes; i++) { unsigned symbol = tree->codes[i]; unsigned l = tree->lengths[i]; unsigned index; if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); maxlens[index] = LODEPNG_MAX(maxlens[index], l); } /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ size = headsize; for(i = 0; i < headsize; ++i) { unsigned l = maxlens[i]; if(l > FIRSTBITS) size += (((size_t)1) << (l - FIRSTBITS)); } tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); if(!tree->table_len || !tree->table_value) { lodepng_free(maxlens); /* freeing tree->table values is done at a higher scope */ return 83; /*alloc fail*/ } /*initialize with an invalid length to indicate unused entries*/ for(i = 0; i < size; ++i) tree->table_len[i] = 16; /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ pointer = headsize; for(i = 0; i < headsize; ++i) { unsigned l = maxlens[i]; if(l <= FIRSTBITS) continue; tree->table_len[i] = l; tree->table_value[i] = (unsigned short)pointer; pointer += (((size_t)1) << (l - FIRSTBITS)); } lodepng_free(maxlens); /*fill in the first table for short symbols, or secondary table for long symbols*/ numpresent = 0; for(i = 0; i < tree->numcodes; ++i) { unsigned l = tree->lengths[i]; unsigned symbol, reverse; if(l == 0) continue; symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ reverse = reverseBits(symbol, l); numpresent++; if(l <= FIRSTBITS) { /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ unsigned num = 1u << (FIRSTBITS - l); unsigned j; for(j = 0; j < num; ++j) { /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ unsigned index = reverse | (j << l); if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ tree->table_len[index] = l; tree->table_value[index] = (unsigned short)i; } } else { /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ /*the FIRSTBITS MSBs of the symbol are the first table index*/ unsigned index = reverse & mask; unsigned maxlen = tree->table_len[index]; /*log2 of secondary table length, should be >= l - FIRSTBITS*/ unsigned tablelen = maxlen - FIRSTBITS; unsigned start = tree->table_value[index]; /*starting index in secondary table*/ unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ unsigned j; if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ for(j = 0; j < num; ++j) { unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); tree->table_len[index2] = l; tree->table_value[index2] = (unsigned short)i; } } } if(numpresent < 2) { /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, but deflate uses 1 bit instead. In case of 0 symbols, no symbols can appear at all, but such huffman tree could still exist (e.g. if distance codes are never used). In both cases, not all symbols of the table will be filled in. Fill them in with an invalid symbol value so returning them from huffmanDecodeSymbol will cause error. */ for(i = 0; i < size; ++i) { if(tree->table_len[i] == 16) { /* As length, use a value smaller than FIRSTBITS for the head table, and a value larger than FIRSTBITS for the secondary table, to ensure valid behavior for advanceBits when reading this symbol. */ tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); tree->table_value[i] = INVALIDSYMBOL; } } } else { /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. If that is not the case (due to too long length codes), the table will not have been fully used, and this is an error (not all bit combinations can be decoded): an oversubscribed huffman tree, indicated by error 55. */ for(i = 0; i < size; ++i) { if(tree->table_len[i] == 16) return 55; } } return 0; } /* Second step for the ...makeFromLengths and ...makeFromFrequencies functions. numcodes, lengths and maxbitlen must already be filled in correctly. return value is error. */ static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { unsigned* blcount; unsigned* nextcode; unsigned error = 0; unsigned bits, n; tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ if(!error) { for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; /*step 1: count number of instances of each code length*/ for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; /*step 2: generate the nextcode values*/ for(bits = 1; bits <= tree->maxbitlen; ++bits) { nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; } /*step 3: generate all the codes*/ for(n = 0; n != tree->numcodes; ++n) { if(tree->lengths[n] != 0) { tree->codes[n] = nextcode[tree->lengths[n]]++; /*remove superfluous bits from the code*/ tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); } } } lodepng_free(blcount); lodepng_free(nextcode); if(!error) error = HuffmanTree_makeTable(tree); return error; } /* given the code lengths (as stored in the PNG file), generate the tree as defined by Deflate. maxbitlen is the maximum bits that a code in the tree can have. return value is error. */ static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, size_t numcodes, unsigned maxbitlen) { unsigned i; tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); if(!tree->lengths) return 83; /*alloc fail*/ for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; tree->numcodes = (unsigned)numcodes; /*number of symbols*/ tree->maxbitlen = maxbitlen; return HuffmanTree_makeFromLengths2(tree); } #ifdef LODEPNG_COMPILE_ENCODER /*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ /*chain node for boundary package merge*/ typedef struct BPMNode { int weight; /*the sum of all weights in this chain*/ unsigned index; /*index of this leaf node (called "count" in the paper)*/ struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ int in_use; } BPMNode; /*lists of chains*/ typedef struct BPMLists { /*memory pool*/ unsigned memsize; BPMNode* memory; unsigned numfree; unsigned nextfree; BPMNode** freelist; /*two heads of lookahead chains per list*/ unsigned listsize; BPMNode** chains0; BPMNode** chains1; } BPMLists; /*creates a new chain node with the given parameters, from the memory in the lists */ static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { unsigned i; BPMNode* result; /*memory full, so garbage collect*/ if(lists->nextfree >= lists->numfree) { /*mark only those that are in use*/ for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; for(i = 0; i != lists->listsize; ++i) { BPMNode* node; for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; } /*collect those that are free*/ lists->numfree = 0; for(i = 0; i != lists->memsize; ++i) { if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; } lists->nextfree = 0; } result = lists->freelist[lists->nextfree++]; result->weight = weight; result->index = index; result->tail = tail; return result; } /*sort the leaves with stable mergesort*/ static void bpmnode_sort(BPMNode* leaves, size_t num) { BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); size_t width, counter = 0; for(width = 1; width < num; width *= 2) { BPMNode* a = (counter & 1) ? mem : leaves; BPMNode* b = (counter & 1) ? leaves : mem; size_t p; for(p = 0; p < num; p += 2 * width) { size_t q = (p + width > num) ? num : (p + width); size_t r = (p + 2 * width > num) ? num : (p + 2 * width); size_t i = p, j = q, k; for(k = p; k < r; k++) { if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; else b[k] = a[j++]; } } counter++; } if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); lodepng_free(mem); } /*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { unsigned lastindex = lists->chains1[c]->index; if(c == 0) { if(lastindex >= numpresent) return; lists->chains0[c] = lists->chains1[c]; lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); } else { /*sum of the weights of the head nodes of the previous lookahead chains.*/ int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; lists->chains0[c] = lists->chains1[c]; if(lastindex < numpresent && sum > leaves[lastindex].weight) { lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); return; } lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); /*in the end we are only interested in the chain of the last list, so no need to recurse if we're at the last one (this gives measurable speedup)*/ if(num + 1 < (int)(2 * numpresent - 2)) { boundaryPM(lists, leaves, numpresent, c - 1, num); boundaryPM(lists, leaves, numpresent, c - 1, num); } } } unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, size_t numcodes, unsigned maxbitlen) { unsigned error = 0; unsigned i; size_t numpresent = 0; /*number of symbols with non-zero frequency*/ BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); if(!leaves) return 83; /*alloc fail*/ for(i = 0; i != numcodes; ++i) { if(frequencies[i] > 0) { leaves[numpresent].weight = (int)frequencies[i]; leaves[numpresent].index = i; ++numpresent; } } lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); /*ensure at least two present symbols. There should be at least one symbol according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To make these work as well ensure there are at least two symbols. The Package-Merge code below also doesn't work correctly if there's only one symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ if(numpresent == 0) { lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ } else if(numpresent == 1) { lengths[leaves[0].index] = 1; lengths[leaves[0].index == 0 ? 1 : 0] = 1; } else { BPMLists lists; BPMNode* node; bpmnode_sort(leaves, numpresent); lists.listsize = maxbitlen; lists.memsize = 2 * maxbitlen * (maxbitlen + 1); lists.nextfree = 0; lists.numfree = lists.memsize; lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ if(!error) { for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; bpmnode_create(&lists, leaves[0].weight, 1, 0); bpmnode_create(&lists, leaves[1].weight, 2, 0); for(i = 0; i != lists.listsize; ++i) { lists.chains0[i] = &lists.memory[0]; lists.chains1[i] = &lists.memory[1]; } /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; } } lodepng_free(lists.memory); lodepng_free(lists.freelist); lodepng_free(lists.chains0); lodepng_free(lists.chains1); } lodepng_free(leaves); return error; } /*Create the Huffman tree given the symbol frequencies*/ static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, size_t mincodes, size_t numcodes, unsigned maxbitlen) { unsigned error = 0; while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); if(!tree->lengths) return 83; /*alloc fail*/ tree->maxbitlen = maxbitlen; tree->numcodes = (unsigned)numcodes; /*number of symbols*/ error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); if(!error) error = HuffmanTree_makeFromLengths2(tree); return error; } #endif /*LODEPNG_COMPILE_ENCODER*/ /*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ static unsigned generateFixedLitLenTree(HuffmanTree* tree) { unsigned i, error = 0; unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); if(!bitlen) return 83; /*alloc fail*/ /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ for(i = 0; i <= 143; ++i) bitlen[i] = 8; for(i = 144; i <= 255; ++i) bitlen[i] = 9; for(i = 256; i <= 279; ++i) bitlen[i] = 7; for(i = 280; i <= 287; ++i) bitlen[i] = 8; error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); lodepng_free(bitlen); return error; } /*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ static unsigned generateFixedDistanceTree(HuffmanTree* tree) { unsigned i, error = 0; unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); if(!bitlen) return 83; /*alloc fail*/ /*there are 32 distance codes, but 30-31 are unused*/ for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); lodepng_free(bitlen); return error; } #ifdef LODEPNG_COMPILE_DECODER /* returns the code. The bit reader must already have been ensured at least 15 bits */ static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { unsigned short code = peekBits(reader, FIRSTBITS); unsigned short l = codetree->table_len[code]; unsigned short value = codetree->table_value[code]; if(l <= FIRSTBITS) { advanceBits(reader, l); return value; } else { advanceBits(reader, FIRSTBITS); value += peekBits(reader, l - FIRSTBITS); advanceBits(reader, codetree->table_len[value] - FIRSTBITS); return codetree->table_value[value]; } } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_DECODER /* ////////////////////////////////////////////////////////////////////////// */ /* / Inflator (Decompressor) / */ /* ////////////////////////////////////////////////////////////////////////// */ /*get the tree of a deflated block with fixed tree, as specified in the deflate specification Returns error code.*/ static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { unsigned error = generateFixedLitLenTree(tree_ll); if(error) return error; return generateFixedDistanceTree(tree_d); } /*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, LodePNGBitReader* reader) { /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ unsigned error = 0; unsigned n, HLIT, HDIST, HCLEN, i; /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ unsigned* bitlen_ll = 0; /*lit,len code lengths*/ unsigned* bitlen_d = 0; /*dist code lengths*/ /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ unsigned* bitlen_cl = 0; HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ if(reader->bitsize - reader->bp < 14) return 49; /*error: the bit pointer is or will go past the memory*/ ensureBits17(reader, 14); /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ HLIT = readBits(reader, 5) + 257; /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ HDIST = readBits(reader, 5) + 1; /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ HCLEN = readBits(reader, 4) + 4; bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); if(!bitlen_cl) return 83 /*alloc fail*/; HuffmanTree_init(&tree_cl); while(!error) { /*read the code length codes out of 3 * (amount of code length codes) bits*/ if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ } for(i = 0; i != HCLEN; ++i) { ensureBits9(reader, 3); /*out of bounds already checked above */ bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); } for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { bitlen_cl[CLCL_ORDER[i]] = 0; } error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); if(error) break; /*now we can use this tree to read the lengths for the tree that this function will return*/ bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ i = 0; while(i < HLIT + HDIST) { unsigned code; ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ code = huffmanDecodeSymbol(reader, &tree_cl); if(code <= 15) /*a length code*/ { if(i < HLIT) bitlen_ll[i] = code; else bitlen_d[i - HLIT] = code; ++i; } else if(code == 16) /*repeat previous*/ { unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ unsigned value; /*set value to the previous code*/ if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ replength += readBits(reader, 2); if(i < HLIT + 1) value = bitlen_ll[i - 1]; else value = bitlen_d[i - HLIT - 1]; /*repeat this value in the next lengths*/ for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = value; else bitlen_d[i - HLIT] = value; ++i; } } else if(code == 17) /*repeat "0" 3-10 times*/ { unsigned replength = 3; /*read in the bits that indicate repeat length*/ replength += readBits(reader, 3); /*repeat this value in the next lengths*/ for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = 0; else bitlen_d[i - HLIT] = 0; ++i; } } else if(code == 18) /*repeat "0" 11-138 times*/ { unsigned replength = 11; /*read in the bits that indicate repeat length*/ replength += readBits(reader, 7); /*repeat this value in the next lengths*/ for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = 0; else bitlen_d[i - HLIT] = 0; ++i; } } else /*if(code == INVALIDSYMBOL)*/ { ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ } /*check if any of the ensureBits above went out of bounds*/ if(reader->bp > reader->bitsize) { /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol (10=no endcode, 11=wrong jump outside of tree)*/ /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ } } if(error) break; if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); if(error) break; error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); break; /*end of error-while*/ } lodepng_free(bitlen_cl); lodepng_free(bitlen_ll); lodepng_free(bitlen_d); HuffmanTree_cleanup(&tree_cl); return error; } /*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, unsigned btype, size_t max_output_size) { unsigned error = 0; HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ HuffmanTree tree_d; /*the huffman tree for distance codes*/ const size_t reserved_size = 260; /* must be at least 258 for max length, and a few extra for adding a few extra literals */ int done = 0; if(!ucvector_reserve(out, out->size + reserved_size)) return 83; /*alloc fail*/ HuffmanTree_init(&tree_ll); HuffmanTree_init(&tree_d); if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); while(!error && !done) /*decode all symbols until end reached, breaks at end code*/ { /*code_ll is literal, length or end code*/ unsigned code_ll; /* ensure enough bits for 2 huffman code reads (15 bits each): if the first is a literal, a second literal is read at once. This appears to be slightly faster, than ensuring 20 bits here for 1 huffman symbol and the potential 5 extra bits for the length symbol.*/ ensureBits32(reader, 30); code_ll = huffmanDecodeSymbol(reader, &tree_ll); if(code_ll <= 255) { /*slightly faster code path if multiple literals in a row*/ out->data[out->size++] = (unsigned char)code_ll; code_ll = huffmanDecodeSymbol(reader, &tree_ll); } if(code_ll <= 255) /*literal symbol*/ { out->data[out->size++] = (unsigned char)code_ll; } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { unsigned code_d, distance; unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ size_t start, backward, length; /*part 1: get length base*/ length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; /*part 2: get extra bits and add the value of that to length*/ numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; if(numextrabits_l != 0) { /* bits already ensured above */ ensureBits25(reader, 5); length += readBits(reader, numextrabits_l); } /*part 3: get distance code*/ ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ code_d = huffmanDecodeSymbol(reader, &tree_d); if(code_d > 29) { if(code_d <= 31) { ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ } else /* if(code_d == INVALIDSYMBOL) */{ ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ } } distance = DISTANCEBASE[code_d]; /*part 4: get extra bits from distance*/ numextrabits_d = DISTANCEEXTRA[code_d]; if(numextrabits_d != 0) { /* bits already ensured above */ distance += readBits(reader, numextrabits_d); } /*part 5: fill in all the out[n] values based on the length and dist*/ start = out->size; if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ backward = start - distance; out->size += length; if(distance < length) { size_t forward; lodepng_memcpy(out->data + start, out->data + backward, distance); start += distance; for(forward = distance; forward < length; ++forward) { out->data[start++] = out->data[backward++]; } } else { lodepng_memcpy(out->data + start, out->data + backward, length); } } else if(code_ll == 256) { done = 1; /*end code, finish the loop*/ } else /*if(code_ll == INVALIDSYMBOL)*/ { ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ } if(out->allocsize - out->size < reserved_size) { if(!ucvector_reserve(out, out->size + reserved_size)) ERROR_BREAK(83); /*alloc fail*/ } /*check if any of the ensureBits above went out of bounds*/ if(reader->bp > reader->bitsize) { /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol (10=no endcode, 11=wrong jump outside of tree)*/ /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ } if(max_output_size && out->size > max_output_size) { ERROR_BREAK(109); /*error, larger than max size*/ } } HuffmanTree_cleanup(&tree_ll); HuffmanTree_cleanup(&tree_d); return error; } static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, const LodePNGDecompressSettings* settings) { size_t bytepos; size_t size = reader->size; unsigned LEN, NLEN, error = 0; /*go to first boundary of byte*/ bytepos = (reader->bp + 7u) >> 3u; /*read LEN (2 bytes) and NLEN (2 bytes)*/ if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; /*check if 16-bit NLEN is really the one's complement of LEN*/ if(!settings->ignore_nlen && LEN + NLEN != 65535) { return 21; /*error: NLEN is not one's complement of LEN*/ } if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ /*read the literal data: LEN bytes are now stored in the out buffer*/ if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ /*out->data can be NULL (when LEN is zero), and arithmetic on NULL ptr is undefined*/ if (LEN) { lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); bytepos += LEN; } reader->bp = bytepos << 3u; return error; } static unsigned lodepng_inflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { unsigned BFINAL = 0; LodePNGBitReader reader; unsigned error = LodePNGBitReader_init(&reader, in, insize); if(error) return error; while(!BFINAL) { unsigned BTYPE; if(reader.bitsize - reader.bp < 3) return 52; /*error, bit pointer will jump past memory*/ ensureBits9(&reader, 3); BFINAL = readBits(&reader, 1); BTYPE = readBits(&reader, 2); if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/ if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109; if(error) break; } return error; } unsigned lodepng_inflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { ucvector v = ucvector_init(*out, *outsize); unsigned error = lodepng_inflatev(&v, in, insize, settings); *out = v.data; *outsize = v.size; return error; } static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { if(settings->custom_inflate) { unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); out->allocsize = out->size; if(error) { /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/ error = 110; /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ if(settings->max_output_size && out->size > settings->max_output_size) error = 109; } return error; } else { return lodepng_inflatev(out, in, insize, settings); } } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* ////////////////////////////////////////////////////////////////////////// */ /* / Deflator (Compressor) / */ /* ////////////////////////////////////////////////////////////////////////// */ static const unsigned MAX_SUPPORTED_DEFLATE_LENGTH = 258; /*search the index in the array, that has the largest value smaller than or equal to the given value, given array must be sorted (if no value is smaller, it returns the size of the given array)*/ static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ size_t left = 1; size_t right = array_size - 1; while(left <= right) { size_t mid = (left + right) >> 1; if(array[mid] >= value) right = mid - 1; else left = mid + 1; } if(left >= array_size || array[left] > value) left--; return left; } static void addLengthDistance(uivector* values, size_t length, size_t distance) { /*values in encoded vector are those used by deflate: 0-255: literal bytes 256: end 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) 286-287: invalid*/ unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); size_t pos = values->size; /*TODO: return error when this fails (out of memory)*/ unsigned ok = uivector_resize(values, values->size + 4); if(ok) { values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; values->data[pos + 1] = extra_length; values->data[pos + 2] = dist_code; values->data[pos + 3] = extra_distance; } } /*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 bytes as input because 3 is the minimum match length for deflate*/ static const unsigned HASH_NUM_VALUES = 65536; static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ typedef struct Hash { int* head; /*hash value to head circular pos - can be outdated if went around window*/ /*circular pos to prev circular pos*/ unsigned short* chain; int* val; /*circular pos to hash value*/ /*TODO: do this not only for zeros but for any repeated byte. However for PNG it's always going to be the zeros that dominate, so not important for PNG*/ int* headz; /*similar to head, but for chainz*/ unsigned short* chainz; /*those with same amount of zeros*/ unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ } Hash; static unsigned hash_init(Hash* hash, unsigned windowsize) { unsigned i; hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { return 83; /*alloc fail*/ } /*initialize hash table*/ for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; for(i = 0; i != windowsize; ++i) hash->val[i] = -1; for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ return 0; } static void hash_cleanup(Hash* hash) { lodepng_free(hash->head); lodepng_free(hash->val); lodepng_free(hash->chain); lodepng_free(hash->zeros); lodepng_free(hash->headz); lodepng_free(hash->chainz); } static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { unsigned result = 0; if(pos + 2 < size) { /*A simple shift and xor hash is used. Since the data of PNGs is dominated by zeroes due to the filters, a better hash does not have a significant effect on speed in traversing the chain, and causes more time spend on calculating the hash.*/ result ^= ((unsigned)data[pos + 0] << 0u); result ^= ((unsigned)data[pos + 1] << 4u); result ^= ((unsigned)data[pos + 2] << 8u); } else { size_t amount, i; if(pos >= size) return 0; amount = size - pos; for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); } return result & HASH_BIT_MASK; } static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { const unsigned char* start = data + pos; const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; if(end > data + size) end = data + size; data = start; while(data != end && *data == 0) ++data; /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ return (unsigned)(data - start); } /*wpos = pos & (windowsize - 1)*/ static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { hash->val[wpos] = (int)hashval; if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; hash->head[hashval] = (int)wpos; hash->zeros[wpos] = numzeros; if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; hash->headz[numzeros] = (int)wpos; } /* LZ77-encode the data. Return value is error code. The input are raw bytes, the output is in the form of unsigned integers with codes representing for example literal bytes, or length/distance pairs. It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a sliding window (of windowsize) is used, and all past bytes in that window can be used as the "dictionary". A brute force search through all possible distances would be slow, and this hash technique is one out of several ways to speed this up. */ static unsigned encodeLZ77(uivector* out, Hash* hash, const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, unsigned minmatch, unsigned nicematch, unsigned lazymatching) { size_t pos; unsigned i, error = 0; /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ unsigned numzeros = 0; unsigned offset; /*the offset represents the distance in LZ77 terminology*/ unsigned length; unsigned lazy = 0; unsigned lazylength = 0, lazyoffset = 0; unsigned hashval; unsigned current_offset, current_length; unsigned prev_offset; const unsigned char *lastptr, *foreptr, *backptr; unsigned hashpos; if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; for(pos = inpos; pos < insize; ++pos) { size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ unsigned chainlength = 0; hashval = getHash(in, insize, pos); if(usezeros && hashval == 0) { if(numzeros == 0) numzeros = countZeros(in, insize, pos); else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; } else { numzeros = 0; } updateHashChain(hash, wpos, hashval, numzeros); /*the length and offset found for the current position*/ length = 0; offset = 0; hashpos = hash->chain[wpos]; lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; /*search for the longest string*/ prev_offset = 0; for(;;) { if(chainlength++ >= maxchainlength) break; current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ prev_offset = current_offset; if(current_offset > 0) { /*test the next characters*/ foreptr = &in[pos]; backptr = &in[pos - current_offset]; /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ if(numzeros >= 3) { unsigned skip = hash->zeros[hashpos]; if(skip > numzeros) skip = numzeros; backptr += skip; foreptr += skip; } while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { ++backptr; ++foreptr; } current_length = (unsigned)(foreptr - &in[pos]); if(current_length > length) { length = current_length; /*the longest length*/ offset = current_offset; /*the offset that is related to this longest length*/ /*jump out once a length of max length is found (speed gain). This also jumps out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ if(current_length >= nicematch) break; } } if(hashpos == hash->chain[hashpos]) break; if(numzeros >= 3 && length > numzeros) { hashpos = hash->chainz[hashpos]; if(hash->zeros[hashpos] != numzeros) break; } else { hashpos = hash->chain[hashpos]; /*outdated hash value, happens if particular value was not encountered in whole last window*/ if(hash->val[hashpos] != (int)hashval) break; } } if(lazymatching) { if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { lazy = 1; lazylength = length; lazyoffset = offset; continue; /*try the next byte*/ } if(lazy) { lazy = 0; if(pos == 0) ERROR_BREAK(81); if(length > lazylength + 1) { /*push the previous character as literal*/ if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); } else { length = lazylength; offset = lazyoffset; hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ hash->headz[numzeros] = -1; /*idem*/ --pos; } } } if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); /*encode it as length/distance pair or literal value*/ if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); } else if(length < minmatch || (length == 3 && offset > 4096)) { /*compensate for the fact that longer offsets have more extra bits, a length of only 3 may be not worth it then*/ if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); } else { addLengthDistance(out, length, offset); for(i = 1; i < length; ++i) { ++pos; wpos = pos & (windowsize - 1); hashval = getHash(in, insize, pos); if(usezeros && hashval == 0) { if(numzeros == 0) numzeros = countZeros(in, insize, pos); else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; } else { numzeros = 0; } updateHashChain(hash, wpos, hashval, numzeros); } } } /*end of the loop through each character of input*/ return error; } /* /////////////////////////////////////////////////////////////////////////// */ static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; size_t datapos = 0; for(i = 0; i != numdeflateblocks; ++i) { unsigned BFINAL, BTYPE, LEN, NLEN; unsigned char firstbyte; size_t pos = out->size; BFINAL = (i == numdeflateblocks - 1); BTYPE = 0; LEN = 65535; if(datasize - datapos < 65535u) LEN = (unsigned)datasize - (unsigned)datapos; NLEN = 65535 - LEN; if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); out->data[pos + 0] = firstbyte; out->data[pos + 1] = (unsigned char)(LEN & 255); out->data[pos + 2] = (unsigned char)(LEN >> 8u); out->data[pos + 3] = (unsigned char)(NLEN & 255); out->data[pos + 4] = (unsigned char)(NLEN >> 8u); lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); datapos += LEN; } return 0; } /* write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. tree_ll: the tree for lit and len codes. tree_d: the tree for distance codes. */ static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { size_t i = 0; for(i = 0; i != lz77_encoded->size; ++i) { unsigned val = lz77_encoded->data[i]; writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); if(val > 256) /*for a length code, 3 more things have to be added*/ { unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; unsigned length_extra_bits = lz77_encoded->data[++i]; unsigned distance_code = lz77_encoded->data[++i]; unsigned distance_index = distance_code; unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; unsigned distance_extra_bits = lz77_encoded->data[++i]; writeBits(writer, length_extra_bits, n_length_extra_bits); writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); writeBits(writer, distance_extra_bits, n_distance_extra_bits); } } } /*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, const unsigned char* data, size_t datapos, size_t dataend, const LodePNGCompressSettings* settings, unsigned final) { unsigned error = 0; /* A block is compressed as follows: The PNG data is lz77 encoded, resulting in literal bytes and length/distance pairs. This is then huffman compressed with two huffman trees. One huffman tree is used for the lit and len values ("ll"), another huffman tree is used for the dist values ("d"). These two trees are stored using their code lengths, and to compress even more these code lengths are also run-length encoded and huffman compressed. This gives a huffman tree of code lengths "cl". The code lengths used to describe this third tree are the code length code lengths ("clcl"). */ /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ uivector lz77_encoded; HuffmanTree tree_ll; /*tree for lit,len values*/ HuffmanTree tree_d; /*tree for distance codes*/ HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ unsigned* frequencies_d = 0; /*frequency of dist codes*/ unsigned* frequencies_cl = 0; /*frequency of code length codes*/ unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ size_t datasize = dataend - datapos; /* If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are some analogies: bitlen_lld is to tree_cl what data is to tree_ll and tree_d. bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. */ unsigned BFINAL = final; size_t i; size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; unsigned HLIT, HDIST, HCLEN; uivector_init(&lz77_encoded); HuffmanTree_init(&tree_ll); HuffmanTree_init(&tree_d); HuffmanTree_init(&tree_cl); /* could fit on stack, but >1KB is on the larger side so allocate instead */ frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ /*This while loop never loops due to a break at the end, it is here to allow breaking out of it to the cleanup phase on error conditions.*/ while(!error) { lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); if(settings->use_lz77) { error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, settings->minmatch, settings->nicematch, settings->lazymatching); if(error) break; } else { if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ } /*Count the frequencies of lit, len and dist codes*/ for(i = 0; i != lz77_encoded.size; ++i) { unsigned symbol = lz77_encoded.data[i]; ++frequencies_ll[symbol]; if(symbol > 256) { unsigned dist = lz77_encoded.data[i + 2]; ++frequencies_d[dist]; i += 3; } } frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); if(error) break; /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); if(error) break; numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); /*store the code lengths of both generated trees in bitlen_lld*/ numcodes_lld = numcodes_ll + numcodes_d; bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); /*numcodes_lld_e never needs more size than bitlen_lld*/ bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ numcodes_lld_e = 0; for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), 17 (3-10 zeroes), 18 (11-138 zeroes)*/ for(i = 0; i != numcodes_lld; ++i) { unsigned j = 0; /*amount of repetitions*/ while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { ++j; /*include the first zero*/ if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { bitlen_lld_e[numcodes_lld_e++] = 17; bitlen_lld_e[numcodes_lld_e++] = j - 3; } else /*repeat code 18 supports max 138 zeroes*/ { if(j > 138) j = 138; bitlen_lld_e[numcodes_lld_e++] = 18; bitlen_lld_e[numcodes_lld_e++] = j - 11; } i += (j - 1); } else if(j >= 3) /*repeat code for value other than zero*/ { size_t k; unsigned num = j / 6u, rest = j % 6u; bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; for(k = 0; k < num; ++k) { bitlen_lld_e[numcodes_lld_e++] = 16; bitlen_lld_e[numcodes_lld_e++] = 6 - 3; } if(rest >= 3) { bitlen_lld_e[numcodes_lld_e++] = 16; bitlen_lld_e[numcodes_lld_e++] = rest - 3; } else j -= rest; i += j; } else /*too short to benefit from repeat code*/ { bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; } } /*generate tree_cl, the huffmantree of huffmantrees*/ for(i = 0; i != numcodes_lld_e; ++i) { ++frequencies_cl[bitlen_lld_e[i]]; /*after a repeat code come the bits that specify the number of repetitions, those don't need to be in the frequencies_cl calculation*/ if(bitlen_lld_e[i] >= 16) ++i; } error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); if(error) break; /*compute amount of code-length-code-lengths to output*/ numcodes_cl = NUM_CODE_LENGTH_CODES; /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { numcodes_cl--; } /* Write everything into the output After the BFINAL and BTYPE, the dynamic block consists out of the following: - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN - (HCLEN+4)*3 bits code lengths of code length alphabet - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length alphabet, + possible repetition codes 16, 17, 18) - HDIST + 1 code lengths of distance alphabet (encoded using the code length alphabet, + possible repetition codes 16, 17, 18) - compressed data - 256 (end code) */ /*Write block type*/ writeBits(writer, BFINAL, 1); writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ /*write the HLIT, HDIST and HCLEN values*/ /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies or in the loop for numcodes_cl above, which saves space. */ HLIT = (unsigned)(numcodes_ll - 257); HDIST = (unsigned)(numcodes_d - 1); HCLEN = (unsigned)(numcodes_cl - 4); writeBits(writer, HLIT, 5); writeBits(writer, HDIST, 5); writeBits(writer, HCLEN, 4); /*write the code lengths of the code length alphabet ("bitlen_cl")*/ for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); /*write the lengths of the lit/len AND the dist alphabet*/ for(i = 0; i != numcodes_lld_e; ++i) { writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); /*extra bits of repeat codes*/ if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); } /*write the compressed data symbols*/ writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); /*error: the length of the end code 256 must be larger than 0*/ if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); /*write the end code*/ writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); break; /*end of error-while*/ } /*cleanup*/ uivector_cleanup(&lz77_encoded); HuffmanTree_cleanup(&tree_ll); HuffmanTree_cleanup(&tree_d); HuffmanTree_cleanup(&tree_cl); lodepng_free(frequencies_ll); lodepng_free(frequencies_d); lodepng_free(frequencies_cl); lodepng_free(bitlen_lld); lodepng_free(bitlen_lld_e); return error; } static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, const unsigned char* data, size_t datapos, size_t dataend, const LodePNGCompressSettings* settings, unsigned final) { HuffmanTree tree_ll; /*tree for literal values and length codes*/ HuffmanTree tree_d; /*tree for distance codes*/ unsigned BFINAL = final; unsigned error = 0; size_t i; HuffmanTree_init(&tree_ll); HuffmanTree_init(&tree_d); error = generateFixedLitLenTree(&tree_ll); if(!error) error = generateFixedDistanceTree(&tree_d); if(!error) { writeBits(writer, BFINAL, 1); writeBits(writer, 1, 1); /*first bit of BTYPE*/ writeBits(writer, 0, 1); /*second bit of BTYPE*/ if(settings->use_lz77) /*LZ77 encoded*/ { uivector lz77_encoded; uivector_init(&lz77_encoded); error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, settings->minmatch, settings->nicematch, settings->lazymatching); if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); uivector_cleanup(&lz77_encoded); } else /*no LZ77, but still will be Huffman compressed*/ { for(i = datapos; i < dataend; ++i) { writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); } } /*add END code*/ if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); } /*cleanup*/ HuffmanTree_cleanup(&tree_ll); HuffmanTree_cleanup(&tree_d); return error; } static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { unsigned error = 0; size_t i, blocksize, numdeflateblocks; Hash hash; LodePNGBitWriter writer; LodePNGBitWriter_init(&writer, out); if(settings->btype > 2) return 61; else if(settings->btype == 0) return deflateNoCompression(out, in, insize); else if(settings->btype == 1) blocksize = insize; else /*if(settings->btype == 2)*/ { /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ blocksize = insize / 8u + 8; if(blocksize < 65536) blocksize = 65536; if(blocksize > 262144) blocksize = 262144; } numdeflateblocks = (insize + blocksize - 1) / blocksize; if(numdeflateblocks == 0) numdeflateblocks = 1; error = hash_init(&hash, settings->windowsize); if(!error) { for(i = 0; i != numdeflateblocks && !error; ++i) { unsigned final = (i == numdeflateblocks - 1); size_t start = i * blocksize; size_t end = start + blocksize; if(end > insize) end = insize; if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); } } hash_cleanup(&hash); return error; } unsigned lodepng_deflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { ucvector v = ucvector_init(*out, *outsize); unsigned error = lodepng_deflatev(&v, in, insize, settings); *out = v.data; *outsize = v.size; return error; } static unsigned deflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { if(settings->custom_deflate) { unsigned error = settings->custom_deflate(out, outsize, in, insize, settings); /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/ return error ? 111 : 0; } else { return lodepng_deflate(out, outsize, in, insize, settings); } } #endif /*LODEPNG_COMPILE_DECODER*/ /* ////////////////////////////////////////////////////////////////////////// */ /* / Adler32 / */ /* ////////////////////////////////////////////////////////////////////////// */ static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { unsigned s1 = adler & 0xffffu; unsigned s2 = (adler >> 16u) & 0xffffu; while(len != 0u) { unsigned i; /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ unsigned amount = len > 5552u ? 5552u : len; len -= amount; for(i = 0; i != amount; ++i) { s1 += (*data++); s2 += s1; } s1 %= 65521u; s2 %= 65521u; } return (s2 << 16u) | s1; } /*Return the adler32 of the bytes data[0..len-1]*/ static unsigned adler32(const unsigned char* data, unsigned len) { return update_adler32(1u, data, len); } /* ////////////////////////////////////////////////////////////////////////// */ /* / Zlib / */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_DECODER static unsigned lodepng_zlib_decompressv(ucvector* out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { unsigned error = 0; unsigned CM, CINFO, FDICT; if(insize < 2) return 53; /*error, size of zlib data too small*/ /*read information from zlib header*/ if((in[0] * 256 + in[1]) % 31 != 0) { /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ return 24; } CM = in[0] & 15; CINFO = (in[0] >> 4) & 15; /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ FDICT = (in[1] >> 5) & 1; /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ if(CM != 8 || CINFO > 7) { /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ return 25; } if(FDICT != 0) { /*error: the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary."*/ return 26; } error = inflatev(out, in + 2, insize - 2, settings); if(error) return error; if(!settings->ignore_adler32) { unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); unsigned checksum = adler32(out->data, (unsigned)(out->size)); if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ } return 0; /*no error*/ } unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { ucvector v = ucvector_init(*out, *outsize); unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); *out = v.data; *outsize = v.size; return error; } /*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { unsigned error; if(settings->custom_zlib) { error = settings->custom_zlib(out, outsize, in, insize, settings); if(error) { /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/ error = 110; /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ if(settings->max_output_size && *outsize > settings->max_output_size) error = 109; } } else { ucvector v = ucvector_init(*out, *outsize); if(expected_size) { /*reserve the memory to avoid intermediate reallocations*/ ucvector_resize(&v, *outsize + expected_size); v.size = *outsize; } error = lodepng_zlib_decompressv(&v, in, insize, settings); *out = v.data; *outsize = v.size; } return error; } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { size_t i; unsigned error; unsigned char* deflatedata = 0; size_t deflatesize = 0; error = deflate(&deflatedata, &deflatesize, in, insize, settings); *out = NULL; *outsize = 0; if(!error) { *outsize = deflatesize + 6; *out = (unsigned char*)lodepng_malloc(*outsize); if(!*out) error = 83; /*alloc fail*/ } if(!error) { unsigned ADLER32 = adler32(in, (unsigned)insize); /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ unsigned FLEVEL = 0; unsigned FDICT = 0; unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; unsigned FCHECK = 31 - CMFFLG % 31; CMFFLG += FCHECK; (*out)[0] = (unsigned char)(CMFFLG >> 8); (*out)[1] = (unsigned char)(CMFFLG & 255); for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); } lodepng_free(deflatedata); return error; } /* compress using the default or custom zlib function */ static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { if(settings->custom_zlib) { unsigned error = settings->custom_zlib(out, outsize, in, insize, settings); /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/ return error ? 111 : 0; } else { return lodepng_zlib_compress(out, outsize, in, insize, settings); } } #endif /*LODEPNG_COMPILE_ENCODER*/ #else /*no LODEPNG_COMPILE_ZLIB*/ #ifdef LODEPNG_COMPILE_DECODER static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ (void)expected_size; return settings->custom_zlib(out, outsize, in, insize, settings); } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings) { if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ return settings->custom_zlib(out, outsize, in, insize, settings); } #endif /*LODEPNG_COMPILE_ENCODER*/ #endif /*LODEPNG_COMPILE_ZLIB*/ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_ENCODER /*this is a good tradeoff between speed and compression ratio*/ #define DEFAULT_WINDOWSIZE 2048 void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ settings->btype = 2; settings->use_lz77 = 1; settings->windowsize = DEFAULT_WINDOWSIZE; settings->minmatch = 3; settings->nicematch = 128; settings->lazymatching = 1; settings->custom_zlib = 0; settings->custom_deflate = 0; settings->custom_context = 0; } const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_DECODER void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { settings->ignore_adler32 = 0; settings->ignore_nlen = 0; settings->max_output_size = 0; settings->custom_zlib = 0; settings->custom_inflate = 0; settings->custom_context = 0; } const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0}; #endif /*LODEPNG_COMPILE_DECODER*/ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /* // End of Zlib related code. Begin of PNG related code. // */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_PNG /* ////////////////////////////////////////////////////////////////////////// */ /* / CRC32 / */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_CRC static const unsigned lodepng_crc32_table0[256] = { 0x00000000u, 0x77073096u, 0xee0e612cu, 0x990951bau, 0x076dc419u, 0x706af48fu, 0xe963a535u, 0x9e6495a3u, 0x0edb8832u, 0x79dcb8a4u, 0xe0d5e91eu, 0x97d2d988u, 0x09b64c2bu, 0x7eb17cbdu, 0xe7b82d07u, 0x90bf1d91u, 0x1db71064u, 0x6ab020f2u, 0xf3b97148u, 0x84be41deu, 0x1adad47du, 0x6ddde4ebu, 0xf4d4b551u, 0x83d385c7u, 0x136c9856u, 0x646ba8c0u, 0xfd62f97au, 0x8a65c9ecu, 0x14015c4fu, 0x63066cd9u, 0xfa0f3d63u, 0x8d080df5u, 0x3b6e20c8u, 0x4c69105eu, 0xd56041e4u, 0xa2677172u, 0x3c03e4d1u, 0x4b04d447u, 0xd20d85fdu, 0xa50ab56bu, 0x35b5a8fau, 0x42b2986cu, 0xdbbbc9d6u, 0xacbcf940u, 0x32d86ce3u, 0x45df5c75u, 0xdcd60dcfu, 0xabd13d59u, 0x26d930acu, 0x51de003au, 0xc8d75180u, 0xbfd06116u, 0x21b4f4b5u, 0x56b3c423u, 0xcfba9599u, 0xb8bda50fu, 0x2802b89eu, 0x5f058808u, 0xc60cd9b2u, 0xb10be924u, 0x2f6f7c87u, 0x58684c11u, 0xc1611dabu, 0xb6662d3du, 0x76dc4190u, 0x01db7106u, 0x98d220bcu, 0xefd5102au, 0x71b18589u, 0x06b6b51fu, 0x9fbfe4a5u, 0xe8b8d433u, 0x7807c9a2u, 0x0f00f934u, 0x9609a88eu, 0xe10e9818u, 0x7f6a0dbbu, 0x086d3d2du, 0x91646c97u, 0xe6635c01u, 0x6b6b51f4u, 0x1c6c6162u, 0x856530d8u, 0xf262004eu, 0x6c0695edu, 0x1b01a57bu, 0x8208f4c1u, 0xf50fc457u, 0x65b0d9c6u, 0x12b7e950u, 0x8bbeb8eau, 0xfcb9887cu, 0x62dd1ddfu, 0x15da2d49u, 0x8cd37cf3u, 0xfbd44c65u, 0x4db26158u, 0x3ab551ceu, 0xa3bc0074u, 0xd4bb30e2u, 0x4adfa541u, 0x3dd895d7u, 0xa4d1c46du, 0xd3d6f4fbu, 0x4369e96au, 0x346ed9fcu, 0xad678846u, 0xda60b8d0u, 0x44042d73u, 0x33031de5u, 0xaa0a4c5fu, 0xdd0d7cc9u, 0x5005713cu, 0x270241aau, 0xbe0b1010u, 0xc90c2086u, 0x5768b525u, 0x206f85b3u, 0xb966d409u, 0xce61e49fu, 0x5edef90eu, 0x29d9c998u, 0xb0d09822u, 0xc7d7a8b4u, 0x59b33d17u, 0x2eb40d81u, 0xb7bd5c3bu, 0xc0ba6cadu, 0xedb88320u, 0x9abfb3b6u, 0x03b6e20cu, 0x74b1d29au, 0xead54739u, 0x9dd277afu, 0x04db2615u, 0x73dc1683u, 0xe3630b12u, 0x94643b84u, 0x0d6d6a3eu, 0x7a6a5aa8u, 0xe40ecf0bu, 0x9309ff9du, 0x0a00ae27u, 0x7d079eb1u, 0xf00f9344u, 0x8708a3d2u, 0x1e01f268u, 0x6906c2feu, 0xf762575du, 0x806567cbu, 0x196c3671u, 0x6e6b06e7u, 0xfed41b76u, 0x89d32be0u, 0x10da7a5au, 0x67dd4accu, 0xf9b9df6fu, 0x8ebeeff9u, 0x17b7be43u, 0x60b08ed5u, 0xd6d6a3e8u, 0xa1d1937eu, 0x38d8c2c4u, 0x4fdff252u, 0xd1bb67f1u, 0xa6bc5767u, 0x3fb506ddu, 0x48b2364bu, 0xd80d2bdau, 0xaf0a1b4cu, 0x36034af6u, 0x41047a60u, 0xdf60efc3u, 0xa867df55u, 0x316e8eefu, 0x4669be79u, 0xcb61b38cu, 0xbc66831au, 0x256fd2a0u, 0x5268e236u, 0xcc0c7795u, 0xbb0b4703u, 0x220216b9u, 0x5505262fu, 0xc5ba3bbeu, 0xb2bd0b28u, 0x2bb45a92u, 0x5cb36a04u, 0xc2d7ffa7u, 0xb5d0cf31u, 0x2cd99e8bu, 0x5bdeae1du, 0x9b64c2b0u, 0xec63f226u, 0x756aa39cu, 0x026d930au, 0x9c0906a9u, 0xeb0e363fu, 0x72076785u, 0x05005713u, 0x95bf4a82u, 0xe2b87a14u, 0x7bb12baeu, 0x0cb61b38u, 0x92d28e9bu, 0xe5d5be0du, 0x7cdcefb7u, 0x0bdbdf21u, 0x86d3d2d4u, 0xf1d4e242u, 0x68ddb3f8u, 0x1fda836eu, 0x81be16cdu, 0xf6b9265bu, 0x6fb077e1u, 0x18b74777u, 0x88085ae6u, 0xff0f6a70u, 0x66063bcau, 0x11010b5cu, 0x8f659effu, 0xf862ae69u, 0x616bffd3u, 0x166ccf45u, 0xa00ae278u, 0xd70dd2eeu, 0x4e048354u, 0x3903b3c2u, 0xa7672661u, 0xd06016f7u, 0x4969474du, 0x3e6e77dbu, 0xaed16a4au, 0xd9d65adcu, 0x40df0b66u, 0x37d83bf0u, 0xa9bcae53u, 0xdebb9ec5u, 0x47b2cf7fu, 0x30b5ffe9u, 0xbdbdf21cu, 0xcabac28au, 0x53b39330u, 0x24b4a3a6u, 0xbad03605u, 0xcdd70693u, 0x54de5729u, 0x23d967bfu, 0xb3667a2eu, 0xc4614ab8u, 0x5d681b02u, 0x2a6f2b94u, 0xb40bbe37u, 0xc30c8ea1u, 0x5a05df1bu, 0x2d02ef8du }; static const unsigned lodepng_crc32_table1[256] = { 0x00000000u, 0x191b3141u, 0x32366282u, 0x2b2d53c3u, 0x646cc504u, 0x7d77f445u, 0x565aa786u, 0x4f4196c7u, 0xc8d98a08u, 0xd1c2bb49u, 0xfaefe88au, 0xe3f4d9cbu, 0xacb54f0cu, 0xb5ae7e4du, 0x9e832d8eu, 0x87981ccfu, 0x4ac21251u, 0x53d92310u, 0x78f470d3u, 0x61ef4192u, 0x2eaed755u, 0x37b5e614u, 0x1c98b5d7u, 0x05838496u, 0x821b9859u, 0x9b00a918u, 0xb02dfadbu, 0xa936cb9au, 0xe6775d5du, 0xff6c6c1cu, 0xd4413fdfu, 0xcd5a0e9eu, 0x958424a2u, 0x8c9f15e3u, 0xa7b24620u, 0xbea97761u, 0xf1e8e1a6u, 0xe8f3d0e7u, 0xc3de8324u, 0xdac5b265u, 0x5d5daeaau, 0x44469febu, 0x6f6bcc28u, 0x7670fd69u, 0x39316baeu, 0x202a5aefu, 0x0b07092cu, 0x121c386du, 0xdf4636f3u, 0xc65d07b2u, 0xed705471u, 0xf46b6530u, 0xbb2af3f7u, 0xa231c2b6u, 0x891c9175u, 0x9007a034u, 0x179fbcfbu, 0x0e848dbau, 0x25a9de79u, 0x3cb2ef38u, 0x73f379ffu, 0x6ae848beu, 0x41c51b7du, 0x58de2a3cu, 0xf0794f05u, 0xe9627e44u, 0xc24f2d87u, 0xdb541cc6u, 0x94158a01u, 0x8d0ebb40u, 0xa623e883u, 0xbf38d9c2u, 0x38a0c50du, 0x21bbf44cu, 0x0a96a78fu, 0x138d96ceu, 0x5ccc0009u, 0x45d73148u, 0x6efa628bu, 0x77e153cau, 0xbabb5d54u, 0xa3a06c15u, 0x888d3fd6u, 0x91960e97u, 0xded79850u, 0xc7cca911u, 0xece1fad2u, 0xf5facb93u, 0x7262d75cu, 0x6b79e61du, 0x4054b5deu, 0x594f849fu, 0x160e1258u, 0x0f152319u, 0x243870dau, 0x3d23419bu, 0x65fd6ba7u, 0x7ce65ae6u, 0x57cb0925u, 0x4ed03864u, 0x0191aea3u, 0x188a9fe2u, 0x33a7cc21u, 0x2abcfd60u, 0xad24e1afu, 0xb43fd0eeu, 0x9f12832du, 0x8609b26cu, 0xc94824abu, 0xd05315eau, 0xfb7e4629u, 0xe2657768u, 0x2f3f79f6u, 0x362448b7u, 0x1d091b74u, 0x04122a35u, 0x4b53bcf2u, 0x52488db3u, 0x7965de70u, 0x607eef31u, 0xe7e6f3feu, 0xfefdc2bfu, 0xd5d0917cu, 0xcccba03du, 0x838a36fau, 0x9a9107bbu, 0xb1bc5478u, 0xa8a76539u, 0x3b83984bu, 0x2298a90au, 0x09b5fac9u, 0x10aecb88u, 0x5fef5d4fu, 0x46f46c0eu, 0x6dd93fcdu, 0x74c20e8cu, 0xf35a1243u, 0xea412302u, 0xc16c70c1u, 0xd8774180u, 0x9736d747u, 0x8e2de606u, 0xa500b5c5u, 0xbc1b8484u, 0x71418a1au, 0x685abb5bu, 0x4377e898u, 0x5a6cd9d9u, 0x152d4f1eu, 0x0c367e5fu, 0x271b2d9cu, 0x3e001cddu, 0xb9980012u, 0xa0833153u, 0x8bae6290u, 0x92b553d1u, 0xddf4c516u, 0xc4eff457u, 0xefc2a794u, 0xf6d996d5u, 0xae07bce9u, 0xb71c8da8u, 0x9c31de6bu, 0x852aef2au, 0xca6b79edu, 0xd37048acu, 0xf85d1b6fu, 0xe1462a2eu, 0x66de36e1u, 0x7fc507a0u, 0x54e85463u, 0x4df36522u, 0x02b2f3e5u, 0x1ba9c2a4u, 0x30849167u, 0x299fa026u, 0xe4c5aeb8u, 0xfdde9ff9u, 0xd6f3cc3au, 0xcfe8fd7bu, 0x80a96bbcu, 0x99b25afdu, 0xb29f093eu, 0xab84387fu, 0x2c1c24b0u, 0x350715f1u, 0x1e2a4632u, 0x07317773u, 0x4870e1b4u, 0x516bd0f5u, 0x7a468336u, 0x635db277u, 0xcbfad74eu, 0xd2e1e60fu, 0xf9ccb5ccu, 0xe0d7848du, 0xaf96124au, 0xb68d230bu, 0x9da070c8u, 0x84bb4189u, 0x03235d46u, 0x1a386c07u, 0x31153fc4u, 0x280e0e85u, 0x674f9842u, 0x7e54a903u, 0x5579fac0u, 0x4c62cb81u, 0x8138c51fu, 0x9823f45eu, 0xb30ea79du, 0xaa1596dcu, 0xe554001bu, 0xfc4f315au, 0xd7626299u, 0xce7953d8u, 0x49e14f17u, 0x50fa7e56u, 0x7bd72d95u, 0x62cc1cd4u, 0x2d8d8a13u, 0x3496bb52u, 0x1fbbe891u, 0x06a0d9d0u, 0x5e7ef3ecu, 0x4765c2adu, 0x6c48916eu, 0x7553a02fu, 0x3a1236e8u, 0x230907a9u, 0x0824546au, 0x113f652bu, 0x96a779e4u, 0x8fbc48a5u, 0xa4911b66u, 0xbd8a2a27u, 0xf2cbbce0u, 0xebd08da1u, 0xc0fdde62u, 0xd9e6ef23u, 0x14bce1bdu, 0x0da7d0fcu, 0x268a833fu, 0x3f91b27eu, 0x70d024b9u, 0x69cb15f8u, 0x42e6463bu, 0x5bfd777au, 0xdc656bb5u, 0xc57e5af4u, 0xee530937u, 0xf7483876u, 0xb809aeb1u, 0xa1129ff0u, 0x8a3fcc33u, 0x9324fd72u }; static const unsigned lodepng_crc32_table2[256] = { 0x00000000u, 0x01c26a37u, 0x0384d46eu, 0x0246be59u, 0x0709a8dcu, 0x06cbc2ebu, 0x048d7cb2u, 0x054f1685u, 0x0e1351b8u, 0x0fd13b8fu, 0x0d9785d6u, 0x0c55efe1u, 0x091af964u, 0x08d89353u, 0x0a9e2d0au, 0x0b5c473du, 0x1c26a370u, 0x1de4c947u, 0x1fa2771eu, 0x1e601d29u, 0x1b2f0bacu, 0x1aed619bu, 0x18abdfc2u, 0x1969b5f5u, 0x1235f2c8u, 0x13f798ffu, 0x11b126a6u, 0x10734c91u, 0x153c5a14u, 0x14fe3023u, 0x16b88e7au, 0x177ae44du, 0x384d46e0u, 0x398f2cd7u, 0x3bc9928eu, 0x3a0bf8b9u, 0x3f44ee3cu, 0x3e86840bu, 0x3cc03a52u, 0x3d025065u, 0x365e1758u, 0x379c7d6fu, 0x35dac336u, 0x3418a901u, 0x3157bf84u, 0x3095d5b3u, 0x32d36beau, 0x331101ddu, 0x246be590u, 0x25a98fa7u, 0x27ef31feu, 0x262d5bc9u, 0x23624d4cu, 0x22a0277bu, 0x20e69922u, 0x2124f315u, 0x2a78b428u, 0x2bbade1fu, 0x29fc6046u, 0x283e0a71u, 0x2d711cf4u, 0x2cb376c3u, 0x2ef5c89au, 0x2f37a2adu, 0x709a8dc0u, 0x7158e7f7u, 0x731e59aeu, 0x72dc3399u, 0x7793251cu, 0x76514f2bu, 0x7417f172u, 0x75d59b45u, 0x7e89dc78u, 0x7f4bb64fu, 0x7d0d0816u, 0x7ccf6221u, 0x798074a4u, 0x78421e93u, 0x7a04a0cau, 0x7bc6cafdu, 0x6cbc2eb0u, 0x6d7e4487u, 0x6f38fadeu, 0x6efa90e9u, 0x6bb5866cu, 0x6a77ec5bu, 0x68315202u, 0x69f33835u, 0x62af7f08u, 0x636d153fu, 0x612bab66u, 0x60e9c151u, 0x65a6d7d4u, 0x6464bde3u, 0x662203bau, 0x67e0698du, 0x48d7cb20u, 0x4915a117u, 0x4b531f4eu, 0x4a917579u, 0x4fde63fcu, 0x4e1c09cbu, 0x4c5ab792u, 0x4d98dda5u, 0x46c49a98u, 0x4706f0afu, 0x45404ef6u, 0x448224c1u, 0x41cd3244u, 0x400f5873u, 0x4249e62au, 0x438b8c1du, 0x54f16850u, 0x55330267u, 0x5775bc3eu, 0x56b7d609u, 0x53f8c08cu, 0x523aaabbu, 0x507c14e2u, 0x51be7ed5u, 0x5ae239e8u, 0x5b2053dfu, 0x5966ed86u, 0x58a487b1u, 0x5deb9134u, 0x5c29fb03u, 0x5e6f455au, 0x5fad2f6du, 0xe1351b80u, 0xe0f771b7u, 0xe2b1cfeeu, 0xe373a5d9u, 0xe63cb35cu, 0xe7fed96bu, 0xe5b86732u, 0xe47a0d05u, 0xef264a38u, 0xeee4200fu, 0xeca29e56u, 0xed60f461u, 0xe82fe2e4u, 0xe9ed88d3u, 0xebab368au, 0xea695cbdu, 0xfd13b8f0u, 0xfcd1d2c7u, 0xfe976c9eu, 0xff5506a9u, 0xfa1a102cu, 0xfbd87a1bu, 0xf99ec442u, 0xf85cae75u, 0xf300e948u, 0xf2c2837fu, 0xf0843d26u, 0xf1465711u, 0xf4094194u, 0xf5cb2ba3u, 0xf78d95fau, 0xf64fffcdu, 0xd9785d60u, 0xd8ba3757u, 0xdafc890eu, 0xdb3ee339u, 0xde71f5bcu, 0xdfb39f8bu, 0xddf521d2u, 0xdc374be5u, 0xd76b0cd8u, 0xd6a966efu, 0xd4efd8b6u, 0xd52db281u, 0xd062a404u, 0xd1a0ce33u, 0xd3e6706au, 0xd2241a5du, 0xc55efe10u, 0xc49c9427u, 0xc6da2a7eu, 0xc7184049u, 0xc25756ccu, 0xc3953cfbu, 0xc1d382a2u, 0xc011e895u, 0xcb4dafa8u, 0xca8fc59fu, 0xc8c97bc6u, 0xc90b11f1u, 0xcc440774u, 0xcd866d43u, 0xcfc0d31au, 0xce02b92du, 0x91af9640u, 0x906dfc77u, 0x922b422eu, 0x93e92819u, 0x96a63e9cu, 0x976454abu, 0x9522eaf2u, 0x94e080c5u, 0x9fbcc7f8u, 0x9e7eadcfu, 0x9c381396u, 0x9dfa79a1u, 0x98b56f24u, 0x99770513u, 0x9b31bb4au, 0x9af3d17du, 0x8d893530u, 0x8c4b5f07u, 0x8e0de15eu, 0x8fcf8b69u, 0x8a809decu, 0x8b42f7dbu, 0x89044982u, 0x88c623b5u, 0x839a6488u, 0x82580ebfu, 0x801eb0e6u, 0x81dcdad1u, 0x8493cc54u, 0x8551a663u, 0x8717183au, 0x86d5720du, 0xa9e2d0a0u, 0xa820ba97u, 0xaa6604ceu, 0xaba46ef9u, 0xaeeb787cu, 0xaf29124bu, 0xad6fac12u, 0xacadc625u, 0xa7f18118u, 0xa633eb2fu, 0xa4755576u, 0xa5b73f41u, 0xa0f829c4u, 0xa13a43f3u, 0xa37cfdaau, 0xa2be979du, 0xb5c473d0u, 0xb40619e7u, 0xb640a7beu, 0xb782cd89u, 0xb2cddb0cu, 0xb30fb13bu, 0xb1490f62u, 0xb08b6555u, 0xbbd72268u, 0xba15485fu, 0xb853f606u, 0xb9919c31u, 0xbcde8ab4u, 0xbd1ce083u, 0xbf5a5edau, 0xbe9834edu }; static const unsigned lodepng_crc32_table3[256] = { 0x00000000u, 0xb8bc6765u, 0xaa09c88bu, 0x12b5afeeu, 0x8f629757u, 0x37def032u, 0x256b5fdcu, 0x9dd738b9u, 0xc5b428efu, 0x7d084f8au, 0x6fbde064u, 0xd7018701u, 0x4ad6bfb8u, 0xf26ad8ddu, 0xe0df7733u, 0x58631056u, 0x5019579fu, 0xe8a530fau, 0xfa109f14u, 0x42acf871u, 0xdf7bc0c8u, 0x67c7a7adu, 0x75720843u, 0xcdce6f26u, 0x95ad7f70u, 0x2d111815u, 0x3fa4b7fbu, 0x8718d09eu, 0x1acfe827u, 0xa2738f42u, 0xb0c620acu, 0x087a47c9u, 0xa032af3eu, 0x188ec85bu, 0x0a3b67b5u, 0xb28700d0u, 0x2f503869u, 0x97ec5f0cu, 0x8559f0e2u, 0x3de59787u, 0x658687d1u, 0xdd3ae0b4u, 0xcf8f4f5au, 0x7733283fu, 0xeae41086u, 0x525877e3u, 0x40edd80du, 0xf851bf68u, 0xf02bf8a1u, 0x48979fc4u, 0x5a22302au, 0xe29e574fu, 0x7f496ff6u, 0xc7f50893u, 0xd540a77du, 0x6dfcc018u, 0x359fd04eu, 0x8d23b72bu, 0x9f9618c5u, 0x272a7fa0u, 0xbafd4719u, 0x0241207cu, 0x10f48f92u, 0xa848e8f7u, 0x9b14583du, 0x23a83f58u, 0x311d90b6u, 0x89a1f7d3u, 0x1476cf6au, 0xaccaa80fu, 0xbe7f07e1u, 0x06c36084u, 0x5ea070d2u, 0xe61c17b7u, 0xf4a9b859u, 0x4c15df3cu, 0xd1c2e785u, 0x697e80e0u, 0x7bcb2f0eu, 0xc377486bu, 0xcb0d0fa2u, 0x73b168c7u, 0x6104c729u, 0xd9b8a04cu, 0x446f98f5u, 0xfcd3ff90u, 0xee66507eu, 0x56da371bu, 0x0eb9274du, 0xb6054028u, 0xa4b0efc6u, 0x1c0c88a3u, 0x81dbb01au, 0x3967d77fu, 0x2bd27891u, 0x936e1ff4u, 0x3b26f703u, 0x839a9066u, 0x912f3f88u, 0x299358edu, 0xb4446054u, 0x0cf80731u, 0x1e4da8dfu, 0xa6f1cfbau, 0xfe92dfecu, 0x462eb889u, 0x549b1767u, 0xec277002u, 0x71f048bbu, 0xc94c2fdeu, 0xdbf98030u, 0x6345e755u, 0x6b3fa09cu, 0xd383c7f9u, 0xc1366817u, 0x798a0f72u, 0xe45d37cbu, 0x5ce150aeu, 0x4e54ff40u, 0xf6e89825u, 0xae8b8873u, 0x1637ef16u, 0x048240f8u, 0xbc3e279du, 0x21e91f24u, 0x99557841u, 0x8be0d7afu, 0x335cb0cau, 0xed59b63bu, 0x55e5d15eu, 0x47507eb0u, 0xffec19d5u, 0x623b216cu, 0xda874609u, 0xc832e9e7u, 0x708e8e82u, 0x28ed9ed4u, 0x9051f9b1u, 0x82e4565fu, 0x3a58313au, 0xa78f0983u, 0x1f336ee6u, 0x0d86c108u, 0xb53aa66du, 0xbd40e1a4u, 0x05fc86c1u, 0x1749292fu, 0xaff54e4au, 0x322276f3u, 0x8a9e1196u, 0x982bbe78u, 0x2097d91du, 0x78f4c94bu, 0xc048ae2eu, 0xd2fd01c0u, 0x6a4166a5u, 0xf7965e1cu, 0x4f2a3979u, 0x5d9f9697u, 0xe523f1f2u, 0x4d6b1905u, 0xf5d77e60u, 0xe762d18eu, 0x5fdeb6ebu, 0xc2098e52u, 0x7ab5e937u, 0x680046d9u, 0xd0bc21bcu, 0x88df31eau, 0x3063568fu, 0x22d6f961u, 0x9a6a9e04u, 0x07bda6bdu, 0xbf01c1d8u, 0xadb46e36u, 0x15080953u, 0x1d724e9au, 0xa5ce29ffu, 0xb77b8611u, 0x0fc7e174u, 0x9210d9cdu, 0x2aacbea8u, 0x38191146u, 0x80a57623u, 0xd8c66675u, 0x607a0110u, 0x72cfaefeu, 0xca73c99bu, 0x57a4f122u, 0xef189647u, 0xfdad39a9u, 0x45115eccu, 0x764dee06u, 0xcef18963u, 0xdc44268du, 0x64f841e8u, 0xf92f7951u, 0x41931e34u, 0x5326b1dau, 0xeb9ad6bfu, 0xb3f9c6e9u, 0x0b45a18cu, 0x19f00e62u, 0xa14c6907u, 0x3c9b51beu, 0x842736dbu, 0x96929935u, 0x2e2efe50u, 0x2654b999u, 0x9ee8defcu, 0x8c5d7112u, 0x34e11677u, 0xa9362eceu, 0x118a49abu, 0x033fe645u, 0xbb838120u, 0xe3e09176u, 0x5b5cf613u, 0x49e959fdu, 0xf1553e98u, 0x6c820621u, 0xd43e6144u, 0xc68bceaau, 0x7e37a9cfu, 0xd67f4138u, 0x6ec3265du, 0x7c7689b3u, 0xc4caeed6u, 0x591dd66fu, 0xe1a1b10au, 0xf3141ee4u, 0x4ba87981u, 0x13cb69d7u, 0xab770eb2u, 0xb9c2a15cu, 0x017ec639u, 0x9ca9fe80u, 0x241599e5u, 0x36a0360bu, 0x8e1c516eu, 0x866616a7u, 0x3eda71c2u, 0x2c6fde2cu, 0x94d3b949u, 0x090481f0u, 0xb1b8e695u, 0xa30d497bu, 0x1bb12e1eu, 0x43d23e48u, 0xfb6e592du, 0xe9dbf6c3u, 0x516791a6u, 0xccb0a91fu, 0x740cce7au, 0x66b96194u, 0xde0506f1u }; static const unsigned lodepng_crc32_table4[256] = { 0x00000000u, 0x3d6029b0u, 0x7ac05360u, 0x47a07ad0u, 0xf580a6c0u, 0xc8e08f70u, 0x8f40f5a0u, 0xb220dc10u, 0x30704bc1u, 0x0d106271u, 0x4ab018a1u, 0x77d03111u, 0xc5f0ed01u, 0xf890c4b1u, 0xbf30be61u, 0x825097d1u, 0x60e09782u, 0x5d80be32u, 0x1a20c4e2u, 0x2740ed52u, 0x95603142u, 0xa80018f2u, 0xefa06222u, 0xd2c04b92u, 0x5090dc43u, 0x6df0f5f3u, 0x2a508f23u, 0x1730a693u, 0xa5107a83u, 0x98705333u, 0xdfd029e3u, 0xe2b00053u, 0xc1c12f04u, 0xfca106b4u, 0xbb017c64u, 0x866155d4u, 0x344189c4u, 0x0921a074u, 0x4e81daa4u, 0x73e1f314u, 0xf1b164c5u, 0xccd14d75u, 0x8b7137a5u, 0xb6111e15u, 0x0431c205u, 0x3951ebb5u, 0x7ef19165u, 0x4391b8d5u, 0xa121b886u, 0x9c419136u, 0xdbe1ebe6u, 0xe681c256u, 0x54a11e46u, 0x69c137f6u, 0x2e614d26u, 0x13016496u, 0x9151f347u, 0xac31daf7u, 0xeb91a027u, 0xd6f18997u, 0x64d15587u, 0x59b17c37u, 0x1e1106e7u, 0x23712f57u, 0x58f35849u, 0x659371f9u, 0x22330b29u, 0x1f532299u, 0xad73fe89u, 0x9013d739u, 0xd7b3ade9u, 0xead38459u, 0x68831388u, 0x55e33a38u, 0x124340e8u, 0x2f236958u, 0x9d03b548u, 0xa0639cf8u, 0xe7c3e628u, 0xdaa3cf98u, 0x3813cfcbu, 0x0573e67bu, 0x42d39cabu, 0x7fb3b51bu, 0xcd93690bu, 0xf0f340bbu, 0xb7533a6bu, 0x8a3313dbu, 0x0863840au, 0x3503adbau, 0x72a3d76au, 0x4fc3fedau, 0xfde322cau, 0xc0830b7au, 0x872371aau, 0xba43581au, 0x9932774du, 0xa4525efdu, 0xe3f2242du, 0xde920d9du, 0x6cb2d18du, 0x51d2f83du, 0x167282edu, 0x2b12ab5du, 0xa9423c8cu, 0x9422153cu, 0xd3826fecu, 0xeee2465cu, 0x5cc29a4cu, 0x61a2b3fcu, 0x2602c92cu, 0x1b62e09cu, 0xf9d2e0cfu, 0xc4b2c97fu, 0x8312b3afu, 0xbe729a1fu, 0x0c52460fu, 0x31326fbfu, 0x7692156fu, 0x4bf23cdfu, 0xc9a2ab0eu, 0xf4c282beu, 0xb362f86eu, 0x8e02d1deu, 0x3c220dceu, 0x0142247eu, 0x46e25eaeu, 0x7b82771eu, 0xb1e6b092u, 0x8c869922u, 0xcb26e3f2u, 0xf646ca42u, 0x44661652u, 0x79063fe2u, 0x3ea64532u, 0x03c66c82u, 0x8196fb53u, 0xbcf6d2e3u, 0xfb56a833u, 0xc6368183u, 0x74165d93u, 0x49767423u, 0x0ed60ef3u, 0x33b62743u, 0xd1062710u, 0xec660ea0u, 0xabc67470u, 0x96a65dc0u, 0x248681d0u, 0x19e6a860u, 0x5e46d2b0u, 0x6326fb00u, 0xe1766cd1u, 0xdc164561u, 0x9bb63fb1u, 0xa6d61601u, 0x14f6ca11u, 0x2996e3a1u, 0x6e369971u, 0x5356b0c1u, 0x70279f96u, 0x4d47b626u, 0x0ae7ccf6u, 0x3787e546u, 0x85a73956u, 0xb8c710e6u, 0xff676a36u, 0xc2074386u, 0x4057d457u, 0x7d37fde7u, 0x3a978737u, 0x07f7ae87u, 0xb5d77297u, 0x88b75b27u, 0xcf1721f7u, 0xf2770847u, 0x10c70814u, 0x2da721a4u, 0x6a075b74u, 0x576772c4u, 0xe547aed4u, 0xd8278764u, 0x9f87fdb4u, 0xa2e7d404u, 0x20b743d5u, 0x1dd76a65u, 0x5a7710b5u, 0x67173905u, 0xd537e515u, 0xe857cca5u, 0xaff7b675u, 0x92979fc5u, 0xe915e8dbu, 0xd475c16bu, 0x93d5bbbbu, 0xaeb5920bu, 0x1c954e1bu, 0x21f567abu, 0x66551d7bu, 0x5b3534cbu, 0xd965a31au, 0xe4058aaau, 0xa3a5f07au, 0x9ec5d9cau, 0x2ce505dau, 0x11852c6au, 0x562556bau, 0x6b457f0au, 0x89f57f59u, 0xb49556e9u, 0xf3352c39u, 0xce550589u, 0x7c75d999u, 0x4115f029u, 0x06b58af9u, 0x3bd5a349u, 0xb9853498u, 0x84e51d28u, 0xc34567f8u, 0xfe254e48u, 0x4c059258u, 0x7165bbe8u, 0x36c5c138u, 0x0ba5e888u, 0x28d4c7dfu, 0x15b4ee6fu, 0x521494bfu, 0x6f74bd0fu, 0xdd54611fu, 0xe03448afu, 0xa794327fu, 0x9af41bcfu, 0x18a48c1eu, 0x25c4a5aeu, 0x6264df7eu, 0x5f04f6ceu, 0xed242adeu, 0xd044036eu, 0x97e479beu, 0xaa84500eu, 0x4834505du, 0x755479edu, 0x32f4033du, 0x0f942a8du, 0xbdb4f69du, 0x80d4df2du, 0xc774a5fdu, 0xfa148c4du, 0x78441b9cu, 0x4524322cu, 0x028448fcu, 0x3fe4614cu, 0x8dc4bd5cu, 0xb0a494ecu, 0xf704ee3cu, 0xca64c78cu }; static const unsigned lodepng_crc32_table5[256] = { 0x00000000u, 0xcb5cd3a5u, 0x4dc8a10bu, 0x869472aeu, 0x9b914216u, 0x50cd91b3u, 0xd659e31du, 0x1d0530b8u, 0xec53826du, 0x270f51c8u, 0xa19b2366u, 0x6ac7f0c3u, 0x77c2c07bu, 0xbc9e13deu, 0x3a0a6170u, 0xf156b2d5u, 0x03d6029bu, 0xc88ad13eu, 0x4e1ea390u, 0x85427035u, 0x9847408du, 0x531b9328u, 0xd58fe186u, 0x1ed33223u, 0xef8580f6u, 0x24d95353u, 0xa24d21fdu, 0x6911f258u, 0x7414c2e0u, 0xbf481145u, 0x39dc63ebu, 0xf280b04eu, 0x07ac0536u, 0xccf0d693u, 0x4a64a43du, 0x81387798u, 0x9c3d4720u, 0x57619485u, 0xd1f5e62bu, 0x1aa9358eu, 0xebff875bu, 0x20a354feu, 0xa6372650u, 0x6d6bf5f5u, 0x706ec54du, 0xbb3216e8u, 0x3da66446u, 0xf6fab7e3u, 0x047a07adu, 0xcf26d408u, 0x49b2a6a6u, 0x82ee7503u, 0x9feb45bbu, 0x54b7961eu, 0xd223e4b0u, 0x197f3715u, 0xe82985c0u, 0x23755665u, 0xa5e124cbu, 0x6ebdf76eu, 0x73b8c7d6u, 0xb8e41473u, 0x3e7066ddu, 0xf52cb578u, 0x0f580a6cu, 0xc404d9c9u, 0x4290ab67u, 0x89cc78c2u, 0x94c9487au, 0x5f959bdfu, 0xd901e971u, 0x125d3ad4u, 0xe30b8801u, 0x28575ba4u, 0xaec3290au, 0x659ffaafu, 0x789aca17u, 0xb3c619b2u, 0x35526b1cu, 0xfe0eb8b9u, 0x0c8e08f7u, 0xc7d2db52u, 0x4146a9fcu, 0x8a1a7a59u, 0x971f4ae1u, 0x5c439944u, 0xdad7ebeau, 0x118b384fu, 0xe0dd8a9au, 0x2b81593fu, 0xad152b91u, 0x6649f834u, 0x7b4cc88cu, 0xb0101b29u, 0x36846987u, 0xfdd8ba22u, 0x08f40f5au, 0xc3a8dcffu, 0x453cae51u, 0x8e607df4u, 0x93654d4cu, 0x58399ee9u, 0xdeadec47u, 0x15f13fe2u, 0xe4a78d37u, 0x2ffb5e92u, 0xa96f2c3cu, 0x6233ff99u, 0x7f36cf21u, 0xb46a1c84u, 0x32fe6e2au, 0xf9a2bd8fu, 0x0b220dc1u, 0xc07ede64u, 0x46eaaccau, 0x8db67f6fu, 0x90b34fd7u, 0x5bef9c72u, 0xdd7beedcu, 0x16273d79u, 0xe7718facu, 0x2c2d5c09u, 0xaab92ea7u, 0x61e5fd02u, 0x7ce0cdbau, 0xb7bc1e1fu, 0x31286cb1u, 0xfa74bf14u, 0x1eb014d8u, 0xd5ecc77du, 0x5378b5d3u, 0x98246676u, 0x852156ceu, 0x4e7d856bu, 0xc8e9f7c5u, 0x03b52460u, 0xf2e396b5u, 0x39bf4510u, 0xbf2b37beu, 0x7477e41bu, 0x6972d4a3u, 0xa22e0706u, 0x24ba75a8u, 0xefe6a60du, 0x1d661643u, 0xd63ac5e6u, 0x50aeb748u, 0x9bf264edu, 0x86f75455u, 0x4dab87f0u, 0xcb3ff55eu, 0x006326fbu, 0xf135942eu, 0x3a69478bu, 0xbcfd3525u, 0x77a1e680u, 0x6aa4d638u, 0xa1f8059du, 0x276c7733u, 0xec30a496u, 0x191c11eeu, 0xd240c24bu, 0x54d4b0e5u, 0x9f886340u, 0x828d53f8u, 0x49d1805du, 0xcf45f2f3u, 0x04192156u, 0xf54f9383u, 0x3e134026u, 0xb8873288u, 0x73dbe12du, 0x6eded195u, 0xa5820230u, 0x2316709eu, 0xe84aa33bu, 0x1aca1375u, 0xd196c0d0u, 0x5702b27eu, 0x9c5e61dbu, 0x815b5163u, 0x4a0782c6u, 0xcc93f068u, 0x07cf23cdu, 0xf6999118u, 0x3dc542bdu, 0xbb513013u, 0x700de3b6u, 0x6d08d30eu, 0xa65400abu, 0x20c07205u, 0xeb9ca1a0u, 0x11e81eb4u, 0xdab4cd11u, 0x5c20bfbfu, 0x977c6c1au, 0x8a795ca2u, 0x41258f07u, 0xc7b1fda9u, 0x0ced2e0cu, 0xfdbb9cd9u, 0x36e74f7cu, 0xb0733dd2u, 0x7b2fee77u, 0x662adecfu, 0xad760d6au, 0x2be27fc4u, 0xe0beac61u, 0x123e1c2fu, 0xd962cf8au, 0x5ff6bd24u, 0x94aa6e81u, 0x89af5e39u, 0x42f38d9cu, 0xc467ff32u, 0x0f3b2c97u, 0xfe6d9e42u, 0x35314de7u, 0xb3a53f49u, 0x78f9ececu, 0x65fcdc54u, 0xaea00ff1u, 0x28347d5fu, 0xe368aefau, 0x16441b82u, 0xdd18c827u, 0x5b8cba89u, 0x90d0692cu, 0x8dd55994u, 0x46898a31u, 0xc01df89fu, 0x0b412b3au, 0xfa1799efu, 0x314b4a4au, 0xb7df38e4u, 0x7c83eb41u, 0x6186dbf9u, 0xaada085cu, 0x2c4e7af2u, 0xe712a957u, 0x15921919u, 0xdececabcu, 0x585ab812u, 0x93066bb7u, 0x8e035b0fu, 0x455f88aau, 0xc3cbfa04u, 0x089729a1u, 0xf9c19b74u, 0x329d48d1u, 0xb4093a7fu, 0x7f55e9dau, 0x6250d962u, 0xa90c0ac7u, 0x2f987869u, 0xe4c4abccu }; static const unsigned lodepng_crc32_table6[256] = { 0x00000000u, 0xa6770bb4u, 0x979f1129u, 0x31e81a9du, 0xf44f2413u, 0x52382fa7u, 0x63d0353au, 0xc5a73e8eu, 0x33ef4e67u, 0x959845d3u, 0xa4705f4eu, 0x020754fau, 0xc7a06a74u, 0x61d761c0u, 0x503f7b5du, 0xf64870e9u, 0x67de9cceu, 0xc1a9977au, 0xf0418de7u, 0x56368653u, 0x9391b8ddu, 0x35e6b369u, 0x040ea9f4u, 0xa279a240u, 0x5431d2a9u, 0xf246d91du, 0xc3aec380u, 0x65d9c834u, 0xa07ef6bau, 0x0609fd0eu, 0x37e1e793u, 0x9196ec27u, 0xcfbd399cu, 0x69ca3228u, 0x582228b5u, 0xfe552301u, 0x3bf21d8fu, 0x9d85163bu, 0xac6d0ca6u, 0x0a1a0712u, 0xfc5277fbu, 0x5a257c4fu, 0x6bcd66d2u, 0xcdba6d66u, 0x081d53e8u, 0xae6a585cu, 0x9f8242c1u, 0x39f54975u, 0xa863a552u, 0x0e14aee6u, 0x3ffcb47bu, 0x998bbfcfu, 0x5c2c8141u, 0xfa5b8af5u, 0xcbb39068u, 0x6dc49bdcu, 0x9b8ceb35u, 0x3dfbe081u, 0x0c13fa1cu, 0xaa64f1a8u, 0x6fc3cf26u, 0xc9b4c492u, 0xf85cde0fu, 0x5e2bd5bbu, 0x440b7579u, 0xe27c7ecdu, 0xd3946450u, 0x75e36fe4u, 0xb044516au, 0x16335adeu, 0x27db4043u, 0x81ac4bf7u, 0x77e43b1eu, 0xd19330aau, 0xe07b2a37u, 0x460c2183u, 0x83ab1f0du, 0x25dc14b9u, 0x14340e24u, 0xb2430590u, 0x23d5e9b7u, 0x85a2e203u, 0xb44af89eu, 0x123df32au, 0xd79acda4u, 0x71edc610u, 0x4005dc8du, 0xe672d739u, 0x103aa7d0u, 0xb64dac64u, 0x87a5b6f9u, 0x21d2bd4du, 0xe47583c3u, 0x42028877u, 0x73ea92eau, 0xd59d995eu, 0x8bb64ce5u, 0x2dc14751u, 0x1c295dccu, 0xba5e5678u, 0x7ff968f6u, 0xd98e6342u, 0xe86679dfu, 0x4e11726bu, 0xb8590282u, 0x1e2e0936u, 0x2fc613abu, 0x89b1181fu, 0x4c162691u, 0xea612d25u, 0xdb8937b8u, 0x7dfe3c0cu, 0xec68d02bu, 0x4a1fdb9fu, 0x7bf7c102u, 0xdd80cab6u, 0x1827f438u, 0xbe50ff8cu, 0x8fb8e511u, 0x29cfeea5u, 0xdf879e4cu, 0x79f095f8u, 0x48188f65u, 0xee6f84d1u, 0x2bc8ba5fu, 0x8dbfb1ebu, 0xbc57ab76u, 0x1a20a0c2u, 0x8816eaf2u, 0x2e61e146u, 0x1f89fbdbu, 0xb9fef06fu, 0x7c59cee1u, 0xda2ec555u, 0xebc6dfc8u, 0x4db1d47cu, 0xbbf9a495u, 0x1d8eaf21u, 0x2c66b5bcu, 0x8a11be08u, 0x4fb68086u, 0xe9c18b32u, 0xd82991afu, 0x7e5e9a1bu, 0xefc8763cu, 0x49bf7d88u, 0x78576715u, 0xde206ca1u, 0x1b87522fu, 0xbdf0599bu, 0x8c184306u, 0x2a6f48b2u, 0xdc27385bu, 0x7a5033efu, 0x4bb82972u, 0xedcf22c6u, 0x28681c48u, 0x8e1f17fcu, 0xbff70d61u, 0x198006d5u, 0x47abd36eu, 0xe1dcd8dau, 0xd034c247u, 0x7643c9f3u, 0xb3e4f77du, 0x1593fcc9u, 0x247be654u, 0x820cede0u, 0x74449d09u, 0xd23396bdu, 0xe3db8c20u, 0x45ac8794u, 0x800bb91au, 0x267cb2aeu, 0x1794a833u, 0xb1e3a387u, 0x20754fa0u, 0x86024414u, 0xb7ea5e89u, 0x119d553du, 0xd43a6bb3u, 0x724d6007u, 0x43a57a9au, 0xe5d2712eu, 0x139a01c7u, 0xb5ed0a73u, 0x840510eeu, 0x22721b5au, 0xe7d525d4u, 0x41a22e60u, 0x704a34fdu, 0xd63d3f49u, 0xcc1d9f8bu, 0x6a6a943fu, 0x5b828ea2u, 0xfdf58516u, 0x3852bb98u, 0x9e25b02cu, 0xafcdaab1u, 0x09baa105u, 0xfff2d1ecu, 0x5985da58u, 0x686dc0c5u, 0xce1acb71u, 0x0bbdf5ffu, 0xadcafe4bu, 0x9c22e4d6u, 0x3a55ef62u, 0xabc30345u, 0x0db408f1u, 0x3c5c126cu, 0x9a2b19d8u, 0x5f8c2756u, 0xf9fb2ce2u, 0xc813367fu, 0x6e643dcbu, 0x982c4d22u, 0x3e5b4696u, 0x0fb35c0bu, 0xa9c457bfu, 0x6c636931u, 0xca146285u, 0xfbfc7818u, 0x5d8b73acu, 0x03a0a617u, 0xa5d7ada3u, 0x943fb73eu, 0x3248bc8au, 0xf7ef8204u, 0x519889b0u, 0x6070932du, 0xc6079899u, 0x304fe870u, 0x9638e3c4u, 0xa7d0f959u, 0x01a7f2edu, 0xc400cc63u, 0x6277c7d7u, 0x539fdd4au, 0xf5e8d6feu, 0x647e3ad9u, 0xc209316du, 0xf3e12bf0u, 0x55962044u, 0x90311ecau, 0x3646157eu, 0x07ae0fe3u, 0xa1d90457u, 0x579174beu, 0xf1e67f0au, 0xc00e6597u, 0x66796e23u, 0xa3de50adu, 0x05a95b19u, 0x34414184u, 0x92364a30u }; static const unsigned lodepng_crc32_table7[256] = { 0x00000000u, 0xccaa009eu, 0x4225077du, 0x8e8f07e3u, 0x844a0efau, 0x48e00e64u, 0xc66f0987u, 0x0ac50919u, 0xd3e51bb5u, 0x1f4f1b2bu, 0x91c01cc8u, 0x5d6a1c56u, 0x57af154fu, 0x9b0515d1u, 0x158a1232u, 0xd92012acu, 0x7cbb312bu, 0xb01131b5u, 0x3e9e3656u, 0xf23436c8u, 0xf8f13fd1u, 0x345b3f4fu, 0xbad438acu, 0x767e3832u, 0xaf5e2a9eu, 0x63f42a00u, 0xed7b2de3u, 0x21d12d7du, 0x2b142464u, 0xe7be24fau, 0x69312319u, 0xa59b2387u, 0xf9766256u, 0x35dc62c8u, 0xbb53652bu, 0x77f965b5u, 0x7d3c6cacu, 0xb1966c32u, 0x3f196bd1u, 0xf3b36b4fu, 0x2a9379e3u, 0xe639797du, 0x68b67e9eu, 0xa41c7e00u, 0xaed97719u, 0x62737787u, 0xecfc7064u, 0x205670fau, 0x85cd537du, 0x496753e3u, 0xc7e85400u, 0x0b42549eu, 0x01875d87u, 0xcd2d5d19u, 0x43a25afau, 0x8f085a64u, 0x562848c8u, 0x9a824856u, 0x140d4fb5u, 0xd8a74f2bu, 0xd2624632u, 0x1ec846acu, 0x9047414fu, 0x5ced41d1u, 0x299dc2edu, 0xe537c273u, 0x6bb8c590u, 0xa712c50eu, 0xadd7cc17u, 0x617dcc89u, 0xeff2cb6au, 0x2358cbf4u, 0xfa78d958u, 0x36d2d9c6u, 0xb85dde25u, 0x74f7debbu, 0x7e32d7a2u, 0xb298d73cu, 0x3c17d0dfu, 0xf0bdd041u, 0x5526f3c6u, 0x998cf358u, 0x1703f4bbu, 0xdba9f425u, 0xd16cfd3cu, 0x1dc6fda2u, 0x9349fa41u, 0x5fe3fadfu, 0x86c3e873u, 0x4a69e8edu, 0xc4e6ef0eu, 0x084cef90u, 0x0289e689u, 0xce23e617u, 0x40ace1f4u, 0x8c06e16au, 0xd0eba0bbu, 0x1c41a025u, 0x92cea7c6u, 0x5e64a758u, 0x54a1ae41u, 0x980baedfu, 0x1684a93cu, 0xda2ea9a2u, 0x030ebb0eu, 0xcfa4bb90u, 0x412bbc73u, 0x8d81bcedu, 0x8744b5f4u, 0x4beeb56au, 0xc561b289u, 0x09cbb217u, 0xac509190u, 0x60fa910eu, 0xee7596edu, 0x22df9673u, 0x281a9f6au, 0xe4b09ff4u, 0x6a3f9817u, 0xa6959889u, 0x7fb58a25u, 0xb31f8abbu, 0x3d908d58u, 0xf13a8dc6u, 0xfbff84dfu, 0x37558441u, 0xb9da83a2u, 0x7570833cu, 0x533b85dau, 0x9f918544u, 0x111e82a7u, 0xddb48239u, 0xd7718b20u, 0x1bdb8bbeu, 0x95548c5du, 0x59fe8cc3u, 0x80de9e6fu, 0x4c749ef1u, 0xc2fb9912u, 0x0e51998cu, 0x04949095u, 0xc83e900bu, 0x46b197e8u, 0x8a1b9776u, 0x2f80b4f1u, 0xe32ab46fu, 0x6da5b38cu, 0xa10fb312u, 0xabcaba0bu, 0x6760ba95u, 0xe9efbd76u, 0x2545bde8u, 0xfc65af44u, 0x30cfafdau, 0xbe40a839u, 0x72eaa8a7u, 0x782fa1beu, 0xb485a120u, 0x3a0aa6c3u, 0xf6a0a65du, 0xaa4de78cu, 0x66e7e712u, 0xe868e0f1u, 0x24c2e06fu, 0x2e07e976u, 0xe2ade9e8u, 0x6c22ee0bu, 0xa088ee95u, 0x79a8fc39u, 0xb502fca7u, 0x3b8dfb44u, 0xf727fbdau, 0xfde2f2c3u, 0x3148f25du, 0xbfc7f5beu, 0x736df520u, 0xd6f6d6a7u, 0x1a5cd639u, 0x94d3d1dau, 0x5879d144u, 0x52bcd85du, 0x9e16d8c3u, 0x1099df20u, 0xdc33dfbeu, 0x0513cd12u, 0xc9b9cd8cu, 0x4736ca6fu, 0x8b9ccaf1u, 0x8159c3e8u, 0x4df3c376u, 0xc37cc495u, 0x0fd6c40bu, 0x7aa64737u, 0xb60c47a9u, 0x3883404au, 0xf42940d4u, 0xfeec49cdu, 0x32464953u, 0xbcc94eb0u, 0x70634e2eu, 0xa9435c82u, 0x65e95c1cu, 0xeb665bffu, 0x27cc5b61u, 0x2d095278u, 0xe1a352e6u, 0x6f2c5505u, 0xa386559bu, 0x061d761cu, 0xcab77682u, 0x44387161u, 0x889271ffu, 0x825778e6u, 0x4efd7878u, 0xc0727f9bu, 0x0cd87f05u, 0xd5f86da9u, 0x19526d37u, 0x97dd6ad4u, 0x5b776a4au, 0x51b26353u, 0x9d1863cdu, 0x1397642eu, 0xdf3d64b0u, 0x83d02561u, 0x4f7a25ffu, 0xc1f5221cu, 0x0d5f2282u, 0x079a2b9bu, 0xcb302b05u, 0x45bf2ce6u, 0x89152c78u, 0x50353ed4u, 0x9c9f3e4au, 0x121039a9u, 0xdeba3937u, 0xd47f302eu, 0x18d530b0u, 0x965a3753u, 0x5af037cdu, 0xff6b144au, 0x33c114d4u, 0xbd4e1337u, 0x71e413a9u, 0x7b211ab0u, 0xb78b1a2eu, 0x39041dcdu, 0xf5ae1d53u, 0x2c8e0fffu, 0xe0240f61u, 0x6eab0882u, 0xa201081cu, 0xa8c40105u, 0x646e019bu, 0xeae10678u, 0x264b06e6u }; /* Computes the cyclic redundancy check as used by PNG chunks*/ unsigned lodepng_crc32(const unsigned char* data, size_t length) { /*Using the Slicing by Eight algorithm*/ unsigned r = 0xffffffffu; while(length >= 8) { r = lodepng_crc32_table7[(data[0] ^ (r & 0xffu))] ^ lodepng_crc32_table6[(data[1] ^ ((r >> 8) & 0xffu))] ^ lodepng_crc32_table5[(data[2] ^ ((r >> 16) & 0xffu))] ^ lodepng_crc32_table4[(data[3] ^ ((r >> 24) & 0xffu))] ^ lodepng_crc32_table3[data[4]] ^ lodepng_crc32_table2[data[5]] ^ lodepng_crc32_table1[data[6]] ^ lodepng_crc32_table0[data[7]]; data += 8; length -= 8; } while(length--) { r = lodepng_crc32_table0[(r ^ *data++) & 0xffu] ^ (r >> 8); } return r ^ 0xffffffffu; } #else /* LODEPNG_COMPILE_CRC */ /*in this case, the function is only declared here, and must be defined externally so that it will be linked in. Example implementation that uses a much smaller lookup table for memory constrained cases: unsigned lodepng_crc32(const unsigned char* data, size_t length) { unsigned r = 0xffffffffu; static const unsigned table[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; while(length--) { r = table[(r ^ *data) & 0xf] ^ (r >> 4); r = table[(r ^ (*data >> 4)) & 0xf] ^ (r >> 4); data++; } return r ^ 0xffffffffu; } */ unsigned lodepng_crc32(const unsigned char* data, size_t length); #endif /* LODEPNG_COMPILE_CRC */ /* ////////////////////////////////////////////////////////////////////////// */ /* / Reading and writing PNG color channel bits / */ /* ////////////////////////////////////////////////////////////////////////// */ /* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); ++(*bitpointer); return result; } /* TODO: make this faster */ static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { unsigned result = 0; size_t i; for(i = 0 ; i < nbits; ++i) { result <<= 1u; result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); } return result; } static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { /*the current bit in bitstream may be 0 or 1 for this to work*/ if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); ++(*bitpointer); } /* ////////////////////////////////////////////////////////////////////////// */ /* / PNG chunks / */ /* ////////////////////////////////////////////////////////////////////////// */ unsigned lodepng_chunk_length(const unsigned char* chunk) { return lodepng_read32bitInt(chunk); } void lodepng_chunk_type(char type[5], const unsigned char* chunk) { unsigned i; for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; type[4] = 0; /*null termination char*/ } unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { if(lodepng_strlen(type) != 4) return 0; return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); } /* chunk type name must exist only out of alphabetic characters a-z or A-Z */ static unsigned char lodepng_chunk_type_name_valid(const unsigned char* chunk) { unsigned i; for(i = 0; i != 4; ++i) { char c = (char)chunk[4 + i]; if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) { return 0; /* not valid */ } } return 1; /* valid */ } unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { return((chunk[4] & 32) != 0); } unsigned char lodepng_chunk_private(const unsigned char* chunk) { return((chunk[5] & 32) != 0); } /* this is an error if it is reserved: the third character must be uppercase in the PNG standard, lowercasing this character is reserved for possible future extension by the spec*/ static unsigned char lodepng_chunk_reserved(const unsigned char* chunk) { return((chunk[6] & 32) != 0); } unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { return((chunk[7] & 32) != 0); } unsigned char* lodepng_chunk_data(unsigned char* chunk) { return &chunk[8]; } const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { return &chunk[8]; } unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { unsigned length = lodepng_chunk_length(chunk); unsigned crc = lodepng_read32bitInt(&chunk[length + 8]); /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ unsigned checksum = lodepng_crc32(&chunk[4], length + 4); if(crc != checksum) return 1; else return 0; } void lodepng_chunk_generate_crc(unsigned char* chunk) { unsigned length = lodepng_chunk_length(chunk); unsigned crc = lodepng_crc32(&chunk[4], length + 4); lodepng_set32bitInt(chunk + 8 + length, crc); } unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { size_t available_size = (size_t)(end - chunk); if(chunk >= end || available_size < 12) return end; /*too small to contain a chunk*/ if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ return chunk + 8; } else { size_t total_chunk_length; if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; if(total_chunk_length > available_size) return end; /*outside of range*/ return chunk + total_chunk_length; } } const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { size_t available_size = (size_t)(end - chunk); if(chunk >= end || available_size < 12) return end; /*too small to contain a chunk*/ if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ return chunk + 8; } else { size_t total_chunk_length; if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; if(total_chunk_length > available_size) return end; /*outside of range*/ return chunk + total_chunk_length; } } unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { for(;;) { if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ if(lodepng_chunk_type_equals(chunk, type)) return chunk; chunk = lodepng_chunk_next(chunk, end); } } const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { for(;;) { if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ if(lodepng_chunk_type_equals(chunk, type)) return chunk; chunk = lodepng_chunk_next_const(chunk, end); } } unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { unsigned i; size_t total_chunk_length, new_length; unsigned char *chunk_start, *new_buffer; if(!lodepng_chunk_type_name_valid(chunk)) { return 121; /* invalid chunk type name */ } if(lodepng_chunk_reserved(chunk)) { return 122; /* invalid third lowercase character */ } if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); if(!new_buffer) return 83; /*alloc fail*/ (*out) = new_buffer; (*outsize) = new_length; chunk_start = &(*out)[new_length - total_chunk_length]; for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; return 0; } /*Sets length and name and allocates the space for data and crc but does not set data or crc yet. Returns the start of the chunk in chunk. The start of the data is at chunk + 8. To finalize chunk, add the data, then use lodepng_chunk_generate_crc */ static unsigned lodepng_chunk_init(unsigned char** chunk, ucvector* out, size_t length, const char* type) { size_t new_length = out->size; if(lodepng_addofl(new_length, length, &new_length)) return 77; if(lodepng_addofl(new_length, 12, &new_length)) return 77; if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ *chunk = out->data + new_length - length - 12u; /*1: length*/ lodepng_set32bitInt(*chunk, (unsigned)length); /*2: chunk name (4 letters)*/ lodepng_memcpy(*chunk + 4, type, 4); return 0; } /* like lodepng_chunk_create but with custom allocsize */ static unsigned lodepng_chunk_createv(ucvector* out, size_t length, const char* type, const unsigned char* data) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); /*3: the data*/ lodepng_memcpy(chunk + 8, data, length); /*4: CRC (of the chunkname characters and the data)*/ lodepng_chunk_generate_crc(chunk); return 0; } unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, size_t length, const char* type, const unsigned char* data) { ucvector v = ucvector_init(*out, *outsize); unsigned error = lodepng_chunk_createv(&v, length, type, data); *out = v.data; *outsize = v.size; return error; } /* ////////////////////////////////////////////////////////////////////////// */ /* / Color types, channels, bits / */ /* ////////////////////////////////////////////////////////////////////////// */ /*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. Return value is a LodePNG error code.*/ static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { switch(colortype) { case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ default: return 31; /* invalid color type */ } return 0; /*allowed color type / bits combination*/ } static unsigned getNumColorChannels(LodePNGColorType colortype) { switch(colortype) { case LCT_GREY: return 1; case LCT_RGB: return 3; case LCT_PALETTE: return 1; case LCT_GREY_ALPHA: return 2; case LCT_RGBA: return 4; case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ default: return 0; /*invalid color type*/ } } static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { /*bits per pixel is amount of channels * bits per channel*/ return getNumColorChannels(colortype) * bitdepth; } /* ////////////////////////////////////////////////////////////////////////// */ void lodepng_color_mode_init(LodePNGColorMode* info) { info->key_defined = 0; info->key_r = info->key_g = info->key_b = 0; info->colortype = LCT_RGBA; info->bitdepth = 8; info->palette = 0; info->palettesize = 0; } /*allocates palette memory if needed, and initializes all colors to black*/ static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { size_t i; /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ /*the palette must have room for up to 256 colors with 4 bytes each.*/ if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); if(!info->palette) return; /*alloc fail*/ for(i = 0; i != 256; ++i) { /*Initialize all unused colors with black, the value used for invalid palette indices. This is an error according to the PNG spec, but common PNG decoders make it black instead. That makes color conversion slightly faster due to no error handling needed.*/ info->palette[i * 4 + 0] = 0; info->palette[i * 4 + 1] = 0; info->palette[i * 4 + 2] = 0; info->palette[i * 4 + 3] = 255; } } void lodepng_color_mode_cleanup(LodePNGColorMode* info) { lodepng_palette_clear(info); } unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { lodepng_color_mode_cleanup(dest); lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); if(source->palette) { dest->palette = (unsigned char*)lodepng_malloc(1024); if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); } return 0; } LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { LodePNGColorMode result; lodepng_color_mode_init(&result); result.colortype = colortype; result.bitdepth = bitdepth; return result; } static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { size_t i; if(a->colortype != b->colortype) return 0; if(a->bitdepth != b->bitdepth) return 0; if(a->key_defined != b->key_defined) return 0; if(a->key_defined) { if(a->key_r != b->key_r) return 0; if(a->key_g != b->key_g) return 0; if(a->key_b != b->key_b) return 0; } if(a->palettesize != b->palettesize) return 0; for(i = 0; i != a->palettesize * 4; ++i) { if(a->palette[i] != b->palette[i]) return 0; } return 1; } void lodepng_palette_clear(LodePNGColorMode* info) { if(info->palette) lodepng_free(info->palette); info->palette = 0; info->palettesize = 0; } unsigned lodepng_palette_add(LodePNGColorMode* info, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { if(!info->palette) /*allocate palette if empty*/ { lodepng_color_mode_alloc_palette(info); if(!info->palette) return 83; /*alloc fail*/ } if(info->palettesize >= 256) { return 108; /*too many palette values*/ } info->palette[4 * info->palettesize + 0] = r; info->palette[4 * info->palettesize + 1] = g; info->palette[4 * info->palettesize + 2] = b; info->palette[4 * info->palettesize + 3] = a; ++info->palettesize; return 0; } /*calculate bits per pixel out of colortype and bitdepth*/ unsigned lodepng_get_bpp(const LodePNGColorMode* info) { return lodepng_get_bpp_lct(info->colortype, info->bitdepth); } unsigned lodepng_get_channels(const LodePNGColorMode* info) { return getNumColorChannels(info->colortype); } unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; } unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { return (info->colortype & 4) != 0; /*4 or 6*/ } unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { return info->colortype == LCT_PALETTE; } unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { size_t i; for(i = 0; i != info->palettesize; ++i) { if(info->palette[i * 4 + 3] < 255) return 1; } return 0; } unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { return info->key_defined || lodepng_is_alpha_type(info) || lodepng_has_palette_alpha(info); } static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); size_t n = (size_t)w * (size_t)h; return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; } size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); } #ifdef LODEPNG_COMPILE_PNG /*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, and in addition has one extra byte per line: the filter byte. So this gives a larger result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { /* + 1 for the filter byte, and possibly plus padding bits per line. */ /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; return (size_t)h * line; } #ifdef LODEPNG_COMPILE_DECODER /*Safely checks whether size_t overflow can be caused due to amount of pixels. This check is overcautious rather than precise. If this check indicates no overflow, you can safely compute in a size_t (but not an unsigned): -(size_t)w * (size_t)h * 8 -amount of bytes in IDAT (including filter, padding and Adam7 bytes) -amount of bytes in raw color model Returns 1 if overflow possible, 0 if not. */ static int lodepng_pixel_overflow(unsigned w, unsigned h, const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); size_t numpixels, total; size_t line; /* bytes per line in worst case */ if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ return 0; /* no overflow */ } #endif /*LODEPNG_COMPILE_DECODER*/ #endif /*LODEPNG_COMPILE_PNG*/ #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS static void LodePNGUnknownChunks_init(LodePNGInfo* info) { unsigned i; for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; } static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { unsigned i; for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); } static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { unsigned i; LodePNGUnknownChunks_cleanup(dest); for(i = 0; i != 3; ++i) { size_t j; dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ for(j = 0; j < src->unknown_chunks_size[i]; ++j) { dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; } } return 0; } /******************************************************************************/ static void LodePNGText_init(LodePNGInfo* info) { info->text_num = 0; info->text_keys = NULL; info->text_strings = NULL; } static void LodePNGText_cleanup(LodePNGInfo* info) { size_t i; for(i = 0; i != info->text_num; ++i) { lodepng_free(info->text_keys[i]); lodepng_free(info->text_strings[i]); } lodepng_free(info->text_keys); lodepng_free(info->text_strings); } static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { size_t i = 0; dest->text_keys = NULL; dest->text_strings = NULL; dest->text_num = 0; for(i = 0; i != source->text_num; ++i) { CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); } return 0; } static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); if(new_keys) info->text_keys = new_keys; if(new_strings) info->text_strings = new_strings; if(!new_keys || !new_strings) return 83; /*alloc fail*/ ++info->text_num; info->text_keys[info->text_num - 1] = alloc_string(key); info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ return 0; } unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); } void lodepng_clear_text(LodePNGInfo* info) { LodePNGText_cleanup(info); /*cleanup only deconstructs, need to init again to set appropriate pointers to NULL*/ LodePNGText_init(info); } /******************************************************************************/ static void LodePNGIText_init(LodePNGInfo* info) { info->itext_num = 0; info->itext_keys = NULL; info->itext_langtags = NULL; info->itext_transkeys = NULL; info->itext_strings = NULL; } static void LodePNGIText_cleanup(LodePNGInfo* info) { size_t i; for(i = 0; i != info->itext_num; ++i) { lodepng_free(info->itext_keys[i]); lodepng_free(info->itext_langtags[i]); lodepng_free(info->itext_transkeys[i]); lodepng_free(info->itext_strings[i]); } lodepng_free(info->itext_keys); lodepng_free(info->itext_langtags); lodepng_free(info->itext_transkeys); lodepng_free(info->itext_strings); } static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { size_t i = 0; dest->itext_keys = NULL; dest->itext_langtags = NULL; dest->itext_transkeys = NULL; dest->itext_strings = NULL; dest->itext_num = 0; for(i = 0; i != source->itext_num; ++i) { CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], source->itext_transkeys[i], source->itext_strings[i])); } return 0; } void lodepng_clear_itext(LodePNGInfo* info) { LodePNGIText_cleanup(info); /*cleanup only deconstructs, need to init again to set appropriate pointers to NULL*/ LodePNGIText_init(info); } static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, const char* transkey, const char* str, size_t size) { char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); if(new_keys) info->itext_keys = new_keys; if(new_langtags) info->itext_langtags = new_langtags; if(new_transkeys) info->itext_transkeys = new_transkeys; if(new_strings) info->itext_strings = new_strings; if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ ++info->itext_num; info->itext_keys[info->itext_num - 1] = alloc_string(key); info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); return 0; } unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, const char* transkey, const char* str) { return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); } unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { if(info->iccp_defined) lodepng_clear_icc(info); if(profile_size == 0) return 123; /*invalid ICC profile size*/ info->iccp_name = alloc_string(name); if(!info->iccp_name) return 83; /*alloc fail*/ info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); if(!info->iccp_profile) { lodepng_free(info->iccp_name); return 83; /*alloc fail*/ } lodepng_memcpy(info->iccp_profile, profile, profile_size); info->iccp_profile_size = profile_size; info->iccp_defined = 1; return 0; /*ok*/ } static void lodepng_init_icc(LodePNGInfo* info) { info->iccp_defined = 0; info->iccp_name = NULL; info->iccp_profile = NULL; info->iccp_profile_size = 0; } void lodepng_clear_icc(LodePNGInfo* info) { lodepng_free(info->iccp_name); lodepng_free(info->iccp_profile); lodepng_init_icc(info); } unsigned lodepng_set_exif(LodePNGInfo* info, const unsigned char* exif, unsigned exif_size) { if(info->exif_defined) lodepng_clear_exif(info); info->exif = (unsigned char*)lodepng_malloc(exif_size); if(!info->exif) return 83; /*alloc fail*/ lodepng_memcpy(info->exif, exif, exif_size); info->exif_size = exif_size; info->exif_defined = 1; return 0; /*ok*/ } static void lodepng_init_exif(LodePNGInfo* info) { info->exif_defined = 0; info->exif = NULL; info->exif_size = 0; } void lodepng_clear_exif(LodePNGInfo* info) { lodepng_free(info->exif); lodepng_init_exif(info); } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ void lodepng_info_init(LodePNGInfo* info) { lodepng_color_mode_init(&info->color); info->interlace_method = 0; info->compression_method = 0; info->filter_method = 0; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS info->background_defined = 0; info->background_r = info->background_g = info->background_b = 0; LodePNGText_init(info); LodePNGIText_init(info); lodepng_init_icc(info); lodepng_init_exif(info); info->time_defined = 0; info->phys_defined = 0; info->gama_defined = 0; info->chrm_defined = 0; info->srgb_defined = 0; info->cicp_defined = 0; info->cicp_color_primaries = 0; info->cicp_transfer_function = 0; info->cicp_matrix_coefficients = 0; info->cicp_video_full_range_flag = 0; info->mdcv_defined = 0; info->mdcv_red_x = 0; info->mdcv_red_y = 0; info->mdcv_green_x = 0; info->mdcv_green_y = 0; info->mdcv_blue_x = 0; info->mdcv_blue_y = 0; info->mdcv_white_x = 0; info->mdcv_white_y = 0; info->mdcv_max_luminance = 0; info->mdcv_min_luminance = 0; info->clli_defined = 0; info->clli_max_cll = 0; info->clli_max_fall = 0; info->sbit_defined = 0; info->sbit_r = info->sbit_g = info->sbit_b = info->sbit_a = 0; LodePNGUnknownChunks_init(info); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } void lodepng_info_cleanup(LodePNGInfo* info) { lodepng_color_mode_cleanup(&info->color); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS LodePNGText_cleanup(info); LodePNGIText_cleanup(info); lodepng_clear_icc(info); lodepng_clear_exif(info); LodePNGUnknownChunks_cleanup(info); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { lodepng_info_cleanup(dest); lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); /*ensure to initialize all fields pointing to allocated data to NULL first*/ lodepng_color_mode_init(&dest->color); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS LodePNGText_init(dest); LodePNGIText_init(dest); lodepng_init_icc(dest); lodepng_init_exif(dest); LodePNGUnknownChunks_init(dest); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); if(source->iccp_defined) { CERROR_TRY_RETURN(lodepng_set_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); } if(source->exif_defined) { CERROR_TRY_RETURN(lodepng_set_exif(dest, source->exif, source->exif_size)); } CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ return 0; } /* ////////////////////////////////////////////////////////////////////////// */ /*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ unsigned p = index & m; in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ in = in << (bits * (m - p)); if(p == 0) out[index * bits / 8u] = in; else out[index * bits / 8u] |= in; } typedef struct ColorTree ColorTree; /* One node of a color tree This is the data structure used to count the number of unique colors and to get a palette index for a color. It's like an octree, but because the alpha channel is used too, each node has 16 instead of 8 children. */ struct ColorTree { ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ int index; /*the payload. Only has a meaningful value if this is in the last level*/ }; static void color_tree_init(ColorTree* tree) { lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); tree->index = -1; } static void color_tree_cleanup(ColorTree* tree) { int i; for(i = 0; i != 16; ++i) { if(tree->children[i]) { color_tree_cleanup(tree->children[i]); lodepng_free(tree->children[i]); } } } /*returns -1 if color not present, its index otherwise*/ static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { int bit = 0; for(bit = 0; bit < 8; ++bit) { int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); if(!tree->children[i]) return -1; else tree = tree->children[i]; } return tree ? tree->index : -1; } #ifdef LODEPNG_COMPILE_ENCODER static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { return color_tree_get(tree, r, g, b, a) >= 0; } #endif /*LODEPNG_COMPILE_ENCODER*/ /*color is not allowed to already exist. Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") Returns error code, or 0 if ok*/ static unsigned color_tree_add(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { int bit; for(bit = 0; bit < 8; ++bit) { int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); if(!tree->children[i]) { tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); if(!tree->children[i]) return 83; /*alloc fail*/ color_tree_init(tree->children[i]); } tree = tree->children[i]; } tree->index = (int)index; return 0; } /*put a pixel, given its RGBA color, into image of any color type*/ static unsigned rgba8ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { if(mode->colortype == LCT_GREY) { unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ if(mode->bitdepth == 8) out[i] = gray; else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; else { /*take the most significant bits of gray*/ gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); addColorBits(out, i, mode->bitdepth, gray); } } else if(mode->colortype == LCT_RGB) { if(mode->bitdepth == 8) { out[i * 3 + 0] = r; out[i * 3 + 1] = g; out[i * 3 + 2] = b; } else { out[i * 6 + 0] = out[i * 6 + 1] = r; out[i * 6 + 2] = out[i * 6 + 3] = g; out[i * 6 + 4] = out[i * 6 + 5] = b; } } else if(mode->colortype == LCT_PALETTE) { int index = color_tree_get(tree, r, g, b, a); if(index < 0) return 82; /*color not in palette*/ if(mode->bitdepth == 8) out[i] = index; else addColorBits(out, i, mode->bitdepth, (unsigned)index); } else if(mode->colortype == LCT_GREY_ALPHA) { unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ if(mode->bitdepth == 8) { out[i * 2 + 0] = gray; out[i * 2 + 1] = a; } else if(mode->bitdepth == 16) { out[i * 4 + 0] = out[i * 4 + 1] = gray; out[i * 4 + 2] = out[i * 4 + 3] = a; } } else if(mode->colortype == LCT_RGBA) { if(mode->bitdepth == 8) { out[i * 4 + 0] = r; out[i * 4 + 1] = g; out[i * 4 + 2] = b; out[i * 4 + 3] = a; } else { out[i * 8 + 0] = out[i * 8 + 1] = r; out[i * 8 + 2] = out[i * 8 + 3] = g; out[i * 8 + 4] = out[i * 8 + 5] = b; out[i * 8 + 6] = out[i * 8 + 7] = a; } } return 0; /*no error*/ } /*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ static void rgba16ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, unsigned short r, unsigned short g, unsigned short b, unsigned short a) { if(mode->colortype == LCT_GREY) { unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ out[i * 2 + 0] = (gray >> 8) & 255; out[i * 2 + 1] = gray & 255; } else if(mode->colortype == LCT_RGB) { out[i * 6 + 0] = (r >> 8) & 255; out[i * 6 + 1] = r & 255; out[i * 6 + 2] = (g >> 8) & 255; out[i * 6 + 3] = g & 255; out[i * 6 + 4] = (b >> 8) & 255; out[i * 6 + 5] = b & 255; } else if(mode->colortype == LCT_GREY_ALPHA) { unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ out[i * 4 + 0] = (gray >> 8) & 255; out[i * 4 + 1] = gray & 255; out[i * 4 + 2] = (a >> 8) & 255; out[i * 4 + 3] = a & 255; } else if(mode->colortype == LCT_RGBA) { out[i * 8 + 0] = (r >> 8) & 255; out[i * 8 + 1] = r & 255; out[i * 8 + 2] = (g >> 8) & 255; out[i * 8 + 3] = g & 255; out[i * 8 + 4] = (b >> 8) & 255; out[i * 8 + 5] = b & 255; out[i * 8 + 6] = (a >> 8) & 255; out[i * 8 + 7] = a & 255; } } /*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a, const unsigned char* in, size_t i, const LodePNGColorMode* mode) { if(mode->colortype == LCT_GREY) { if(mode->bitdepth == 8) { *r = *g = *b = in[i]; if(mode->key_defined && *r == mode->key_r) *a = 0; else *a = 255; } else if(mode->bitdepth == 16) { *r = *g = *b = in[i * 2 + 0]; if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; else *a = 255; } else { unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ size_t j = i * mode->bitdepth; unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); *r = *g = *b = (value * 255) / highest; if(mode->key_defined && value == mode->key_r) *a = 0; else *a = 255; } } else if(mode->colortype == LCT_RGB) { if(mode->bitdepth == 8) { *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; else *a = 255; } else { *r = in[i * 6 + 0]; *g = in[i * 6 + 2]; *b = in[i * 6 + 4]; if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; else *a = 255; } } else if(mode->colortype == LCT_PALETTE) { unsigned index; if(mode->bitdepth == 8) index = in[i]; else { size_t j = i * mode->bitdepth; index = readBitsFromReversedStream(&j, in, mode->bitdepth); } /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ *r = mode->palette[index * 4 + 0]; *g = mode->palette[index * 4 + 1]; *b = mode->palette[index * 4 + 2]; *a = mode->palette[index * 4 + 3]; } else if(mode->colortype == LCT_GREY_ALPHA) { if(mode->bitdepth == 8) { *r = *g = *b = in[i * 2 + 0]; *a = in[i * 2 + 1]; } else { *r = *g = *b = in[i * 4 + 0]; *a = in[i * 4 + 2]; } } else if(mode->colortype == LCT_RGBA) { if(mode->bitdepth == 8) { *r = in[i * 4 + 0]; *g = in[i * 4 + 1]; *b = in[i * 4 + 2]; *a = in[i * 4 + 3]; } else { *r = in[i * 8 + 0]; *g = in[i * 8 + 2]; *b = in[i * 8 + 4]; *a = in[i * 8 + 6]; } } } /*Similar to getPixelColorRGBA8, but with all the for loops inside of the color mode test cases, optimized to convert the colors much faster, when converting to the common case of RGBA with 8 bit per channel. buffer must be RGBA with enough memory.*/ static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, const unsigned char* LODEPNG_RESTRICT in, const LodePNGColorMode* mode) { unsigned num_channels = 4; size_t i; if(mode->colortype == LCT_GREY) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i]; buffer[3] = 255; } if(mode->key_defined) { buffer -= numpixels * num_channels; for(i = 0; i != numpixels; ++i, buffer += num_channels) { if(buffer[0] == mode->key_r) buffer[3] = 0; } } } else if(mode->bitdepth == 16) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2]; buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; } } else { unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ size_t j = 0; for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; } } } else if(mode->colortype == LCT_RGB) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { lodepng_memcpy(buffer, &in[i * 3], 3); buffer[3] = 255; } if(mode->key_defined) { buffer -= numpixels * num_channels; for(i = 0; i != numpixels; ++i, buffer += num_channels) { if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; } } } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 6 + 0]; buffer[1] = in[i * 6 + 2]; buffer[2] = in[i * 6 + 4]; buffer[3] = mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; } } } else if(mode->colortype == LCT_PALETTE) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned index = in[i]; /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ lodepng_memcpy(buffer, &mode->palette[index * 4], 4); } } else { size_t j = 0; for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ lodepng_memcpy(buffer, &mode->palette[index * 4], 4); } } } else if(mode->colortype == LCT_GREY_ALPHA) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; buffer[3] = in[i * 2 + 1]; } } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; buffer[3] = in[i * 4 + 2]; } } } else if(mode->colortype == LCT_RGBA) { if(mode->bitdepth == 8) { lodepng_memcpy(buffer, in, numpixels * 4); } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 8 + 0]; buffer[1] = in[i * 8 + 2]; buffer[2] = in[i * 8 + 4]; buffer[3] = in[i * 8 + 6]; } } } } /*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, const unsigned char* LODEPNG_RESTRICT in, const LodePNGColorMode* mode) { const unsigned num_channels = 3; size_t i; if(mode->colortype == LCT_GREY) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i]; } } else if(mode->bitdepth == 16) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2]; } } else { unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ size_t j = 0; for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; } } } else if(mode->colortype == LCT_RGB) { if(mode->bitdepth == 8) { lodepng_memcpy(buffer, in, numpixels * 3); } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 6 + 0]; buffer[1] = in[i * 6 + 2]; buffer[2] = in[i * 6 + 4]; } } } else if(mode->colortype == LCT_PALETTE) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned index = in[i]; /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ lodepng_memcpy(buffer, &mode->palette[index * 4], 3); } } else { size_t j = 0; for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ lodepng_memcpy(buffer, &mode->palette[index * 4], 3); } } } else if(mode->colortype == LCT_GREY_ALPHA) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; } } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; } } } else if(mode->colortype == LCT_RGBA) { if(mode->bitdepth == 8) { for(i = 0; i != numpixels; ++i, buffer += num_channels) { lodepng_memcpy(buffer, &in[i * 4], 3); } } else { for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 8 + 0]; buffer[1] = in[i * 8 + 2]; buffer[2] = in[i * 8 + 4]; } } } } /*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with given color type, but the given color type must be 16-bit itself.*/ static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, const unsigned char* in, size_t i, const LodePNGColorMode* mode) { if(mode->colortype == LCT_GREY) { *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; else *a = 65535; } else if(mode->colortype == LCT_RGB) { *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; if(mode->key_defined && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; else *a = 65535; } else if(mode->colortype == LCT_GREY_ALPHA) { *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; } else if(mode->colortype == LCT_RGBA) { *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; } } unsigned lodepng_convert(unsigned char* out, const unsigned char* in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, unsigned w, unsigned h) { size_t i; ColorTree tree; size_t numpixels = (size_t)w * (size_t)h; unsigned error = 0; if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { return 107; /* error: must provide palette if input mode is palette */ } if(lodepng_color_mode_equal(mode_out, mode_in)) { size_t numbytes = lodepng_get_raw_size(w, h, mode_in); lodepng_memcpy(out, in, numbytes); return 0; } if(mode_out->colortype == LCT_PALETTE) { size_t palettesize = mode_out->palettesize; const unsigned char* palette = mode_out->palette; size_t palsize = (size_t)1u << mode_out->bitdepth; /*if the user specified output palette but did not give the values, assume they want the values of the input color type (assuming that one is palette). Note that we never create a new palette ourselves.*/ if(palettesize == 0) { palettesize = mode_in->palettesize; palette = mode_in->palette; /*if the input was also palette with same bitdepth, then the color types are also equal, so copy literally. This to preserve the exact indices that were in the PNG even in case there are duplicate colors in the palette.*/ if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { size_t numbytes = lodepng_get_raw_size(w, h, mode_in); lodepng_memcpy(out, in, numbytes); return 0; } } if(palettesize < palsize) palsize = palettesize; color_tree_init(&tree); for(i = 0; i != palsize; ++i) { const unsigned char* p = &palette[i * 4]; error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); if(error) break; } } if(!error) { if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { for(i = 0; i != numpixels; ++i) { unsigned short r = 0, g = 0, b = 0, a = 0; getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); rgba16ToPixel(out, i, mode_out, r, g, b, a); } } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { getPixelColorsRGBA8(out, numpixels, in, mode_in); } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { getPixelColorsRGB8(out, numpixels, in, mode_in); } else { unsigned char r = 0, g = 0, b = 0, a = 0; for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); if(error) break; } } } if(mode_out->colortype == LCT_PALETTE) { color_tree_cleanup(&tree); } return error; } /* Converts a single rgb color without alpha from one type to another, color bits truncated to their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow function, do not use to process all pixels of an image. Alpha channel not supported on purpose: this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the specification it looks like bKGD should ignore the alpha values of the palette since it can use any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ static unsigned lodepng_convert_rgb( unsigned* r_out, unsigned* g_out, unsigned* b_out, unsigned r_in, unsigned g_in, unsigned b_in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { unsigned r = 0, g = 0, b = 0; unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ unsigned shift = 16 - mode_out->bitdepth; if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { r = g = b = r_in * mul; } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { r = r_in * mul; g = g_in * mul; b = b_in * mul; } else if(mode_in->colortype == LCT_PALETTE) { if(r_in >= mode_in->palettesize) return 82; r = mode_in->palette[r_in * 4 + 0] * 257u; g = mode_in->palette[r_in * 4 + 1] * 257u; b = mode_in->palette[r_in * 4 + 2] * 257u; } else { return 31; } /* now convert to output format */ if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { *r_out = r >> shift ; } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { *r_out = r >> shift ; *g_out = g >> shift ; *b_out = b >> shift ; } else if(mode_out->colortype == LCT_PALETTE) { unsigned i; /* a 16-bit color cannot be in the palette */ if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; for(i = 0; i < mode_out->palettesize; i++) { unsigned j = i * 4; if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && (b >> 8) == mode_out->palette[j + 2]) { *r_out = i; return 0; } } return 82; } else { return 31; } return 0; } #ifdef LODEPNG_COMPILE_ENCODER void lodepng_color_stats_init(LodePNGColorStats* stats) { /*stats*/ stats->colored = 0; stats->key = 0; stats->key_r = stats->key_g = stats->key_b = 0; stats->alpha = 0; stats->numcolors = 0; stats->bits = 1; stats->numpixels = 0; /*settings*/ stats->allow_palette = 1; stats->allow_greyscale = 1; } /*function used for debug purposes with C++*/ /*void printColorStats(LodePNGColorStats* p) { std::cout << "colored: " << (int)p->colored << ", "; std::cout << "key: " << (int)p->key << ", "; std::cout << "key_r: " << (int)p->key_r << ", "; std::cout << "key_g: " << (int)p->key_g << ", "; std::cout << "key_b: " << (int)p->key_b << ", "; std::cout << "alpha: " << (int)p->alpha << ", "; std::cout << "numcolors: " << (int)p->numcolors << ", "; std::cout << "bits: " << (int)p->bits << std::endl; }*/ /*Returns how many bits needed to represent given value (max 8 bit)*/ static unsigned getValueRequiredBits(unsigned char value) { if(value == 0 || value == 255) return 1; /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; return 8; } /*stats must already have been inited. */ unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, const unsigned char* in, unsigned w, unsigned h, const LodePNGColorMode* mode_in) { size_t i; ColorTree tree; size_t numpixels = (size_t)w * (size_t)h; unsigned error = 0; /* mark things as done already if it would be impossible to have a more expensive case */ unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; unsigned numcolors_done = 0; unsigned bpp = lodepng_get_bpp(mode_in); unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; unsigned sixteen = 0; /* whether the input image is 16 bit */ unsigned maxnumcolors = 257; if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); stats->numpixels += numpixels; /*if palette not allowed, no need to compute numcolors*/ if(!stats->allow_palette) numcolors_done = 1; color_tree_init(&tree); /*If the stats was already filled in from previous data, fill its palette in tree and mark things as done already if we know they are the most expensive case already*/ if(stats->alpha) alpha_done = 1; if(stats->colored) colored_done = 1; if(stats->bits == 16) numcolors_done = 1; if(stats->bits >= bpp) bits_done = 1; if(stats->numcolors >= maxnumcolors) numcolors_done = 1; if(!numcolors_done) { for(i = 0; i < stats->numcolors; i++) { const unsigned char* color = &stats->palette[i * 4]; error = color_tree_add(&tree, color[0], color[1], color[2], color[3], (unsigned)i); if(error) goto cleanup; } } /*Check if the 16-bit input is truly 16-bit*/ if(mode_in->bitdepth == 16 && !sixteen) { unsigned short r = 0, g = 0, b = 0, a = 0; for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { stats->bits = 16; sixteen = 1; bits_done = 1; numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ break; } } } if(sixteen) { unsigned short r = 0, g = 0, b = 0, a = 0; for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); if(!colored_done && (r != g || r != b)) { stats->colored = 1; colored_done = 1; } if(!alpha_done) { unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { stats->alpha = 1; stats->key = 0; alpha_done = 1; } else if(a == 0 && !stats->alpha && !stats->key) { stats->key = 1; stats->key_r = r; stats->key_g = g; stats->key_b = b; } else if(a == 65535 && stats->key && matchkey) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ stats->alpha = 1; stats->key = 0; alpha_done = 1; } } if(alpha_done && numcolors_done && colored_done && bits_done) break; } if(stats->key && !stats->alpha) { for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ stats->alpha = 1; stats->key = 0; alpha_done = 1; } } } } else /* < 16-bit */ { unsigned char r = 0, g = 0, b = 0, a = 0; unsigned char pr = 0, pg = 0, pb = 0, pa = 0; for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); /*skip if color same as before, this speeds up large non-photographic images with many same colors by avoiding 'color_tree_has' below */ if(i != 0 && r == pr && g == pg && b == pb && a == pa) continue; pr = r; pg = g; pb = b; pa = a; if(!bits_done && stats->bits < 8) { /*only r is checked, < 8 bits is only relevant for grayscale*/ unsigned bits = getValueRequiredBits(r); if(bits > stats->bits) stats->bits = bits; } bits_done = (stats->bits >= bpp); if(!colored_done && (r != g || r != b)) { stats->colored = 1; colored_done = 1; if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ } if(!alpha_done) { unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); if(a != 255 && (a != 0 || (stats->key && !matchkey))) { stats->alpha = 1; stats->key = 0; alpha_done = 1; if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } else if(a == 0 && !stats->alpha && !stats->key) { stats->key = 1; stats->key_r = r; stats->key_g = g; stats->key_b = b; } else if(a == 255 && stats->key && matchkey) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ stats->alpha = 1; stats->key = 0; alpha_done = 1; if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } } if(!numcolors_done) { if(!color_tree_has(&tree, r, g, b, a)) { error = color_tree_add(&tree, r, g, b, a, stats->numcolors); if(error) goto cleanup; if(stats->numcolors < 256) { unsigned char* p = stats->palette; unsigned n = stats->numcolors; p[n * 4 + 0] = r; p[n * 4 + 1] = g; p[n * 4 + 2] = b; p[n * 4 + 3] = a; } ++stats->numcolors; numcolors_done = stats->numcolors >= maxnumcolors; } } if(alpha_done && numcolors_done && colored_done && bits_done) break; } if(stats->key && !stats->alpha) { for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ stats->alpha = 1; stats->key = 0; alpha_done = 1; if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } } } /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ stats->key_r += (stats->key_r << 8); stats->key_g += (stats->key_g << 8); stats->key_b += (stats->key_b << 8); } cleanup: color_tree_cleanup(&tree); return error; } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit (with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for all pixels of an image but only for a few additional values. */ static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, unsigned r, unsigned g, unsigned b, unsigned a) { unsigned error = 0; unsigned char image[8]; LodePNGColorMode mode; lodepng_color_mode_init(&mode); image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; mode.bitdepth = 16; mode.colortype = LCT_RGBA; error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); lodepng_color_mode_cleanup(&mode); return error; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*Computes a minimal PNG color model that can contain all colors as indicated by the stats. The stats should be computed with lodepng_compute_color_stats. mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... This is used if auto_convert is enabled (it is by default). */ static unsigned auto_choose_color(LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, const LodePNGColorStats* stats) { unsigned error = 0; unsigned palettebits; size_t i, n; size_t numpixels = stats->numpixels; unsigned palette_ok, gray_ok; unsigned alpha = stats->alpha; unsigned key = stats->key; unsigned bits = stats->bits; mode_out->key_defined = 0; if(key && numpixels <= 16) { alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ key = 0; if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ } gray_ok = !stats->colored; if(!stats->allow_greyscale) gray_ok = 0; if(!gray_ok && bits < 8) bits = 8; n = stats->numcolors; palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ if(!stats->allow_palette) palette_ok = 0; if(palette_ok) { const unsigned char* p = stats->palette; lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ for(i = 0; i != stats->numcolors; ++i) { error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); if(error) break; } mode_out->colortype = LCT_PALETTE; mode_out->bitdepth = palettebits; if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize && mode_in->bitdepth == mode_out->bitdepth) { /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ lodepng_color_mode_cleanup(mode_out); /*clears palette, keeps the above set colortype and bitdepth fields as-is*/ lodepng_color_mode_copy(mode_out, mode_in); } } else /*8-bit or 16-bit per channel*/ { mode_out->bitdepth = bits; mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) : (gray_ok ? LCT_GREY : LCT_RGB); if(key) { unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ mode_out->key_r = stats->key_r & mask; mode_out->key_g = stats->key_g & mask; mode_out->key_b = stats->key_b & mask; mode_out->key_defined = 1; } } return error; } #endif /* #ifdef LODEPNG_COMPILE_ENCODER */ /*Paeth predictor, used by PNG filter type 4*/ static unsigned char paethPredictor(unsigned char a, unsigned char b, unsigned char c) { /* the subtractions of unsigned char cast it to a signed type. With gcc, short is faster than int, with clang int is as fast (as of april 2023)*/ short pa = (b - c) < 0 ? -(b - c) : (b - c); short pb = (a - c) < 0 ? -(a - c) : (a - c); /* writing it out like this compiles to something faster than introducing a temp variable*/ short pc = (a + b - c - c) < 0 ? -(a + b - c - c) : (a + b - c - c); /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ if(pb < pa) { a = b; pa = pb; } return (pc < pa) ? c : a; } /*shared values used by multiple Adam7 related functions*/ static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ /* Outputs various dimensions and positions in the image related to the Adam7 reduced images. passw: output containing the width of the 7 passes passh: output containing the height of the 7 passes filter_passstart: output containing the index of the start and end of each reduced image with filter bytes padded_passstart output containing the index of the start and end of each reduced image when without filter bytes but with padded scanlines passstart: output containing the index of the start and end of each reduced image without padding between scanlines, but still padding between the images w, h: width and height of non-interlaced image bpp: bits per pixel "padded" is only relevant if bpp is less than 8 and a scanline or image does not end at a full byte */ static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ unsigned i; /*calculate width and height in pixels of each pass*/ for(i = 0; i != 7; ++i) { passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; if(passw[i] == 0) passh[i] = 0; if(passh[i] == 0) passw[i] = 0; } filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; for(i = 0; i != 7; ++i) { /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ filter_passstart[i + 1] = filter_passstart[i] + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); /*bits padded if needed to fill full byte at end of each scanline*/ padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); /*only padded at end of reduced image*/ passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; } } #ifdef LODEPNG_COMPILE_DECODER /* ////////////////////////////////////////////////////////////////////////// */ /* / PNG Decoder / */ /* ////////////////////////////////////////////////////////////////////////// */ /*read the information from the header and store it in the LodePNGInfo. return value is error*/ unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) { unsigned width, height; LodePNGInfo* info = &state->info_png; if(insize == 0 || in == 0) { CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ } if(insize < 33) { CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ } /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ /* TODO: remove this. One should use a new LodePNGState for new sessions */ lodepng_info_cleanup(info); lodepng_info_init(info); if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ } if(lodepng_chunk_length(in + 8) != 13) { CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ } if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ } /*read the values given in the header*/ width = lodepng_read32bitInt(&in[16]); height = lodepng_read32bitInt(&in[20]); /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ if(w) *w = width; if(h) *h = height; info->color.bitdepth = in[24]; info->color.colortype = (LodePNGColorType)in[25]; info->compression_method = in[26]; info->filter_method = in[27]; info->interlace_method = in[28]; /*errors returned only after the parsing so other values are still output*/ /*error: invalid image size*/ if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); /*error: invalid colortype or bitdepth combination*/ state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); if(state->error) return state->error; /*error: only compression method 0 is allowed in the specification*/ if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); /*error: only filter method 0 is allowed in the specification*/ if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); /*error: only interlace methods 0 and 1 exist in the specification*/ if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); if(!state->decoder.ignore_crc) { unsigned crc = lodepng_read32bitInt(&in[29]); unsigned checksum = lodepng_crc32(&in[12], 17); if(crc != checksum) { CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ } } return state->error; } static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned char filterType, size_t length) { /* For PNG filter method 0 unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works byte per byte (bytewidth = 1) precon is the previous unfiltered scanline, recon the result, scanline the current one the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead recon and scanline MAY be the same memory address! precon must be disjoint. */ size_t i; switch(filterType) { case 0: for(i = 0; i != length; ++i) recon[i] = scanline[i]; break; case 1: { size_t j = 0; for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + recon[j]; break; } case 2: if(precon) { for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; } else { for(i = 0; i != length; ++i) recon[i] = scanline[i]; } break; case 3: if(precon) { size_t j = 0; for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); /* Unroll independent paths of this predictor. A 6x and 8x version is also possible but that adds too much code. Whether this speeds up anything depends on compiler and settings. */ if(bytewidth >= 4) { for(; i + 3 < length; i += 4, j += 4) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; recon[i + 0] = s0 + ((r0 + p0) >> 1u); recon[i + 1] = s1 + ((r1 + p1) >> 1u); recon[i + 2] = s2 + ((r2 + p2) >> 1u); recon[i + 3] = s3 + ((r3 + p3) >> 1u); } } else if(bytewidth >= 3) { for(; i + 2 < length; i += 3, j += 3) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; recon[i + 0] = s0 + ((r0 + p0) >> 1u); recon[i + 1] = s1 + ((r1 + p1) >> 1u); recon[i + 2] = s2 + ((r2 + p2) >> 1u); } } else if(bytewidth >= 2) { for(; i + 1 < length; i += 2, j += 2) { unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; recon[i + 0] = s0 + ((r0 + p0) >> 1u); recon[i + 1] = s1 + ((r1 + p1) >> 1u); } } for(; i != length; ++i, ++j) recon[i] = scanline[i] + ((recon[j] + precon[i]) >> 1u); } else { size_t j = 0; for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + (recon[j] >> 1u); } break; case 4: if(precon) { /* Unroll independent paths of this predictor. Whether this speeds up anything depends on compiler and settings. */ if(bytewidth == 8) { unsigned char a0, b0 = 0, c0, d0 = 0, a1, b1 = 0, c1, d1 = 0; unsigned char a2, b2 = 0, c2, d2 = 0, a3, b3 = 0, c3, d3 = 0; unsigned char a4, b4 = 0, c4, d4 = 0, a5, b5 = 0, c5, d5 = 0; unsigned char a6, b6 = 0, c6, d6 = 0, a7, b7 = 0, c7, d7 = 0; for(i = 0; i + 7 < length; i += 8) { c0 = b0; c1 = b1; c2 = b2; c3 = b3; c4 = b4; c5 = b5; c6 = b6; c7 = b7; b0 = precon[i + 0]; b1 = precon[i + 1]; b2 = precon[i + 2]; b3 = precon[i + 3]; b4 = precon[i + 4]; b5 = precon[i + 5]; b6 = precon[i + 6]; b7 = precon[i + 7]; a0 = d0; a1 = d1; a2 = d2; a3 = d3; a4 = d4; a5 = d5; a6 = d6; a7 = d7; d0 = scanline[i + 0] + paethPredictor(a0, b0, c0); d1 = scanline[i + 1] + paethPredictor(a1, b1, c1); d2 = scanline[i + 2] + paethPredictor(a2, b2, c2); d3 = scanline[i + 3] + paethPredictor(a3, b3, c3); d4 = scanline[i + 4] + paethPredictor(a4, b4, c4); d5 = scanline[i + 5] + paethPredictor(a5, b5, c5); d6 = scanline[i + 6] + paethPredictor(a6, b6, c6); d7 = scanline[i + 7] + paethPredictor(a7, b7, c7); recon[i + 0] = d0; recon[i + 1] = d1; recon[i + 2] = d2; recon[i + 3] = d3; recon[i + 4] = d4; recon[i + 5] = d5; recon[i + 6] = d6; recon[i + 7] = d7; } } else if(bytewidth == 6) { unsigned char a0, b0 = 0, c0, d0 = 0, a1, b1 = 0, c1, d1 = 0; unsigned char a2, b2 = 0, c2, d2 = 0, a3, b3 = 0, c3, d3 = 0; unsigned char a4, b4 = 0, c4, d4 = 0, a5, b5 = 0, c5, d5 = 0; for(i = 0; i + 5 < length; i += 6) { c0 = b0; c1 = b1; c2 = b2; c3 = b3; c4 = b4; c5 = b5; b0 = precon[i + 0]; b1 = precon[i + 1]; b2 = precon[i + 2]; b3 = precon[i + 3]; b4 = precon[i + 4]; b5 = precon[i + 5]; a0 = d0; a1 = d1; a2 = d2; a3 = d3; a4 = d4; a5 = d5; d0 = scanline[i + 0] + paethPredictor(a0, b0, c0); d1 = scanline[i + 1] + paethPredictor(a1, b1, c1); d2 = scanline[i + 2] + paethPredictor(a2, b2, c2); d3 = scanline[i + 3] + paethPredictor(a3, b3, c3); d4 = scanline[i + 4] + paethPredictor(a4, b4, c4); d5 = scanline[i + 5] + paethPredictor(a5, b5, c5); recon[i + 0] = d0; recon[i + 1] = d1; recon[i + 2] = d2; recon[i + 3] = d3; recon[i + 4] = d4; recon[i + 5] = d5; } } else if(bytewidth == 4) { unsigned char a0, b0 = 0, c0, d0 = 0, a1, b1 = 0, c1, d1 = 0; unsigned char a2, b2 = 0, c2, d2 = 0, a3, b3 = 0, c3, d3 = 0; for(i = 0; i + 3 < length; i += 4) { c0 = b0; c1 = b1; c2 = b2; c3 = b3; b0 = precon[i + 0]; b1 = precon[i + 1]; b2 = precon[i + 2]; b3 = precon[i + 3]; a0 = d0; a1 = d1; a2 = d2; a3 = d3; d0 = scanline[i + 0] + paethPredictor(a0, b0, c0); d1 = scanline[i + 1] + paethPredictor(a1, b1, c1); d2 = scanline[i + 2] + paethPredictor(a2, b2, c2); d3 = scanline[i + 3] + paethPredictor(a3, b3, c3); recon[i + 0] = d0; recon[i + 1] = d1; recon[i + 2] = d2; recon[i + 3] = d3; } } else if(bytewidth == 3) { unsigned char a0, b0 = 0, c0, d0 = 0; unsigned char a1, b1 = 0, c1, d1 = 0; unsigned char a2, b2 = 0, c2, d2 = 0; for(i = 0; i + 2 < length; i += 3) { c0 = b0; c1 = b1; c2 = b2; b0 = precon[i + 0]; b1 = precon[i + 1]; b2 = precon[i + 2]; a0 = d0; a1 = d1; a2 = d2; d0 = scanline[i + 0] + paethPredictor(a0, b0, c0); d1 = scanline[i + 1] + paethPredictor(a1, b1, c1); d2 = scanline[i + 2] + paethPredictor(a2, b2, c2); recon[i + 0] = d0; recon[i + 1] = d1; recon[i + 2] = d2; } } else if(bytewidth == 2) { unsigned char a0, b0 = 0, c0, d0 = 0; unsigned char a1, b1 = 0, c1, d1 = 0; for(i = 0; i + 1 < length; i += 2) { c0 = b0; c1 = b1; b0 = precon[i + 0]; b1 = precon[i + 1]; a0 = d0; a1 = d1; d0 = scanline[i + 0] + paethPredictor(a0, b0, c0); d1 = scanline[i + 1] + paethPredictor(a1, b1, c1); recon[i + 0] = d0; recon[i + 1] = d1; } } else if(bytewidth == 1) { unsigned char a, b = 0, c, d = 0; for(i = 0; i != length; ++i) { c = b; b = precon[i]; a = d; d = scanline[i] + paethPredictor(a, b, c); recon[i] = d; } } else { /* Normally not a possible case, but this would handle it correctly */ for(i = 0; i != bytewidth; ++i) { recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ } } /* finish any remaining bytes */ for(; i != length; ++i) { recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); } } else { size_t j = 0; for(i = 0; i != bytewidth; ++i) { recon[i] = scanline[i]; } for(i = bytewidth; i != length; ++i, ++j) { /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ recon[i] = (scanline[i] + recon[j]); } } break; default: return 36; /*error: invalid filter type given*/ } return 0; } static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { /* For PNG filter method 0 this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) */ unsigned y; unsigned char* prevline = 0; /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ size_t bytewidth = (bpp + 7u) / 8u; /*the width of a scanline in bytes, not including the filter type*/ size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; for(y = 0; y < h; ++y) { size_t outindex = linebytes * y; size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ unsigned char filterType = in[inindex]; CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); prevline = &out[outindex]; } return 0; } /* in: Adam7 interlaced image, with no padding bits between scanlines, but between reduced images so that each reduced image starts at a byte. out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h bpp: bits per pixel out has the following size in bits: w * h * bpp. in is possibly bigger due to padding bits between reduced images. out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation (because that's likely a little bit faster) NOTE: comments about padding bits are only relevant if bpp < 8 */ static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); if(bpp >= 8) { for(i = 0; i != 7; ++i) { unsigned x, y, b; size_t bytewidth = bpp / 8u; for(y = 0; y < passh[i]; ++y) for(x = 0; x < passw[i]; ++x) { size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; for(b = 0; b < bytewidth; ++b) { out[pixeloutstart + b] = in[pixelinstart + b]; } } } } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { for(i = 0; i != 7; ++i) { unsigned x, y, b; unsigned ilinebits = bpp * passw[i]; unsigned olinebits = bpp * w; size_t obp, ibp; /*bit pointers (for out and in buffer)*/ for(y = 0; y < passh[i]; ++y) for(x = 0; x < passw[i]; ++x) { ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; for(b = 0; b < bpp; ++b) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } } } } } static void removePaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h) { /* After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers for the Adam7 code, the color convert code and the output to the user. in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 only useful if (ilinebits - olinebits) is a value in the range 1..7 */ unsigned y; size_t diff = ilinebits - olinebits; size_t ibp = 0, obp = 0; /*input and output bit pointers*/ for(y = 0; y < h; ++y) { size_t x; for(x = 0; x < olinebits; ++x) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } ibp += diff; } } /*out must be buffer big enough to contain full image, and in must contain the full decompressed data from the IDAT chunks (with filter index bytes and possible padding bits) return value is error*/ static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, unsigned w, unsigned h, const LodePNGInfo* info_png) { /* This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. Steps: *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace NOTE: the in buffer will be overwritten with intermediate data! */ unsigned bpp = lodepng_get_bpp(&info_png->color); if(bpp == 0) return 31; /*error: invalid colortype*/ if(info_png->interlace_method == 0) { if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); } /*we can immediately filter into the out buffer, no other steps needed*/ else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); } else /*interlace_method is 1 (Adam7)*/ { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); for(i = 0; i != 7; ++i) { CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, move bytes instead of bits or move not at all*/ if(bpp < 8) { /*remove padding bits in scanlines; after this there still may be padding bits between the different reduced images: each reduced image still starts nicely at a byte*/ removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); } } Adam7_deinterlace(out, in, w, h, bpp); } return 0; } static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { unsigned pos = 0, i; color->palettesize = chunkLength / 3u; if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ lodepng_color_mode_alloc_palette(color); if(!color->palette && color->palettesize) { color->palettesize = 0; return 83; /*alloc fail*/ } for(i = 0; i != color->palettesize; ++i) { color->palette[4 * i + 0] = data[pos++]; /*R*/ color->palette[4 * i + 1] = data[pos++]; /*G*/ color->palette[4 * i + 2] = data[pos++]; /*B*/ color->palette[4 * i + 3] = 255; /*alpha*/ } return 0; /* OK */ } static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { unsigned i; if(color->colortype == LCT_PALETTE) { /*error: more alpha values given than there are palette entries*/ if(chunkLength > color->palettesize) return 39; for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; } else if(color->colortype == LCT_GREY) { /*error: this chunk must be 2 bytes for grayscale image*/ if(chunkLength != 2) return 30; color->key_defined = 1; color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; } else if(color->colortype == LCT_RGB) { /*error: this chunk must be 6 bytes for RGB image*/ if(chunkLength != 6) return 41; color->key_defined = 1; color->key_r = 256u * data[0] + data[1]; color->key_g = 256u * data[2] + data[3]; color->key_b = 256u * data[4] + data[5]; } else return 42; /*error: tRNS chunk not allowed for other color models*/ return 0; /* OK */ } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*background color chunk (bKGD)*/ static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(info->color.colortype == LCT_PALETTE) { /*error: this chunk must be 1 byte for indexed color image*/ if(chunkLength != 1) return 43; /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ if(data[0] >= info->color.palettesize) return 103; info->background_defined = 1; info->background_r = info->background_g = info->background_b = data[0]; } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { /*error: this chunk must be 2 bytes for grayscale image*/ if(chunkLength != 2) return 44; /*the values are truncated to bitdepth in the PNG file*/ info->background_defined = 1; info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { /*error: this chunk must be 6 bytes for grayscale image*/ if(chunkLength != 6) return 45; /*the values are truncated to bitdepth in the PNG file*/ info->background_defined = 1; info->background_r = 256u * data[0] + data[1]; info->background_g = 256u * data[2] + data[3]; info->background_b = 256u * data[4] + data[5]; } return 0; /* OK */ } /*text chunk (tEXt)*/ static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { unsigned error = 0; char *key = 0, *str = 0; while(!error) /*not really a while loop, only used to break on error*/ { unsigned length, string2_begin; length = 0; while(length < chunkLength && data[length] != 0) ++length; /*even though it's not allowed by the standard, no error is thrown if there's no null termination char, if the text is empty*/ if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ key = (char*)lodepng_malloc(length + 1); if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(key, data, length); key[length] = 0; string2_begin = length + 1; /*skip keyword null terminator*/ length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); str = (char*)lodepng_malloc(length + 1); if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(str, data + string2_begin, length); str[length] = 0; error = lodepng_add_text(info, key, str); break; } lodepng_free(key); lodepng_free(str); return error; } /*compressed text chunk (zTXt)*/ static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, const unsigned char* data, size_t chunkLength) { unsigned error = 0; /*copy the object to change parameters in it*/ LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; unsigned length, string2_begin; char *key = 0; unsigned char* str = 0; size_t size = 0; while(!error) /*not really a while loop, only used to break on error*/ { for(length = 0; length < chunkLength && data[length] != 0; ++length) ; if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ key = (char*)lodepng_malloc(length + 1); if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(key, data, length); key[length] = 0; if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ string2_begin = length + 2; if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ length = (unsigned)chunkLength - string2_begin; zlibsettings.max_output_size = decoder->max_text_size; /*will fail if zlib error, e.g. if length is too small*/ error = zlib_decompress(&str, &size, 0, &data[string2_begin], length, &zlibsettings); /*error: compressed text larger than decoder->max_text_size*/ if(error && size > zlibsettings.max_output_size) error = 112; if(error) break; error = lodepng_add_text_sized(info, key, (char*)str, size); break; } lodepng_free(key); lodepng_free(str); return error; } /*international text chunk (iTXt)*/ static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, const unsigned char* data, size_t chunkLength) { unsigned error = 0; unsigned i; /*copy the object to change parameters in it*/ LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; unsigned length, begin, compressed; char *key = 0, *langtag = 0, *transkey = 0; while(!error) /*not really a while loop, only used to break on error*/ { /*Quick check if the chunk length isn't too small. Even without check it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ /*read the key*/ for(length = 0; length < chunkLength && data[length] != 0; ++length) ; if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ key = (char*)lodepng_malloc(length + 1); if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(key, data, length); key[length] = 0; /*read the compression method*/ compressed = data[length + 1]; if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ /*even though it's not allowed by the standard, no error is thrown if there's no null termination char, if the text is empty for the next 3 texts*/ /*read the langtag*/ begin = length + 3; length = 0; for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; langtag = (char*)lodepng_malloc(length + 1); if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(langtag, data + begin, length); langtag[length] = 0; /*read the transkey*/ begin += length + 1; length = 0; for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; transkey = (char*)lodepng_malloc(length + 1); if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ lodepng_memcpy(transkey, data + begin, length); transkey[length] = 0; /*read the actual text*/ begin += length + 1; length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; if(compressed) { unsigned char* str = 0; size_t size = 0; zlibsettings.max_output_size = decoder->max_text_size; /*will fail if zlib error, e.g. if length is too small*/ error = zlib_decompress(&str, &size, 0, &data[begin], length, &zlibsettings); /*error: compressed text larger than decoder->max_text_size*/ if(error && size > zlibsettings.max_output_size) error = 112; if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); lodepng_free(str); } else { error = lodepng_add_itext_sized(info, key, langtag, transkey, (const char*)(data + begin), length); } break; } lodepng_free(key); lodepng_free(langtag); lodepng_free(transkey); return error; } static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ info->time_defined = 1; info->time.year = 256u * data[0] + data[1]; info->time.month = data[2]; info->time.day = data[3]; info->time.hour = data[4]; info->time.minute = data[5]; info->time.second = data[6]; return 0; /* OK */ } static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ info->phys_defined = 1; info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; info->phys_unit = data[8]; return 0; /* OK */ } static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ info->gama_defined = 1; info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; return 0; /* OK */ } static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ info->chrm_defined = 1; info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; return 0; /* OK */ } static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ info->srgb_defined = 1; info->srgb_intent = data[0]; return 0; /* OK */ } static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, const unsigned char* data, size_t chunkLength) { unsigned error = 0; unsigned i; size_t size = 0; /*copy the object to change parameters in it*/ LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; unsigned length, string2_begin; if(info->iccp_defined) lodepng_clear_icc(info); for(length = 0; length < chunkLength && data[length] != 0; ++length) ; if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ if(length < 1 || length > 79) return 89; /*keyword too short or long*/ info->iccp_name = (char*)lodepng_malloc(length + 1); if(!info->iccp_name) return 83; /*alloc fail*/ info->iccp_name[length] = 0; for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ string2_begin = length + 2; if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ length = (unsigned)chunkLength - string2_begin; zlibsettings.max_output_size = decoder->max_icc_size; error = zlib_decompress(&info->iccp_profile, &size, 0, &data[string2_begin], length, &zlibsettings); /*error: ICC profile larger than decoder->max_icc_size*/ if(error && size > zlibsettings.max_output_size) error = 113; info->iccp_profile_size = (unsigned)size; if(!error && !info->iccp_profile_size) error = 123; /*invalid ICC profile size*/ if(!error) info->iccp_defined = 1; return error; } static unsigned readChunk_cICP(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 4) return 117; /*invalid cICP chunk size*/ info->cicp_defined = 1; /* No error checking for value ranges is done here, that is up to a CICP handling library, not the PNG decoding. Just pass on the metadata. */ info->cicp_color_primaries = data[0]; info->cicp_transfer_function = data[1]; info->cicp_matrix_coefficients = data[2]; info->cicp_video_full_range_flag = data[3]; return 0; /* OK */ } static unsigned readChunk_mDCV(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 24) return 119; /*invalid mDCV chunk size*/ info->mdcv_defined = 1; info->mdcv_red_x = 256u * data[0] + data[1]; info->mdcv_red_y = 256u * data[2] + data[3]; info->mdcv_green_x = 256u * data[4] + data[5]; info->mdcv_green_y = 256u * data[6] + data[7]; info->mdcv_blue_x = 256u * data[8] + data[9]; info->mdcv_blue_y = 256u * data[10] + data[11]; info->mdcv_white_x = 256u * data[12] + data[13]; info->mdcv_white_y = 256u * data[14] + data[15]; info->mdcv_max_luminance = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; info->mdcv_min_luminance = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; return 0; /* OK */ } static unsigned readChunk_cLLI(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 8) return 120; /*invalid cLLI chunk size*/ info->clli_defined = 1; info->clli_max_cll = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; info->clli_max_fall = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; return 0; /* OK */ } static unsigned readChunk_eXIf(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { return lodepng_set_exif(info, data, (unsigned)chunkLength); } /*significant bits chunk (sBIT)*/ static unsigned readChunk_sBIT(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { unsigned bitdepth = (info->color.colortype == LCT_PALETTE) ? 8 : info->color.bitdepth; if(info->color.colortype == LCT_GREY) { /*error: this chunk must be 1 bytes for grayscale image*/ if(chunkLength != 1) return 114; if(data[0] == 0 || data[0] > bitdepth) return 115; info->sbit_defined = 1; info->sbit_r = info->sbit_g = info->sbit_b = data[0]; /*setting g and b is not required, but sensible*/ } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_PALETTE) { /*error: this chunk must be 3 bytes for RGB and palette image*/ if(chunkLength != 3) return 114; if(data[0] == 0 || data[1] == 0 || data[2] == 0) return 115; if(data[0] > bitdepth || data[1] > bitdepth || data[2] > bitdepth) return 115; info->sbit_defined = 1; info->sbit_r = data[0]; info->sbit_g = data[1]; info->sbit_b = data[2]; } else if(info->color.colortype == LCT_GREY_ALPHA) { /*error: this chunk must be 2 byte for grayscale with alpha image*/ if(chunkLength != 2) return 114; if(data[0] == 0 || data[1] == 0) return 115; if(data[0] > bitdepth || data[1] > bitdepth) return 115; info->sbit_defined = 1; info->sbit_r = info->sbit_g = info->sbit_b = data[0]; /*setting g and b is not required, but sensible*/ info->sbit_a = data[1]; } else if(info->color.colortype == LCT_RGBA) { /*error: this chunk must be 4 bytes for grayscale image*/ if(chunkLength != 4) return 114; if(data[0] == 0 || data[1] == 0 || data[2] == 0 || data[3] == 0) return 115; if(data[0] > bitdepth || data[1] > bitdepth || data[2] > bitdepth || data[3] > bitdepth) return 115; info->sbit_defined = 1; info->sbit_r = data[0]; info->sbit_g = data[1]; info->sbit_b = data[2]; info->sbit_a = data[3]; } return 0; /* OK */ } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, const unsigned char* in, size_t insize) { const unsigned char* chunk = in + pos; unsigned chunkLength; const unsigned char* data; unsigned unhandled = 0; unsigned error = 0; if(pos + 4 > insize) return 30; chunkLength = lodepng_chunk_length(chunk); if(chunkLength > 2147483647) return 63; data = lodepng_chunk_data_const(chunk); if(chunkLength + 12 > insize - pos) return 30; if(lodepng_chunk_type_equals(chunk, "PLTE")) { error = readChunk_PLTE(&state->info_png.color, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { error = readChunk_tRNS(&state->info_png.color, data, chunkLength); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { error = readChunk_bKGD(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { error = readChunk_tEXt(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "tIME")) { error = readChunk_tIME(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { error = readChunk_pHYs(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { error = readChunk_gAMA(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { error = readChunk_cHRM(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { error = readChunk_sRGB(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "cICP")) { error = readChunk_cICP(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "mDCV")) { error = readChunk_mDCV(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "cLLI")) { error = readChunk_cLLI(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "eXIf")) { error = readChunk_eXIf(&state->info_png, data, chunkLength); } else if(lodepng_chunk_type_equals(chunk, "sBIT")) { error = readChunk_sBIT(&state->info_png, data, chunkLength); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } else { /* unhandled chunk is ok (is not an error) */ unhandled = 1; } if(!error && !unhandled && !state->decoder.ignore_crc) { if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ } return error; } /*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) { unsigned char IEND = 0; const unsigned char* chunk; /*points to beginning of next chunk*/ unsigned char* idat; /*the data from idat chunks, zlib compressed*/ size_t idatsize = 0; unsigned char* scanlines = 0; size_t scanlines_size = 0, expected_size = 0; size_t outsize = 0; /*for unknown chunk order*/ unsigned unknown = 0; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /* safe output values in case error happens */ *out = 0; *w = *h = 0; state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ if(state->error) return; if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ } /*the input filesize is a safe upper bound for the sum of idat chunks size*/ idat = (unsigned char*)lodepng_malloc(insize); if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ chunk = &in[33]; /*first byte of the first chunk after the header*/ /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer*/ while(!IEND && !state->error) { unsigned chunkLength; const unsigned char* data; /*the data in the chunk*/ size_t pos = (size_t)(chunk - in); /*error: next chunk out of bounds of the in buffer*/ if(chunk < in || pos + 12 > insize) { if(state->decoder.ignore_end) break; /*other errors may still happen though*/ CERROR_BREAK(state->error, 30); } /*length of the data of the chunk, excluding the 12 bytes for length, chunk type and CRC*/ chunkLength = lodepng_chunk_length(chunk); /*error: chunk length larger than the max PNG chunk size*/ if(chunkLength > 2147483647) { if(state->decoder.ignore_end) break; /*other errors may still happen though*/ CERROR_BREAK(state->error, 63); } if(pos + (size_t)chunkLength + 12 > insize || pos + (size_t)chunkLength + 12 < pos) { CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk (or int overflow)*/ } data = lodepng_chunk_data_const(chunk); unknown = 0; /*IDAT chunk, containing compressed image data*/ if(lodepng_chunk_type_equals(chunk, "IDAT")) { size_t newsize; if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); if(newsize > insize) CERROR_BREAK(state->error, 95); lodepng_memcpy(idat + idatsize, data, chunkLength); idatsize += chunkLength; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS critical_pos = 3; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } else if(lodepng_chunk_type_equals(chunk, "IEND")) { /*IEND chunk*/ IEND = 1; } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { /*palette chunk (PLTE)*/ state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); if(state->error) break; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS critical_pos = 2; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that affects the alpha channel of pixels. */ state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); if(state->error) break; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*background color chunk (bKGD)*/ } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { state->error = readChunk_bKGD(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { /*text chunk (tEXt)*/ if(state->decoder.read_text_chunks) { state->error = readChunk_tEXt(&state->info_png, data, chunkLength); if(state->error) break; } } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { /*compressed text chunk (zTXt)*/ if(state->decoder.read_text_chunks) { state->error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); if(state->error) break; } } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { /*international text chunk (iTXt)*/ if(state->decoder.read_text_chunks) { state->error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); if(state->error) break; } } else if(lodepng_chunk_type_equals(chunk, "tIME")) { state->error = readChunk_tIME(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { state->error = readChunk_pHYs(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { state->error = readChunk_gAMA(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { state->error = readChunk_cHRM(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { state->error = readChunk_sRGB(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "cICP")) { state->error = readChunk_cICP(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "mDCV")) { state->error = readChunk_mDCV(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "cLLI")) { state->error = readChunk_cLLI(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "eXIf")) { state->error = readChunk_eXIf(&state->info_png, data, chunkLength); if(state->error) break; } else if(lodepng_chunk_type_equals(chunk, "sBIT")) { state->error = readChunk_sBIT(&state->info_png, data, chunkLength); if(state->error) break; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { if(!lodepng_chunk_type_name_valid(chunk)) { CERROR_BREAK(state->error, 121); /* invalid chunk type name */ } if(lodepng_chunk_reserved(chunk)) { CERROR_BREAK(state->error, 122); /* invalid third lowercase character */ } /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { CERROR_BREAK(state->error, 69); } unknown = 1; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(state->decoder.remember_unknown_chunks) { state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); if(state->error) break; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ } if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); } if(!state->error && state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ } if(!state->error) { /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. If the decompressed size does not match the prediction, the image must be corrupt.*/ if(state->info_png.interlace_method == 0) { unsigned bpp = lodepng_get_bpp(&state->info_png.color); expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); } else { unsigned bpp = lodepng_get_bpp(&state->info_png.color); /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ expected_size = 0; expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); } state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); } if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ lodepng_free(idat); if(!state->error) { outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); *out = (unsigned char*)lodepng_malloc(outsize); if(!*out) state->error = 83; /*alloc fail*/ } if(!state->error) { lodepng_memset(*out, 0, outsize); state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); } lodepng_free(scanlines); } unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize) { *out = 0; decodeGeneric(out, w, h, state, in, insize); if(state->error) return state->error; if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { /*same color type, no copying or converting of data needed*/ /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype the raw image has to the end user*/ if(!state->decoder.color_convert) { state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); if(state->error) return state->error; } } else { /*color conversion needed*/ unsigned char* data = *out; size_t outsize; /*TODO: check if this works according to the statement in the documentation: "The converter can convert from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) && !(state->info_raw.bitdepth == 8)) { return 56; /*unsupported color mode conversion*/ } outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); *out = (unsigned char*)lodepng_malloc(outsize); if(!(*out)) { state->error = 83; /*alloc fail*/ } else state->error = lodepng_convert(*out, data, &state->info_raw, &state->info_png.color, *w, *h); lodepng_free(data); } return state->error; } unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize, LodePNGColorType colortype, unsigned bitdepth) { unsigned error; LodePNGState state; lodepng_state_init(&state); state.info_raw.colortype = colortype; state.info_raw.bitdepth = bitdepth; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*disable reading things that this function doesn't output*/ state.decoder.read_text_chunks = 0; state.decoder.remember_unknown_chunks = 0; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ error = lodepng_decode(out, w, h, &state, in, insize); lodepng_state_cleanup(&state); return error; } unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); } unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); } #ifdef LODEPNG_COMPILE_DISK unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer = 0; size_t buffersize; unsigned error; /* safe output values in case error happens */ *out = 0; *w = *h = 0; error = lodepng_load_file(&buffer, &buffersize, filename); if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); lodepng_free(buffer); return error; } unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); } unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); } #endif /*LODEPNG_COMPILE_DISK*/ void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { settings->color_convert = 1; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS settings->read_text_chunks = 1; settings->remember_unknown_chunks = 0; settings->max_text_size = 16777216; settings->max_icc_size = 16777216; /* 16MB is much more than enough for any reasonable ICC profile */ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ settings->ignore_crc = 0; settings->ignore_critical = 0; settings->ignore_end = 0; lodepng_decompress_settings_init(&settings->zlibsettings); } #endif /*LODEPNG_COMPILE_DECODER*/ #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) void lodepng_state_init(LodePNGState* state) { #ifdef LODEPNG_COMPILE_DECODER lodepng_decoder_settings_init(&state->decoder); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER lodepng_encoder_settings_init(&state->encoder); #endif /*LODEPNG_COMPILE_ENCODER*/ lodepng_color_mode_init(&state->info_raw); lodepng_info_init(&state->info_png); state->error = 1; } void lodepng_state_cleanup(LodePNGState* state) { lodepng_color_mode_cleanup(&state->info_raw); lodepng_info_cleanup(&state->info_png); } unsigned lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { lodepng_state_cleanup(dest); *dest = *source; lodepng_color_mode_init(&dest->info_raw); lodepng_info_init(&dest->info_png); dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return dest->error; dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); return dest->error; } #endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ #ifdef LODEPNG_COMPILE_ENCODER /* ////////////////////////////////////////////////////////////////////////// */ /* / PNG Encoder / */ /* ////////////////////////////////////////////////////////////////////////// */ static unsigned writeSignature(ucvector* out) { size_t pos = out->size; const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; /*8 bytes PNG signature, aka the magic bytes*/ if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ lodepng_memcpy(out->data + pos, signature, 8); return 0; } static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { unsigned char *chunk, *data; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); data = chunk + 8; lodepng_set32bitInt(data + 0, w); /*width*/ lodepng_set32bitInt(data + 4, h); /*height*/ data[8] = (unsigned char)bitdepth; /*bit depth*/ data[9] = (unsigned char)colortype; /*color type*/ data[10] = 0; /*compression method*/ data[11] = 0; /*filter method*/ data[12] = interlace_method; /*interlace method*/ lodepng_chunk_generate_crc(chunk); return 0; } /* only adds the chunk if needed (there is a key or palette with alpha) */ static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { unsigned char* chunk; size_t i, j = 8; if(info->palettesize == 0 || info->palettesize > 256) { return 68; /*invalid palette size, it is only allowed to be 1-256*/ } CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); for(i = 0; i != info->palettesize; ++i) { /*add all channels except alpha channel*/ chunk[j++] = info->palette[i * 4 + 0]; chunk[j++] = info->palette[i * 4 + 1]; chunk[j++] = info->palette[i * 4 + 2]; } lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { unsigned char* chunk = 0; if(info->colortype == LCT_PALETTE) { size_t i, amount = info->palettesize; /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ for(i = info->palettesize; i != 0; --i) { if(info->palette[4 * (i - 1) + 3] != 255) break; --amount; } if(amount) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); /*add the alpha channel values from the palette*/ for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; } } else if(info->colortype == LCT_GREY) { if(info->key_defined) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); chunk[8] = (unsigned char)(info->key_r >> 8); chunk[9] = (unsigned char)(info->key_r & 255); } } else if(info->colortype == LCT_RGB) { if(info->key_defined) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); chunk[8] = (unsigned char)(info->key_r >> 8); chunk[9] = (unsigned char)(info->key_r & 255); chunk[10] = (unsigned char)(info->key_g >> 8); chunk[11] = (unsigned char)(info->key_g & 255); chunk[12] = (unsigned char)(info->key_b >> 8); chunk[13] = (unsigned char)(info->key_b & 255); } } if(chunk) lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, const LodePNGCompressSettings* zlibsettings) { unsigned error = 0; unsigned char* zlib = 0; size_t pos = 0; size_t zlibsize = 0; /* max chunk length allowed by the specification is 2147483647 bytes */ const size_t max_chunk_length = 2147483647u; error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); while(!error) { if(zlibsize - pos > max_chunk_length) { error = lodepng_chunk_createv(out, max_chunk_length, "IDAT", zlib + pos); pos += max_chunk_length; } else { error = lodepng_chunk_createv(out, zlibsize - pos, "IDAT", zlib + pos); break; } } lodepng_free(zlib); return error; } static unsigned addChunk_IEND(ucvector* out) { return lodepng_chunk_createv(out, 0, "IEND", 0); } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { unsigned char* chunk = 0; size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); size_t size = keysize + 1 + textsize; if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); lodepng_memcpy(chunk + 8, keyword, keysize); chunk[8 + keysize] = 0; /*null termination char*/ lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, const LodePNGCompressSettings* zlibsettings) { unsigned error = 0; unsigned char* chunk = 0; unsigned char* compressed = 0; size_t compressedsize = 0; size_t textsize = lodepng_strlen(textstring); size_t keysize = lodepng_strlen(keyword); if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ error = zlib_compress(&compressed, &compressedsize, (const unsigned char*)textstring, textsize, zlibsettings); if(!error) { size_t size = keysize + 2 + compressedsize; error = lodepng_chunk_init(&chunk, out, size, "zTXt"); } if(!error) { lodepng_memcpy(chunk + 8, keyword, keysize); chunk[8 + keysize] = 0; /*null termination char*/ chunk[9 + keysize] = 0; /*compression method: 0*/ lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); lodepng_chunk_generate_crc(chunk); } lodepng_free(compressed); return error; } static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, const char* transkey, const char* textstring, const LodePNGCompressSettings* zlibsettings) { unsigned error = 0; unsigned char* chunk = 0; unsigned char* compressed = 0; size_t compressedsize = 0; size_t textsize = lodepng_strlen(textstring); size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ if(compress) { error = zlib_compress(&compressed, &compressedsize, (const unsigned char*)textstring, textsize, zlibsettings); } if(!error) { size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); error = lodepng_chunk_init(&chunk, out, size, "iTXt"); } if(!error) { size_t pos = 8; lodepng_memcpy(chunk + pos, keyword, keysize); pos += keysize; chunk[pos++] = 0; /*null termination char*/ chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ chunk[pos++] = 0; /*compression method: 0*/ lodepng_memcpy(chunk + pos, langtag, langsize); pos += langsize; chunk[pos++] = 0; /*null termination char*/ lodepng_memcpy(chunk + pos, transkey, transsize); pos += transsize; chunk[pos++] = 0; /*null termination char*/ if(compress) { lodepng_memcpy(chunk + pos, compressed, compressedsize); } else { lodepng_memcpy(chunk + pos, textstring, textsize); } lodepng_chunk_generate_crc(chunk); } lodepng_free(compressed); return error; } static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk = 0; if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); chunk[8] = (unsigned char)(info->background_r >> 8); chunk[9] = (unsigned char)(info->background_r & 255); } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); chunk[8] = (unsigned char)(info->background_r >> 8); chunk[9] = (unsigned char)(info->background_r & 255); chunk[10] = (unsigned char)(info->background_g >> 8); chunk[11] = (unsigned char)(info->background_g & 255); chunk[12] = (unsigned char)(info->background_b >> 8); chunk[13] = (unsigned char)(info->background_b & 255); } else if(info->color.colortype == LCT_PALETTE) { CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ } if(chunk) lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); chunk[8] = (unsigned char)(time->year >> 8); chunk[9] = (unsigned char)(time->year & 255); chunk[10] = (unsigned char)time->month; chunk[11] = (unsigned char)time->day; chunk[12] = (unsigned char)time->hour; chunk[13] = (unsigned char)time->minute; chunk[14] = (unsigned char)time->second; lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); lodepng_set32bitInt(chunk + 8, info->phys_x); lodepng_set32bitInt(chunk + 12, info->phys_y); chunk[16] = info->phys_unit; lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); lodepng_set32bitInt(chunk + 8, info->gama_gamma); lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); lodepng_set32bitInt(chunk + 8, info->chrm_white_x); lodepng_set32bitInt(chunk + 12, info->chrm_white_y); lodepng_set32bitInt(chunk + 16, info->chrm_red_x); lodepng_set32bitInt(chunk + 20, info->chrm_red_y); lodepng_set32bitInt(chunk + 24, info->chrm_green_x); lodepng_set32bitInt(chunk + 28, info->chrm_green_y); lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { unsigned char data = info->srgb_intent; return lodepng_chunk_createv(out, 1, "sRGB", &data); } static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, const LodePNGCompressSettings* zlibsettings) { unsigned error = 0; unsigned char* chunk = 0; unsigned char* compressed = 0; size_t compressedsize = 0; size_t keysize = lodepng_strlen(info->iccp_name); if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ error = zlib_compress(&compressed, &compressedsize, info->iccp_profile, info->iccp_profile_size, zlibsettings); if(!error) { size_t size = keysize + 2 + compressedsize; error = lodepng_chunk_init(&chunk, out, size, "iCCP"); } if(!error) { lodepng_memcpy(chunk + 8, info->iccp_name, keysize); chunk[8 + keysize] = 0; /*null termination char*/ chunk[9 + keysize] = 0; /*compression method: 0*/ lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); lodepng_chunk_generate_crc(chunk); } lodepng_free(compressed); return error; } static unsigned addChunk_cICP(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; /* Allow up to 255 since they are bytes. The ITU-R-BT.709 spec has a more restricted set of valid values for each field, but that's up to the error handling of a CICP library, not the PNG encoding/decoding, to manage. */ if(info->cicp_color_primaries > 255) return 116; if(info->cicp_transfer_function > 255) return 116; if(info->cicp_matrix_coefficients > 255) return 116; if(info->cicp_video_full_range_flag > 255) return 116; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "cICP")); chunk[8 + 0] = (unsigned char)info->cicp_color_primaries; chunk[8 + 1] = (unsigned char)info->cicp_transfer_function; chunk[8 + 2] = (unsigned char)info->cicp_matrix_coefficients; chunk[8 + 3] = (unsigned char)info->cicp_video_full_range_flag; lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_mDCV(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; /* Allow up to 65535 since they are 16-bit ints. */ if(info->mdcv_red_x > 65535) return 118; if(info->mdcv_red_y > 65535) return 118; if(info->mdcv_green_x > 65535) return 118; if(info->mdcv_green_y > 65535) return 118; if(info->mdcv_blue_x > 65535) return 118; if(info->mdcv_blue_y > 65535) return 118; if(info->mdcv_white_x > 65535) return 118; if(info->mdcv_white_y > 65535) return 118; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 24, "mDCV")); chunk[8 + 0] = (unsigned char)((info->mdcv_red_x) >> 8u); chunk[8 + 1] = (unsigned char)(info->mdcv_red_x); chunk[8 + 2] = (unsigned char)((info->mdcv_red_y) >> 8u); chunk[8 + 3] = (unsigned char)(info->mdcv_red_y); chunk[8 + 4] = (unsigned char)((info->mdcv_green_x) >> 8u); chunk[8 + 5] = (unsigned char)(info->mdcv_green_x); chunk[8 + 6] = (unsigned char)((info->mdcv_green_y) >> 8u); chunk[8 + 7] = (unsigned char)(info->mdcv_green_y); chunk[8 + 8] = (unsigned char)((info->mdcv_blue_x) >> 8u); chunk[8 + 9] = (unsigned char)(info->mdcv_blue_x); chunk[8 + 10] = (unsigned char)((info->mdcv_blue_y) >> 8u); chunk[8 + 11] = (unsigned char)(info->mdcv_blue_y); chunk[8 + 12] = (unsigned char)((info->mdcv_white_x) >> 8u); chunk[8 + 13] = (unsigned char)(info->mdcv_white_x); chunk[8 + 14] = (unsigned char)((info->mdcv_white_y) >> 8u); chunk[8 + 15] = (unsigned char)(info->mdcv_white_y); lodepng_set32bitInt(chunk + 8 + 16, info->mdcv_max_luminance); lodepng_set32bitInt(chunk + 8 + 20, info->mdcv_min_luminance); lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_cLLI(ucvector* out, const LodePNGInfo* info) { unsigned char* chunk; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 8, "cLLI")); lodepng_set32bitInt(chunk + 8 + 0, info->clli_max_cll); lodepng_set32bitInt(chunk + 8 + 4, info->clli_max_fall); lodepng_chunk_generate_crc(chunk); return 0; } static unsigned addChunk_eXIf(ucvector* out, const LodePNGInfo* info) { return lodepng_chunk_createv(out, info->exif_size, "eXIf", info->exif); } static unsigned addChunk_sBIT(ucvector* out, const LodePNGInfo* info) { unsigned bitdepth = (info->color.colortype == LCT_PALETTE) ? 8 : info->color.bitdepth; unsigned char* chunk = 0; if(info->color.colortype == LCT_GREY) { if(info->sbit_r == 0 || info->sbit_r > bitdepth) return 115; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "sBIT")); chunk[8] = info->sbit_r; } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_PALETTE) { if(info->sbit_r == 0 || info->sbit_g == 0 || info->sbit_b == 0) return 115; if(info->sbit_r > bitdepth || info->sbit_g > bitdepth || info->sbit_b > bitdepth) return 115; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 3, "sBIT")); chunk[8] = info->sbit_r; chunk[9] = info->sbit_g; chunk[10] = info->sbit_b; } else if(info->color.colortype == LCT_GREY_ALPHA) { if(info->sbit_r == 0 || info->sbit_a == 0) return 115; if(info->sbit_r > bitdepth || info->sbit_a > bitdepth) return 115; CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "sBIT")); chunk[8] = info->sbit_r; chunk[9] = info->sbit_a; } else if(info->color.colortype == LCT_RGBA) { if(info->sbit_r == 0 || info->sbit_g == 0 || info->sbit_b == 0 || info->sbit_a == 0 || info->sbit_r > bitdepth || info->sbit_g > bitdepth || info->sbit_b > bitdepth || info->sbit_a > bitdepth) { return 115; } CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "sBIT")); chunk[8] = info->sbit_r; chunk[9] = info->sbit_g; chunk[10] = info->sbit_b; chunk[11] = info->sbit_a; } if(chunk) lodepng_chunk_generate_crc(chunk); return 0; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, size_t length, size_t bytewidth, unsigned char filterType) { size_t i; switch(filterType) { case 0: /*None*/ for(i = 0; i != length; ++i) out[i] = scanline[i]; break; case 1: /*Sub*/ for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; break; case 2: /*Up*/ if(prevline) { for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; } else { for(i = 0; i != length; ++i) out[i] = scanline[i]; } break; case 3: /*Average*/ if(prevline) { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); } else { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); } break; case 4: /*Paeth*/ if(prevline) { /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); for(i = bytewidth; i < length; ++i) { out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); } } else { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); } break; default: return; /*invalid filter type given*/ } } /* integer binary logarithm, max return value is 31 */ static size_t ilog2(size_t i) { size_t result = 0; if(i >= 65536) { result += 16; i >>= 16; } if(i >= 256) { result += 8; i >>= 8; } if(i >= 16) { result += 4; i >>= 4; } if(i >= 4) { result += 2; i >>= 2; } if(i >= 2) { result += 1; /*i >>= 1;*/ } return result; } /* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ static size_t ilog2i(size_t i) { size_t l; if(i == 0) return 0; l = ilog2(i); /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) linearly approximates the missing fractional part multiplied by i */ return i * l + ((i - (((size_t)1) << l)) << 1u); } static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { /* For PNG filter method 0 out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are the scanlines with 1 extra byte per scanline */ unsigned bpp = lodepng_get_bpp(color); /*the width of a scanline in bytes, not including the filter type*/ size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ size_t bytewidth = (bpp + 7u) / 8u; const unsigned char* prevline = 0; unsigned x, y; unsigned error = 0; LodePNGFilterStrategy strategy = settings->filter_strategy; if(settings->filter_palette_zero && (color->colortype == LCT_PALETTE || color->bitdepth < 8)) { /*if the filter_palette_zero setting is enabled, override the filter strategy with zero for all scanlines for palette and less-than-8-bitdepth images*/ strategy = LFS_ZERO; } if(bpp == 0) return 31; /*error: invalid color type*/ if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { unsigned char type = (unsigned char)strategy; for(y = 0; y != h; ++y) { size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ size_t inindex = linebytes * y; out[outindex] = type; /*filter type byte*/ filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); prevline = &in[inindex]; } } else if(strategy == LFS_MINSUM) { /*adaptive filtering: independently for each row, try all five filter types and select the one that produces the smallest sum of absolute values per row.*/ unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ size_t smallest = 0; unsigned char type, bestType = 0; for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) error = 83; /*alloc fail*/ } if(!error) { for(y = 0; y != h; ++y) { /*try the 5 filter types*/ for(type = 0; type != 5; ++type) { size_t sum = 0; filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); /*calculate the sum of the result*/ if(type == 0) { for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); } else { for(x = 0; x != linebytes; ++x) { /*For differences, each byte should be treated as signed, values above 127 are negative (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. This means filtertype 0 is almost never chosen, but that is justified.*/ unsigned char s = attempt[type][x]; sum += s < 128 ? s : (255U - s); } } /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ if(type == 0 || sum < smallest) { bestType = type; smallest = sum; } } prevline = &in[y * linebytes]; /*now fill the out values*/ out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; } } for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); } else if(strategy == LFS_ENTROPY) { unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ size_t bestSum = 0; unsigned type, bestType = 0; unsigned count[256]; for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) error = 83; /*alloc fail*/ } if(!error) { for(y = 0; y != h; ++y) { /*try the 5 filter types*/ for(type = 0; type != 5; ++type) { size_t sum = 0; filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); lodepng_memset(count, 0, 256 * sizeof(*count)); for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; ++count[type]; /*the filter type itself is part of the scanline*/ for(x = 0; x != 256; ++x) { sum += ilog2i(count[x]); } /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ if(type == 0 || sum > bestSum) { bestType = type; bestSum = sum; } } prevline = &in[y * linebytes]; /*now fill the out values*/ out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; } } for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); } else if(strategy == LFS_PREDEFINED) { for(y = 0; y != h; ++y) { size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ size_t inindex = linebytes * y; unsigned char type = settings->predefined_filters[y]; out[outindex] = type; /*filter type byte*/ filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); prevline = &in[inindex]; } } else if(strategy == LFS_BRUTE_FORCE) { /*brute force filter chooser. deflate the scanline after every filter attempt to see which one deflates best. This is very slow and gives only slightly smaller, sometimes even larger, result*/ size_t size[5]; unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ size_t smallest = 0; unsigned type = 0, bestType = 0; unsigned char* dummy; LodePNGCompressSettings zlibsettings; lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, to simulate the true case where the tree is the same for the whole image. Sometimes it gives better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare cases better compression. It does make this a bit less slow, so it's worth doing this.*/ zlibsettings.btype = 1; /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG images only, so disable it*/ zlibsettings.custom_zlib = 0; zlibsettings.custom_deflate = 0; for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) error = 83; /*alloc fail*/ } if(!error) { for(y = 0; y != h; ++y) /*try the 5 filter types*/ { for(type = 0; type != 5; ++type) { unsigned testsize = (unsigned)linebytes; /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); size[type] = 0; dummy = 0; zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); lodepng_free(dummy); /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ if(type == 0 || size[type] < smallest) { bestType = type; smallest = size[type]; } } prevline = &in[y * linebytes]; out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; } } for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); } else return 88; /* unknown filter strategy */ return error; } static void addPaddingBits(unsigned char* out, const unsigned char* in, size_t olinebits, size_t ilinebits, unsigned h) { /*The opposite of the removePaddingBits function olinebits must be >= ilinebits*/ unsigned y; size_t diff = olinebits - ilinebits; size_t obp = 0, ibp = 0; /*bit pointers*/ for(y = 0; y != h; ++y) { size_t x; for(x = 0; x < ilinebits; ++x) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } /*obp += diff; --> no, fill in some value in the padding bits too, to avoid "Use of uninitialised value of size ###" warning from valgrind*/ for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); } } /* in: non-interlaced image with size w*h out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with no padding bits between scanlines, but between reduced images so that each reduced image starts at a byte. bpp: bits per pixel there are no padding bits, not between scanlines, not between reduced images in has the following size in bits: w * h * bpp. out is possibly bigger due to padding bits between reduced images NOTE: comments about padding bits are only relevant if bpp < 8 */ static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); if(bpp >= 8) { for(i = 0; i != 7; ++i) { unsigned x, y, b; size_t bytewidth = bpp / 8u; for(y = 0; y < passh[i]; ++y) for(x = 0; x < passw[i]; ++x) { size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; for(b = 0; b < bytewidth; ++b) { out[pixeloutstart + b] = in[pixelinstart + b]; } } } } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { for(i = 0; i != 7; ++i) { unsigned x, y, b; unsigned ilinebits = bpp * passw[i]; unsigned olinebits = bpp * w; size_t obp, ibp; /*bit pointers (for out and in buffer)*/ for(y = 0; y < passh[i]; ++y) for(x = 0; x < passw[i]; ++x) { ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); for(b = 0; b < bpp; ++b) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } } } } } /*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. return value is error**/ static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, unsigned w, unsigned h, const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { /* This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter */ size_t bpp = lodepng_get_bpp(&info_png->color); unsigned error = 0; if(info_png->interlace_method == 0) { /*image size plus an extra byte per scanline + possible padding bits*/ *outsize = (size_t)h + ((size_t)h * (((size_t)w * bpp + 7u) / 8u)); *out = (unsigned char*)lodepng_malloc(*outsize); if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ if(!error) { /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ if(bpp < 8 && (size_t)w * bpp != (((size_t)w * bpp + 7u) / 8u) * 8u) { unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); if(!padded) error = 83; /*alloc fail*/ if(!error) { addPaddingBits(padded, in, (((size_t)w * bpp + 7u) / 8u) * 8u, (size_t)w * bpp, h); error = filter(*out, padded, w, h, &info_png->color, settings); } lodepng_free(padded); } else { /*we can immediately filter into the out buffer, no other steps needed*/ error = filter(*out, in, w, h, &info_png->color, settings); } } } else /*interlace_method is 1 (Adam7)*/ { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned char* adam7; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, (unsigned)bpp); *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ *out = (unsigned char*)lodepng_malloc(*outsize); if(!(*out)) error = 83; /*alloc fail*/ adam7 = (unsigned char*)lodepng_malloc(passstart[7]); if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ if(!error) { unsigned i; Adam7_interlace(adam7, in, w, h, (unsigned)bpp); for(i = 0; i != 7; ++i) { if(bpp < 8) { unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); if(!padded) ERROR_BREAK(83); /*alloc fail*/ addPaddingBits(padded, &adam7[passstart[i]], (((size_t)passw[i] * bpp + 7u) / 8u) * 8u, (size_t)passw[i] * bpp, passh[i]); error = filter(&(*out)[filter_passstart[i]], padded, passw[i], passh[i], &info_png->color, settings); lodepng_free(padded); } else { error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], passw[i], passh[i], &info_png->color, settings); } if(error) break; } } lodepng_free(adam7); } return error; } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { unsigned char* inchunk = data; while((size_t)(inchunk - data) < datasize) { CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); out->allocsize = out->size; /*fix the allocsize again*/ inchunk = lodepng_chunk_next(inchunk, data + datasize); } return 0; } static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { /* It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 are "RGB ". We do not perform any full parsing of the ICC profile here, other than check those 4 bytes to grayscale profile. Other than that, validity of the profile is not checked. This is needed only because the PNG specification requires using a non-gray color model if there is an ICC profile with "RGB " (sadly limiting compression opportunities if the input data is grayscale RGB data), and requires using a gray color model if it is "GRAY". */ if(size < 20) return 0; return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; } static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { /* See comment in isGrayICCProfile*/ if(size < 20) return 0; return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ unsigned lodepng_encode(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, LodePNGState* state) { unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ size_t datasize = 0; ucvector outv = ucvector_init(NULL, 0); LodePNGInfo info; const LodePNGInfo* info_png = &state->info_png; LodePNGColorMode auto_color; unsigned error = 0; lodepng_info_init(&info); lodepng_color_mode_init(&auto_color); /*provide some proper output values if error will happen*/ *out = 0; *outsize = 0; /*check input values validity*/ if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { /*this error is returned even if auto_convert is enabled and thus encoder could generate the palette by itself: while allowing this could be possible in theory, it may complicate the code or edge cases, and always requiring to give a palette when setting this color type is a simpler contract*/ error = 68; /*invalid palette size, it is only allowed to be 1-256*/ goto cleanup; } if(state->encoder.zlibsettings.btype > 2) { error = 61; /*error: invalid btype*/ goto cleanup; } if(info_png->interlace_method > 1) { error = 71; /*error: invalid interlace mode*/ goto cleanup; } error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); if(error) goto cleanup; /*error: invalid color type given*/ error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); if(error) goto cleanup; /*error: invalid color type given*/ /* color convert and compute scanline filter types */ CERROR_TRY_RETURN(lodepng_info_copy(&info, &state->info_png)); if(state->encoder.auto_convert) { LodePNGColorStats stats; unsigned allow_convert = 1; lodepng_color_stats_init(&stats); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(info_png->iccp_defined && isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { /*the PNG specification does not allow to use palette with a GRAY ICC profile, even if the palette has only gray colors, so disallow it.*/ stats.allow_palette = 0; } if(info_png->iccp_defined && isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ stats.allow_greyscale = 0; } #endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); if(error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(info_png->background_defined) { /*the background chunk's color must be taken into account as well*/ unsigned r = 0, g = 0, b = 0; LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); error = lodepng_color_stats_add(&stats, r, g, b, 65535); if(error) goto cleanup; } #endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ error = auto_choose_color(&auto_color, &state->info_raw, &stats); if(error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(info_png->sbit_defined) { /*if sbit is defined, due to strict requirements of which sbit values can be present for which color modes, auto_convert can't be done in many cases. However, do support a few cases here. TODO: more conversions may be possible, and it may also be possible to get a more appropriate color type out of auto_choose_color if knowledge about sbit is used beforehand */ unsigned sbit_max = LODEPNG_MAX(LODEPNG_MAX(LODEPNG_MAX(info_png->sbit_r, info_png->sbit_g), info_png->sbit_b), info_png->sbit_a); unsigned equal = (!info_png->sbit_g || info_png->sbit_g == info_png->sbit_r) && (!info_png->sbit_b || info_png->sbit_b == info_png->sbit_r) && (!info_png->sbit_a || info_png->sbit_a == info_png->sbit_r); allow_convert = 0; if(info.color.colortype == LCT_PALETTE && auto_color.colortype == LCT_PALETTE) { /* input and output are palette, and in this case it may happen that palette data is expected to be copied from info_raw into the info_png */ allow_convert = 1; } /*going from 8-bit RGB to palette (or 16-bit as long as sbit_max <= 8) is possible since both are 8-bit RGB for sBIT's purposes*/ if(info.color.colortype == LCT_RGB && auto_color.colortype == LCT_PALETTE && sbit_max <= 8) { allow_convert = 1; } /*going from 8-bit RGBA to palette is also ok but only if sbit_a is exactly 8*/ if(info.color.colortype == LCT_RGBA && auto_color.colortype == LCT_PALETTE && info_png->sbit_a == 8 && sbit_max <= 8) { allow_convert = 1; } /*going from 16-bit RGB(A) to 8-bit RGB(A) is ok if all sbit values are <= 8*/ if((info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA) && info.color.bitdepth == 16 && auto_color.colortype == info.color.colortype && auto_color.bitdepth == 8 && sbit_max <= 8) { allow_convert = 1; } /*going to less channels is ok if all bit values are equal (all possible values in sbit, as well as the chosen bitdepth of the result). Due to how auto_convert works, we already know that auto_color.colortype has less than or equal amount of channels than info.colortype. Palette is not used here. This conversion is not allowed if info_png->sbit_r < auto_color.bitdepth, because specifically for alpha, non-presence of an sbit value heavily implies that alpha's bit depth is equal to the PNG bit depth (rather than the bit depths set in the r, g and b sbit values, by how the PNG specification describes handling tRNS chunk case with sBIT), so be conservative here about ignoring user input.*/ if(info.color.colortype != LCT_PALETTE && auto_color.colortype != LCT_PALETTE && equal && info_png->sbit_r == auto_color.bitdepth) { allow_convert = 1; } } #endif if(state->encoder.force_palette) { if(info.color.colortype != LCT_GREY && info.color.colortype != LCT_GREY_ALPHA && (auto_color.colortype == LCT_GREY || auto_color.colortype == LCT_GREY_ALPHA)) { /*user specifically forced a PLTE palette, so cannot convert to grayscale types because the PNG specification only allows writing a suggested palette in PLTE for truecolor types*/ allow_convert = 0; } } if(allow_convert) { lodepng_color_mode_copy(&info.color, &auto_color); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*also convert the background chunk*/ if(info_png->background_defined) { if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { error = 104; goto cleanup; } } #endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ } } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS if(info_png->iccp_defined) { unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; if(!gray_icc && !rgb_icc) { error = 100; /* Disallowed profile color type for PNG */ goto cleanup; } if(gray_icc != gray_png) { /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, or in case of auto_convert, it wasn't possible to find appropriate model*/ error = state->encoder.auto_convert ? 102 : 101; goto cleanup; } } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { unsigned char* converted; size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; converted = (unsigned char*)lodepng_malloc(size); if(!converted && size) error = 83; /*alloc fail*/ if(!error) { error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); } if(!error) { error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); } lodepng_free(converted); if(error) goto cleanup; } else { error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); if(error) goto cleanup; } /* output all PNG chunks */ { #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS size_t i; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*write signature and chunks*/ error = writeSignature(&outv); if(error) goto cleanup; /*IHDR*/ error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); if(error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*unknown chunks between IHDR and PLTE*/ if(info.unknown_chunks_data[0]) { error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); if(error) goto cleanup; } /*color profile chunks must come before PLTE */ if(info.cicp_defined) { error = addChunk_cICP(&outv, &info); if(error) goto cleanup; } if(info.mdcv_defined) { error = addChunk_mDCV(&outv, &info); if(error) goto cleanup; } if(info.clli_defined) { error = addChunk_cLLI(&outv, &info); if(error) goto cleanup; } if(info.iccp_defined) { error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); if(error) goto cleanup; } if(info.srgb_defined) { error = addChunk_sRGB(&outv, &info); if(error) goto cleanup; } if(info.gama_defined) { error = addChunk_gAMA(&outv, &info); if(error) goto cleanup; } if(info.chrm_defined) { error = addChunk_cHRM(&outv, &info); if(error) goto cleanup; } if(info_png->sbit_defined) { error = addChunk_sBIT(&outv, &info); if(error) goto cleanup; } if(info.exif_defined) { error = addChunk_eXIf(&outv, &info); if(error) goto cleanup; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*PLTE*/ if(info.color.colortype == LCT_PALETTE) { error = addChunk_PLTE(&outv, &info.color); if(error) goto cleanup; } if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ error = addChunk_PLTE(&outv, &info.color); if(error) goto cleanup; } /*tRNS (this will only add if when necessary) */ error = addChunk_tRNS(&outv, &info.color); if(error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*bKGD (must come between PLTE and the IDAt chunks*/ if(info.background_defined) { error = addChunk_bKGD(&outv, &info); if(error) goto cleanup; } /*pHYs (must come before the IDAT chunks)*/ if(info.phys_defined) { error = addChunk_pHYs(&outv, &info); if(error) goto cleanup; } /*unknown chunks between PLTE and IDAT*/ if(info.unknown_chunks_data[1]) { error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); if(error) goto cleanup; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*IDAT (multiple IDAT chunks must be consecutive)*/ error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); if(error) goto cleanup; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*tIME*/ if(info.time_defined) { error = addChunk_tIME(&outv, &info.time); if(error) goto cleanup; } /*tEXt and/or zTXt*/ for(i = 0; i != info.text_num; ++i) { if(lodepng_strlen(info.text_keys[i]) > 79) { error = 66; /*text chunk too large*/ goto cleanup; } if(lodepng_strlen(info.text_keys[i]) < 1) { error = 67; /*text chunk too small*/ goto cleanup; } if(state->encoder.text_compression) { error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); if(error) goto cleanup; } else { error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); if(error) goto cleanup; } } /*LodePNG version id in text chunk*/ if(state->encoder.add_id) { unsigned already_added_id_text = 0; for(i = 0; i != info.text_num; ++i) { const char* k = info.text_keys[i]; /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { already_added_id_text = 1; break; } } if(already_added_id_text == 0) { error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ if(error) goto cleanup; } } /*iTXt*/ for(i = 0; i != info.itext_num; ++i) { if(lodepng_strlen(info.itext_keys[i]) > 79) { error = 66; /*text chunk too large*/ goto cleanup; } if(lodepng_strlen(info.itext_keys[i]) < 1) { error = 67; /*text chunk too small*/ goto cleanup; } error = addChunk_iTXt( &outv, state->encoder.text_compression, info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], &state->encoder.zlibsettings); if(error) goto cleanup; } /*unknown chunks between IDAT and IEND*/ if(info.unknown_chunks_data[2]) { error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); if(error) goto cleanup; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ error = addChunk_IEND(&outv); if(error) goto cleanup; } cleanup: lodepng_info_cleanup(&info); lodepng_free(data); lodepng_color_mode_cleanup(&auto_color); /*instead of cleaning the vector up, give it to the output*/ *out = outv.data; *outsize = outv.size; state->error = error; /*TODO: remove this and make input state const*/ return error; } unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { unsigned error; LodePNGState state; lodepng_state_init(&state); state.info_raw.colortype = colortype; state.info_raw.bitdepth = bitdepth; state.info_png.color.colortype = colortype; state.info_png.color.bitdepth = bitdepth; error = lodepng_encode(out, outsize, image, w, h, &state); lodepng_state_cleanup(&state); return error; } unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); } unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); } #ifdef LODEPNG_COMPILE_DISK unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); if(!error) error = lodepng_save_file(buffer, buffersize, filename); lodepng_free(buffer); return error; } unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); } unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); } #endif /*LODEPNG_COMPILE_DISK*/ void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { lodepng_compress_settings_init(&settings->zlibsettings); settings->filter_palette_zero = 1; settings->filter_strategy = LFS_MINSUM; settings->auto_convert = 1; settings->force_palette = 0; settings->predefined_filters = 0; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS settings->add_id = 0; settings->text_compression = 1; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } #endif /*LODEPNG_COMPILE_ENCODER*/ #endif /*LODEPNG_COMPILE_PNG*/ #ifdef LODEPNG_COMPILE_ERROR_TEXT /* This returns the description of a numerical error code in English. This is also the documentation of all the error codes. */ const char* lodepng_error_text(unsigned code) { switch(code) { case 0: return "no error, everything went ok"; case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ case 13: return "problem while processing dynamic deflate block"; case 14: return "problem while processing dynamic deflate block"; case 15: return "problem while processing dynamic deflate block"; /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ case 16: return "invalid code while processing dynamic deflate block"; case 17: return "end of out buffer memory reached while inflating"; case 18: return "invalid distance code while inflating"; case 19: return "end of out buffer memory reached while inflating"; case 20: return "invalid deflate block BTYPE encountered while decoding"; case 21: return "NLEN is not ones complement of LEN in a deflate block"; /*end of out buffer memory reached while inflating: This can happen if the inflated deflate data is longer than the amount of bytes required to fill up all the pixels of the image, given the color depth and image dimensions. Something that doesn't happen in a normal, well encoded, PNG image.*/ case 22: return "end of out buffer memory reached while inflating"; case 23: return "end of in buffer memory reached while inflating"; case 24: return "invalid FCHECK in zlib header"; case 25: return "invalid compression method in zlib header"; case 26: return "FDICT encountered in zlib header while it's not used for PNG"; case 27: return "PNG file is smaller than a PNG header"; /*Checks the magic file header, the first 8 bytes of the PNG file*/ case 28: return "incorrect PNG signature, it's no PNG or corrupted"; case 29: return "first chunk is not the header chunk"; case 30: return "chunk length too large, chunk broken off at end of file"; case 31: return "illegal PNG color type or bpp"; case 32: return "illegal PNG compression method"; case 33: return "illegal PNG filter method"; case 34: return "illegal PNG interlace method"; case 35: return "chunk length of a chunk is too large or the chunk too small"; case 36: return "illegal PNG filter type encountered"; case 37: return "illegal bit depth for this color type given"; case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ case 39: return "tRNS chunk before PLTE or has more entries than palette size"; case 40: return "tRNS chunk has wrong size for grayscale image"; case 41: return "tRNS chunk has wrong size for RGB image"; case 42: return "tRNS chunk appeared while it was not allowed for this color type"; case 43: return "bKGD chunk has wrong size for palette image"; case 44: return "bKGD chunk has wrong size for grayscale image"; case 45: return "bKGD chunk has wrong size for RGB image"; case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; case 49: return "jumped past memory while generating dynamic huffman tree"; case 50: return "jumped past memory while generating dynamic huffman tree"; case 51: return "jumped past memory while inflating huffman block"; case 52: return "jumped past memory while inflating"; case 53: return "size of zlib data too small"; case 54: return "repeat symbol in tree while there was no value symbol yet"; /*jumped past tree while generating huffman tree, this could be when the tree will have more leaves than symbols after generating it out of the given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ case 55: return "jumped past tree while generating huffman tree"; case 56: return "given output image colortype or bitdepth not supported for color conversion"; case 57: return "invalid CRC encountered (checking CRC can be disabled)"; case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; case 59: return "requested color conversion not supported"; case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ case 62: return "conversion from color to grayscale not supported"; /*(2^31-1)*/ case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; case 72: return "while decoding, invalid compression method encountered in zTXt, iTXt or iCCP chunk (it must be 0)"; case 73: return "invalid tIME chunk size"; case 74: return "invalid pHYs chunk size"; /*length could be wrong, or data chopped off*/ case 75: return "no null termination char found while decoding text chunk"; case 76: return "iTXt chunk too short to contain required bytes"; case 77: return "integer overflow in buffer size"; case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ case 79: return "failed to open file for writing"; case 80: return "tried creating a tree of 0 symbols"; case 81: return "lazy matching at pos 0 is impossible"; case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; case 83: return "memory allocation failed"; case 84: return "given image too small to contain all pixels to be encoded"; case 86: return "impossible offset in lz77 encoding (internal bug)"; case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; case 89: return "text chunk keyword too short or long: must have size 1-79"; /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ case 90: return "windowsize must be a power of two"; case 91: return "invalid decompressed idat size"; case 92: return "integer overflow due to too many pixels"; case 93: return "zero width or height is invalid"; case 94: return "header chunk must have a size of 13 bytes"; case 95: return "integer overflow with combined idat chunk size"; case 96: return "invalid gAMA chunk size"; case 97: return "invalid cHRM chunk size"; case 98: return "invalid sRGB chunk size"; case 99: return "invalid sRGB rendering intent"; case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; case 105: return "integer overflow of bitsize"; case 106: return "PNG file must have PLTE chunk if color type is palette"; case 107: return "color convert from palette mode requested without setting the palette data in it"; case 108: return "tried to add more than 256 values to a palette"; /*this limit can be configured in LodePNGDecompressSettings*/ case 109: return "tried to decompress zlib or deflate data larger than desired max_output_size"; case 110: return "custom zlib or inflate decompression failed"; case 111: return "custom zlib or deflate compression failed"; /*max text size limit can be configured in LodePNGDecoderSettings. This error prevents unreasonable memory consumption when decoding due to impossibly large text sizes.*/ case 112: return "compressed text unreasonably large"; /*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents unreasonable memory consumption when decoding due to impossibly large ICC profile*/ case 113: return "ICC profile unreasonably large"; case 114: return "sBIT chunk has wrong size for the color type of the image"; case 115: return "sBIT value out of range"; case 116: return "cICP value out of range"; case 117: return "invalid cICP chunk size"; case 118: return "mDCV value out of range"; case 119: return "invalid mDCV chunk size"; case 120: return "invalid cLLI chunk size"; case 121: return "invalid chunk type name: may only contain [a-zA-Z]"; case 122: return "invalid chunk type name: third character must be uppercase"; case 123: return "invalid ICC profile size"; } return "unknown error code"; } #endif /*LODEPNG_COMPILE_ERROR_TEXT*/ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ /* // C++ Wrapper // */ /* ////////////////////////////////////////////////////////////////////////// */ /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_CPP namespace lodepng { #ifdef LODEPNG_COMPILE_DISK /* Resizes the vector to the file size and reads the file into it. Returns error code.*/ static unsigned load_file_(std::vector& buffer, FILE* file) { long size = lodepng_filesize(file); if(size < 0) return 78; buffer.resize((size_t)size); if(size == 0) return 0; /*ok*/ if(fread(&buffer[0], 1, buffer.size(), file) != buffer.size()) return 78; return 0; /*ok*/ } unsigned load_file(std::vector& buffer, const std::string& filename) { unsigned error; FILE* file = fopen(filename.c_str(), "rb"); if(!file) return 78; error = load_file_(buffer, file); fclose(file); return error; } /*write given buffer to the file, overwriting the file, it doesn't append to it.*/ unsigned save_file(const std::vector& buffer, const std::string& filename) { return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); } #endif /* LODEPNG_COMPILE_DISK */ #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_DECODER unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings& settings) { unsigned char* buffer = 0; size_t buffersize = 0; unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); if(buffer) { out.insert(out.end(), buffer, &buffer[buffersize]); lodepng_free(buffer); } return error; } unsigned decompress(std::vector& out, const std::vector& in, const LodePNGDecompressSettings& settings) { return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); } #endif /* LODEPNG_COMPILE_DECODER */ #ifdef LODEPNG_COMPILE_ENCODER unsigned compress(std::vector& out, const unsigned char* in, size_t insize, const LodePNGCompressSettings& settings) { unsigned char* buffer = 0; size_t buffersize = 0; unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); if(buffer) { out.insert(out.end(), buffer, &buffer[buffersize]); lodepng_free(buffer); } return error; } unsigned compress(std::vector& out, const std::vector& in, const LodePNGCompressSettings& settings) { return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); } #endif /* LODEPNG_COMPILE_ENCODER */ #endif /* LODEPNG_COMPILE_ZLIB */ #ifdef LODEPNG_COMPILE_PNG State::State() { lodepng_state_init(this); } State::State(const State& other) { lodepng_state_init(this); lodepng_state_copy(this, &other); } State::~State() { lodepng_state_cleanup(this); } State& State::operator=(const State& other) { lodepng_state_copy(this, &other); return *this; } #ifdef LODEPNG_COMPILE_DECODER unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, size_t insize, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer = 0; unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); if(buffer && !error) { State state; state.info_raw.colortype = colortype; state.info_raw.bitdepth = bitdepth; size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); out.insert(out.end(), buffer, &buffer[buffersize]); } lodepng_free(buffer); return error; } unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); } unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, const unsigned char* in, size_t insize) { unsigned char* buffer = NULL; unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); if(buffer && !error) { size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); out.insert(out.end(), buffer, &buffer[buffersize]); } lodepng_free(buffer); return error; } unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, const std::vector& in) { return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); } #ifdef LODEPNG_COMPILE_DISK unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, LodePNGColorType colortype, unsigned bitdepth) { std::vector buffer; /* safe output values in case error happens */ w = h = 0; unsigned error = load_file(buffer, filename); if(error) return error; return decode(out, w, h, buffer, colortype, bitdepth); } #endif /* LODEPNG_COMPILE_DECODER */ #endif /* LODEPNG_COMPILE_DISK */ #ifdef LODEPNG_COMPILE_ENCODER unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); if(buffer) { out.insert(out.end(), buffer, &buffer[buffersize]); lodepng_free(buffer); } return error; } unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); } unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, State& state) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); if(buffer) { out.insert(out.end(), buffer, &buffer[buffersize]); lodepng_free(buffer); } return error; } unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, State& state) { if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; return encode(out, in.empty() ? 0 : &in[0], w, h, state); } #ifdef LODEPNG_COMPILE_DISK unsigned encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { std::vector buffer; unsigned error = encode(buffer, in, w, h, colortype, bitdepth); if(!error) error = save_file(buffer, filename); return error; } unsigned encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); } #endif /* LODEPNG_COMPILE_DISK */ #endif /* LODEPNG_COMPILE_ENCODER */ #endif /* LODEPNG_COMPILE_PNG */ } /* namespace lodepng */ #endif /*LODEPNG_COMPILE_CPP*/ jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/lodepng.h000066400000000000000000003244531523150047600225440ustar00rootroot00000000000000/* LodePNG version 20260119 Copyright (c) 2005-2026 Lode Vandevenne 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. */ #ifndef LODEPNG_H #define LODEPNG_H #include /*for size_t*/ extern const char* LODEPNG_VERSION_STRING; /* The following #defines are used to create code sections. They can be disabled to disable code sections, which can give faster compile time and smaller binary. The "NO_COMPILE" defines are designed to be used to pass as defines to the compiler command to disable them without modifying this header, e.g. -DLODEPNG_NO_COMPILE_ZLIB for gcc or clang. */ /*deflate & zlib. If disabled, you must specify alternative zlib functions in the custom_zlib field of the compress and decompress settings*/ #ifndef LODEPNG_NO_COMPILE_ZLIB /*pass -DLODEPNG_NO_COMPILE_ZLIB to the compiler to disable this, or comment out LODEPNG_COMPILE_ZLIB below*/ #define LODEPNG_COMPILE_ZLIB #endif /*png encoder and png decoder*/ #ifndef LODEPNG_NO_COMPILE_PNG /*pass -DLODEPNG_NO_COMPILE_PNG to the compiler to disable this, or comment out LODEPNG_COMPILE_PNG below*/ #define LODEPNG_COMPILE_PNG #endif /*deflate&zlib decoder and png decoder*/ #ifndef LODEPNG_NO_COMPILE_DECODER /*pass -DLODEPNG_NO_COMPILE_DECODER to the compiler to disable this, or comment out LODEPNG_COMPILE_DECODER below*/ #define LODEPNG_COMPILE_DECODER #endif /*deflate&zlib encoder and png encoder*/ #ifndef LODEPNG_NO_COMPILE_ENCODER /*pass -DLODEPNG_NO_COMPILE_ENCODER to the compiler to disable this, or comment out LODEPNG_COMPILE_ENCODER below*/ #define LODEPNG_COMPILE_ENCODER #endif /*the optional built in harddisk file loading and saving functions*/ #ifndef LODEPNG_NO_COMPILE_DISK /*pass -DLODEPNG_NO_COMPILE_DISK to the compiler to disable this, or comment out LODEPNG_COMPILE_DISK below*/ #define LODEPNG_COMPILE_DISK #endif /*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ #ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS /*pass -DLODEPNG_NO_COMPILE_ANCILLARY_CHUNKS to the compiler to disable this, or comment out LODEPNG_COMPILE_ANCILLARY_CHUNKS below*/ #define LODEPNG_COMPILE_ANCILLARY_CHUNKS #endif /*ability to convert error numerical codes to English text string*/ #ifndef LODEPNG_NO_COMPILE_ERROR_TEXT /*pass -DLODEPNG_NO_COMPILE_ERROR_TEXT to the compiler to disable this, or comment out LODEPNG_COMPILE_ERROR_TEXT below*/ #define LODEPNG_COMPILE_ERROR_TEXT #endif /*Compile the default allocators (C's free, malloc and realloc). If you disable this, you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your source files with custom allocators.*/ #ifndef LODEPNG_NO_COMPILE_ALLOCATORS /*pass -DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler to disable the built-in ones, or comment out LODEPNG_COMPILE_ALLOCATORS below*/ #define LODEPNG_COMPILE_ALLOCATORS #endif /*Disable built-in CRC function, in that case a custom implementation of lodepng_crc32 must be defined externally so that it can be linked in. The default built-in CRC code comes with 8KB of lookup tables, so for memory constrained environment you may want it disabled and provide a much smaller implementation externally as said above. You can find such an example implementation in a comment in the lodepng.c(pp) file in the 'else' case of the searchable LODEPNG_COMPILE_CRC section.*/ #ifndef LODEPNG_NO_COMPILE_CRC /*pass -DLODEPNG_NO_COMPILE_CRC to the compiler to disable the built-in one, or comment out LODEPNG_COMPILE_CRC below*/ #define LODEPNG_COMPILE_CRC #endif /*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ #ifdef __cplusplus #ifndef LODEPNG_NO_COMPILE_CPP /*pass -DLODEPNG_NO_COMPILE_CPP to the compiler to disable C++ (not needed if a C-only compiler), or comment out LODEPNG_COMPILE_CPP below*/ #define LODEPNG_COMPILE_CPP #endif #endif #ifdef LODEPNG_COMPILE_CPP #include #include #endif /*LODEPNG_COMPILE_CPP*/ #ifdef LODEPNG_COMPILE_PNG /*The PNG color types (also used for raw image).*/ typedef enum LodePNGColorType { LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ LCT_RGB = 2, /*RGB: 8,16 bit*/ LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid byte value from 0 to 255 that could be present in an invalid PNG file header. Do not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use the valid color type names above, or numeric values like 1 or 7 when checking for particular disallowed color type byte values, or cast to integer to print it.*/ LCT_MAX_OCTET_VALUE = 255 } LodePNGColorType; #ifdef LODEPNG_COMPILE_DECODER /* Converts PNG data in memory to raw pixel data. out: Output parameter. Pointer to buffer that will contain the raw pixel data. After decoding, its size is w * h * (bytes per pixel) bytes larger than initially. Bytes per pixel depends on colortype and bitdepth. Must be freed after usage with free(*out). Note: for 16-bit per channel colors, uses big endian format like PNG does. w: Output parameter. Pointer to width of pixel data. h: Output parameter. Pointer to height of pixel data. in: Memory buffer with the PNG file. insize: size of the in buffer. colortype: the desired color type for the raw output image. See explanation on PNG color types. bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. Return value: LodePNG error code (0 means no error). */ unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize, LodePNGColorType colortype, unsigned bitdepth); /*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize); /*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize); #ifdef LODEPNG_COMPILE_DISK /* Load PNG from disk, from file with given name. Same as the other decode functions, but instead takes a filename as input. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory.*/ unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, LodePNGColorType colortype, unsigned bitdepth); /*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory.*/ unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename); /*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory.*/ unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename); #endif /*LODEPNG_COMPILE_DISK*/ #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* Converts raw pixel data into a PNG image in memory. The colortype and bitdepth of the output PNG image cannot be chosen, they are automatically determined by the colortype, bitdepth and content of the input pixel data. Note: for 16-bit per channel colors, needs big endian format like PNG does. out: Output parameter. Pointer to buffer that will contain the PNG image data. Must be freed after usage with free(*out). outsize: Output parameter. Pointer to the size in bytes of the out buffer. image: The raw pixel data to encode. The size of this buffer should be w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. w: width of the raw pixel data in pixels. h: height of the raw pixel data in pixels. colortype: the color type of the raw input image. See explanation on PNG color types. bitdepth: the bit depth of the raw input image. See explanation on PNG color types. Return value: LodePNG error code (0 means no error). */ unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth); /*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); /*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h); #ifdef LODEPNG_COMPILE_DISK /* Converts raw pixel data into a PNG file on disk. Same as the other encode functions, but instead takes a filename as output. NOTE: This overwrites existing files without warning! NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory.*/ unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth); /*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory.*/ unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h); /*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory.*/ unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h); #endif /*LODEPNG_COMPILE_DISK*/ #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_CPP namespace lodepng { #ifdef LODEPNG_COMPILE_DECODER /*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, size_t insize, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::vector& in, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); #ifdef LODEPNG_COMPILE_DISK /* Converts PNG file from disk to raw pixel data in memory. Same as the other decode functions, but instead takes a filename as input. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory. */ unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); #endif /* LODEPNG_COMPILE_DISK */ #endif /* LODEPNG_COMPILE_DECODER */ #ifdef LODEPNG_COMPILE_ENCODER /*Same as lodepng_encode_memory, but encodes to an std::vector. colortype is that of the raw input data. The output PNG color type will be auto chosen.*/ unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); #ifdef LODEPNG_COMPILE_DISK /* Converts 32-bit RGBA raw pixel data into a PNG file on disk. Same as the other encode functions, but instead takes a filename as output. NOTE: This overwrites existing files without warning! NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory. */ unsigned encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); unsigned encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h, LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); #endif /* LODEPNG_COMPILE_DISK */ #endif /* LODEPNG_COMPILE_ENCODER */ } /* namespace lodepng */ #endif /*LODEPNG_COMPILE_CPP*/ #endif /*LODEPNG_COMPILE_PNG*/ #ifdef LODEPNG_COMPILE_ERROR_TEXT /*Returns an English description of the numerical error code.*/ const char* lodepng_error_text(unsigned code); #endif /*LODEPNG_COMPILE_ERROR_TEXT*/ #ifdef LODEPNG_COMPILE_DECODER /*Settings for zlib decompression*/ typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; struct LodePNGDecompressSettings { /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ /*Maximum decompressed size, beyond this the decoder may (and is encouraged to) stop decoding, return an error, output a data size > max_output_size and all the data up to that point. This is not hard limit nor a guarantee, but can prevent excessive memory usage. This setting is ignored by the PNG decoder, but is used by the deflate/zlib decoder and can be used by custom ones. Set to 0 to impose no limit (the default).*/ size_t max_output_size; /*use custom zlib decoder instead of built in one (default: null). Should return 0 if success, any non-0 if error (numeric value not exposed).*/ unsigned (*custom_zlib)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGDecompressSettings*); /*use custom deflate decoder instead of built in one (default: null) if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate). Should return 0 if success, any non-0 if error (numeric value not exposed).*/ unsigned (*custom_inflate)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGDecompressSettings*); const void* custom_context; /*optional custom settings for custom functions*/ }; extern const LodePNGDecompressSettings lodepng_default_decompress_settings; void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* Settings for zlib compression. Tweaking these settings tweaks the balance between speed and compression ratio. */ typedef struct LodePNGCompressSettings LodePNGCompressSettings; struct LodePNGCompressSettings /*deflate = compress*/ { /*LZ77 related settings*/ unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ /*use custom zlib encoder instead of built in one (default: null)*/ unsigned (*custom_zlib)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGCompressSettings*); /*use custom deflate encoder instead of built in one (default: null) if custom_zlib is used, custom_deflate is ignored since only the built in zlib function will call custom_deflate*/ unsigned (*custom_deflate)(unsigned char**, size_t*, const unsigned char*, size_t, const LodePNGCompressSettings*); const void* custom_context; /*optional custom settings for custom functions*/ }; extern const LodePNGCompressSettings lodepng_default_compress_settings; void lodepng_compress_settings_init(LodePNGCompressSettings* settings); #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_PNG /* Color mode of an image. Contains all information required to decode the pixel bits to RGBA colors. This information is the same as used in the PNG file format, and is used both for PNG and raw image data in LodePNG. */ typedef struct LodePNGColorMode { /*header (IHDR)*/ LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ /* palette (PLTE and tRNS) Dynamically allocated with the colors of the palette, including alpha. This field may not be allocated directly, use lodepng_color_mode_init first, then lodepng_palette_add per color to correctly initialize it (to ensure size of exactly 1024 bytes). The alpha channels must be set as well, set them to 255 for opaque images. When decoding, with the default settings you can ignore this palette, since LodePNG already fills the palette colors in the pixels of the raw RGBA output, but when decoding to the original PNG color mode it is needed to reconstruct the colors. The palette is only supported for color type 3. */ unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ /* transparent color key (tRNS) This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. For grayscale PNGs, r, g and b will all 3 be set to the same. When decoding, by default you can ignore this information, since LodePNG sets pixels with this key to transparent already in the raw RGBA output. The color key is only supported for color types 0 and 2. */ unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ unsigned key_r; /*red/grayscale component of color key*/ unsigned key_g; /*green component of color key*/ unsigned key_b; /*blue component of color key*/ } LodePNGColorMode; /*init, cleanup and copy functions to use with this struct*/ void lodepng_color_mode_init(LodePNGColorMode* info); void lodepng_color_mode_cleanup(LodePNGColorMode* info); /*return value is error code (0 means no error)*/ unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); /* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); void lodepng_palette_clear(LodePNGColorMode* info); /*add 1 color to the palette*/ unsigned lodepng_palette_add(LodePNGColorMode* info, unsigned char r, unsigned char g, unsigned char b, unsigned char a); /*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ unsigned lodepng_get_bpp(const LodePNGColorMode* info); /*get the amount of color channels used, based on colortype in the struct. If a palette is used, it counts as 1 channel.*/ unsigned lodepng_get_channels(const LodePNGColorMode* info); /*is it a grayscale type? (only colortype 0 or 4)*/ unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); /*has it got an alpha channel? (only colortype 2 or 6)*/ unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); /*has it got a palette? (only colortype 3)*/ unsigned lodepng_is_palette_type(const LodePNGColorMode* info); /*only returns true if there is a palette and there is a value in the palette with alpha < 255. Loops through the palette to check this.*/ unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); /* Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). Returns false if the image can only have opaque pixels. In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, or if "key_defined" is true. */ unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); /*Returns the byte size of a raw image buffer with given width, height and color mode*/ size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*The information of a Time chunk in PNG.*/ typedef struct LodePNGTime { unsigned year; /*2 bytes used (0-65535)*/ unsigned month; /*1-12*/ unsigned day; /*1-31*/ unsigned hour; /*0-23*/ unsigned minute; /*0-59*/ unsigned second; /*0-60 (to allow for leap seconds)*/ } LodePNGTime; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*Information about the PNG image, except pixels, width and height.*/ typedef struct LodePNGInfo { /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ unsigned compression_method;/*compression method of the original file. Always 0.*/ unsigned filter_method; /*filter method of the original file*/ unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /* Suggested background color chunk (bKGD) This uses the same color mode and bit depth as the PNG (except no alpha channel), with values truncated to the bit depth in the unsigned integer. For grayscale and palette PNGs, the value is stored in background_r. The values in background_g and background_b are then unused. The decoder will set them equal to background_r, the encoder ignores them in this case. When decoding, you may get these in a different color mode than the one you requested for the raw pixels: the colortype and bitdepth defined by info_png.color, that is the ones defined in the header of the PNG image, are used. When encoding with auto_convert, you must use the color model defined in info_png.color for these values. The encoder normally ignores info_png.color when auto_convert is on, but will use it to interpret these values (and convert copies of them to its chosen color model). When encoding, avoid setting this to an expensive color, such as a non-gray value when the image is gray, or the compression will be worse since it will be forced to write the PNG with a more expensive color mode (when auto_convert is on). The decoder does not use this background color to edit the color of pixels. This is a completely optional metadata feature. */ unsigned background_defined; /*is a suggested background color given?*/ unsigned background_r; /*red/gray/palette component of suggested background color*/ unsigned background_g; /*green component of suggested background color*/ unsigned background_b; /*blue component of suggested background color*/ /* Non-international text chunks (tEXt and zTXt) The char** arrays each contain num strings. The actual messages are in text_strings, while text_keys are keywords that give a short description what the actual text represents, e.g. Title, Author, Description, or anything else. All the string fields below including strings, keys, names and language tags are null terminated. The PNG specification uses null characters for the keys, names and tags, and forbids null characters to appear in the main text which is why we can use null termination everywhere here. A keyword is minimum 1 character and maximum 79 characters long (plus the additional null terminator). It's discouraged to use a single line length longer than 79 characters for texts. Don't allocate these text buffers yourself. Use the init/cleanup functions correctly and use lodepng_add_text and lodepng_clear_text. Standard text chunk keywords and strings are encoded using Latin-1. */ size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ char** text_strings; /*the actual text*/ /* International text chunks (iTXt) Similar to the non-international text chunks, but with additional strings "langtags" and "transkeys", and the following text encodings are used: keys: Latin-1, langtags: ASCII, transkeys and strings: UTF-8. keys must be 1-79 characters (plus the additional null terminator), the other strings are any length. */ size_t itext_num; /*the amount of international texts in this PNG*/ char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ char** itext_strings; /*the actual international text - UTF-8 string*/ /* Optional exif metadata in exif_size bytes. Don't allocate this buffer yourself. Use the init/cleanup functions correctly and use lodepng_set_exif and lodepng_clear_exif. The exif data is in exif-encoded form but without JPEG markers, starting with the 'II' or 'MM' marker that indicates endianness. It's up to an exif handling library to encode/decode its information. */ unsigned exif_defined; /* Whether exif metadata is present, that is, the PNG image has an eXIf chunk */ unsigned char* exif; /* The bytes of the exif metadata, if present */ unsigned exif_size; /* The size of the exif data in bytes */ /*time chunk (tIME)*/ unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ LodePNGTime time; /*phys chunk (pHYs)*/ unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ unsigned phys_x; /*pixels per unit in x direction*/ unsigned phys_y; /*pixels per unit in y direction*/ unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ /* Color profile related chunk types: cICP, iCPP, sRGB, gAMA, cHRM, sBIT LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color profile values. It merely passes on the information. If you wish to use color profiles and convert colors, a separate color management library should be used. There is also a limited library for this in lodepng_util.h. There are 4 types of (sets of) chunks providing color information. If multiple are present, each will be decoded by LodePNG, but only one should be handled by the user, with the following order of priority depending on what the user supports: 1: cICP: Coding-independent code points (CICP) 2: iCCP: ICC profile 3: sRGB: indicates the image is in the sRGB color profile 4: gAMA and cHRM: indicates a gamma and chromaticity value to define the color profile */ /* gAMA chunk: Image gamma Optional, overridden by cICP, iCCP or sRGB if those are present. Together with cHRM, this is a primitive way of specifying the image color profile. */ unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ unsigned gama_gamma; /* Gamma exponent times 100000 */ /* cHRM chunk: Primary chromaticities and white point Optional, overridden by cICP, iCCP or sRGB if those are present. Together with gAMA, this is a primitive way of specifying the image color profile. */ unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ unsigned chrm_white_x; /* White Point x times 100000 */ unsigned chrm_white_y; /* White Point y times 100000 */ unsigned chrm_red_x; /* Red x times 100000 */ unsigned chrm_red_y; /* Red y times 100000 */ unsigned chrm_green_x; /* Green x times 100000 */ unsigned chrm_green_y; /* Green y times 100000 */ unsigned chrm_blue_x; /* Blue x times 100000 */ unsigned chrm_blue_y; /* Blue y times 100000 */ /* sRGB chunk: Indicates the image is in the sRGB color space. Optional. Should not appear at the same time as iCCP. If gAMA is also present gAMA must contain value 45455. If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. */ unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ /* iCCP chunk: Embedded ICC profile. Optional. Should not appear at the same time as sRGB. Contains ICC profile, which can use any version of the ICC.1 specification by the International Color Consortium. See its specification for more details. LodePNG does not parse or use the ICC profile (except its color space header field for "RGB" or "GRAY", see below), a separate library to handle the ICC data format is needed to use it for color management and conversions. For encoding, if iCCP is present, the PNG specification recommends to also add gAMA and cHRM chunks that approximate the ICC profile, for compatibility with applications that don't use the ICC chunk. This is not required, and it's up to the user to compute approximate values and set then in the appropriate gama_ and chrm_ fields, LodePNG does not do this automatically since it does not interpret the ICC profile. For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray PNG color types (types 2, 3 and 6) and a "GRAY" profile for gray PNG color types (types 1 and 4). If you disable auto_convert, you must ensure the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is enabled (the default), and the ICC profile is not a correct match for the pixel data, this will result in an encoder error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel data if the pixels could be encoded as grayscale but the ICC profile is RGB. To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so make sure you compute it carefully to avoid the above problems. */ unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ /* The ICC profile in iccp_profile_size bytes. Don't allocate this buffer yourself. Use the init/cleanup functions correctly and use lodepng_set_icc and lodepng_clear_icc. */ unsigned char* iccp_profile; unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ /* cICP chunk: Coding-independent code points for video signal type identification. Optional. If present, and supported, overrides iCCP, sRGB, gAMA and cHRM. The meaning of the values are as defined in the specification ITU-T-H.273. LodePNG does not use these values, only passes on the metadata. The meaning of the values is they are enum values representing certain color spaces, including HDR color spaces, such as Display P3, PQ and HLG. The video full range flag value should typically be 1 for the use cases of PNG images, but can be 0 for narrow-range images in certain video editing workflows. */ unsigned cicp_defined; /* Whether an cICP chunk is present (0 = not present, 1 = present). */ unsigned cicp_color_primaries; /* Colour primaries value */ unsigned cicp_transfer_function; /* Transfer characteristics value */ unsigned cicp_matrix_coefficients; /* Matrix coefficients value */ unsigned cicp_video_full_range_flag; /* Video full range flag value */ /* mDCV chunk: Mastering Display Color Volume. Optional, typically used in conjunction with certain HDR color spaces that can be represented by the cICP chunk. See the PNG specification, third edition, for more information on this chunk. All the red, green, blue and white x and y values are encoded as 16-bit integers and therefore must be in range 0-65536. The min and max luminance values are 32-bit integers. */ unsigned mdcv_defined; /* Whether an mDCV chunk is present (0 = not present, 1 = present). */ /* Mastering display color primary chromaticities (CIE 1931 x,y of R,G,B) */ unsigned mdcv_red_x; /* Red x times 50000 */ unsigned mdcv_red_y; /* Red y times 50000 */ unsigned mdcv_green_x; /* Green x times 50000 */ unsigned mdcv_green_y; /* Green y times 50000 */ unsigned mdcv_blue_x; /* Blue x times 50000 */ unsigned mdcv_blue_y; /* Blue y times 50000 */ /* Mastering display white point chromaticity (CIE 1931 x,y) */ unsigned mdcv_white_x; /* White Point x times 50000 */ unsigned mdcv_white_y; /* White Point y times 50000 */ /* Mastering display luminance */ unsigned mdcv_max_luminance; /* Max luminance in cd/m^2 times 10000 */ unsigned mdcv_min_luminance; /* Min luminance in cd/m^2 times 10000 */ /* cLLI chunk: Content Light Level Information. Optional, typically used in conjunction with certain HDR color spaces that can be represented by the cICP chunk. See the PNG specification, third edition, for more information on this chunk. The clli_max_cll and clli_max_fall values are 32-bit integers. */ unsigned clli_defined; /* Whether a cLLI chunk is present (0 = not present, 1 = present). */ unsigned clli_max_cll; /* Maximum Content Light Level (MaxCLL) in cd/m^2 times 10000 */ unsigned clli_max_fall; /* Maximum Frame-Average Light Level (MaxFALL) in cd/m^2 times 10000 */ /* sBIT chunk: significant bits. Optional metadata, only set this if needed. If defined, these values give the bit depth of the original data. Since PNG only stores 1, 2, 4, 8 or 16-bit per channel data, the significant bits value can be used to indicate the original encoded data has another sample depth, such as 10 or 12. Encoders using this value, when storing the pixel data, should use the most significant bits of the data to store the original bits, and use a good sample depth scaling method such as "left bit replication" to fill in the least significant bits, rather than fill zeroes. Decoders using this value, if able to work with data that's e.g. 10-bit or 12-bit, should right shift the data to go back to the original bit depth, but decoders are also allowed to ignore sbit and work e.g. with the 8-bit or 16-bit data from the PNG directly, since thanks to the encoder contract, the values encoded in PNG are in valid range for the PNG bit depth. For grayscale images, sbit_g and sbit_b are not used, and for images that don't use color type RGBA or grayscale+alpha, sbit_a is not used (it's not used even for palette images with translucent palette values, or images with color key). The values that are used must be greater than zero and smaller than or equal to the PNG bit depth. The color type from the header in the PNG image defines these used and unused fields: if decoding with a color mode conversion, such as always decoding to RGBA, this metadata still only uses the color type of the original PNG, and may e.g. lack the alpha channel info if the PNG was RGB. When encoding with auto_convert (as well as without), also always the color model defined in info_png.color determines this. NOTE: enabling sbit can hurt compression, because the encoder can then not always use auto_convert to choose a more optimal color mode for the data, because the PNG format has strict requirements for the allowed sbit values in combination with color modes. For example, setting these fields to 10-bit will force the encoder to keep using a 16-bit per channel color mode, even if the pixel data would in fact fit in a more efficient 8-bit mode. */ unsigned sbit_defined; /*is significant bits given? if not, the values below are unused*/ unsigned sbit_r; /*red or gray component of significant bits*/ unsigned sbit_g; /*green component of significant bits*/ unsigned sbit_b; /*blue component of significant bits*/ unsigned sbit_a; /*alpha component of significant bits*/ /* End of color profile related chunks */ /* unknown chunks: chunks not known by LodePNG, passed on byte for byte. There are 3 buffers, one for each position in the PNG where unknown chunks can appear. Each buffer contains all unknown chunks for that position consecutively. The 3 positions are: 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag above in here, since the encoder will blindly follow this and could then encode an invalid PNG file (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), or any non-standard PNG chunk. Do not allocate or traverse this data yourself. Use the chunk traversing functions declared later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. */ unsigned char* unknown_chunks_data[3]; size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } LodePNGInfo; /*init, cleanup and copy functions to use with this struct*/ void lodepng_info_init(LodePNGInfo* info); /*destructs the LodePNGInfo and brings it to invalid state, requiring lodepng_info_init again before reusing it*/ void lodepng_info_cleanup(LodePNGInfo* info); /*return value is error code (0 means no error)*/ unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ /*replaces if exists*/ unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the profile again after you filled it in*/ /*replaces if exists*/ unsigned lodepng_set_exif(LodePNGInfo* info, const unsigned char* exif, unsigned exif_size); void lodepng_clear_exif(LodePNGInfo* info); /*use this to clear the exif metadata again after you filled it in*/ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /* Converts raw buffer from one color type to another color type, based on LodePNGColorMode structs to describe the input and output color type. See the reference manual at the end of this header file to see which color conversions are supported. return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel of the output color type (lodepng_get_bpp). For < 8 bpp images, there should not be padding bits at the end of scanlines. For 16-bit per channel colors, uses big endian format like PNG does. Return value is LodePNG error code */ unsigned lodepng_convert(unsigned char* out, const unsigned char* in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, unsigned w, unsigned h); #ifdef LODEPNG_COMPILE_DECODER /* Settings for the decoder. This contains settings for the PNG and the Zlib decoder, but not the Info settings from the Info structs. */ typedef struct LodePNGDecoderSettings { LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ unsigned ignore_crc; /*ignore CRC checksums*/ unsigned ignore_critical; /*ignore unknown critical chunks*/ unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters in string keys, invalid characters in chunk types names, etc... */ unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ unsigned remember_unknown_chunks; /* maximum size for decompressed text chunks. If a text chunk's text is larger than this, an error is returned, unless reading text chunks is disabled or this limit is set higher or disabled. Set to 0 to allow any size. By default it is a value that prevents unreasonably large strings from hogging memory. */ size_t max_text_size; /* maximum size for compressed ICC chunks. If the ICC profile is larger than this, an error will be returned. Set to 0 to allow any size. By default this is a value that prevents ICC profiles that would be much larger than any legitimate profile could be to hog memory. */ size_t max_icc_size; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } LodePNGDecoderSettings; void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /*strategy to use to choose the PNG filter per scanline. Strategies 0-4 correspond to each of the 5 filter types PNG supports, the next values are adaptive strategies*/ typedef enum LodePNGFilterStrategy { /*every filter at zero*/ LFS_ZERO = 0, /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ LFS_ONE = 1, LFS_TWO = 2, LFS_THREE = 3, LFS_FOUR = 4, /*Use the filter out of the 5 above types that gives minimum sum, by trying each one. This is the adaptive filtering suggested heuristic in the PNG standard chapter 'Filter selection'.*/ LFS_MINSUM, /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending on the image, this is better or worse than minsum.*/ LFS_ENTROPY, /* Brute-force-search PNG filters by compressing each filter for each scanline. Experimental, very slow, and only rarely gives better compression than MINSUM. */ LFS_BRUTE_FORCE, /*use predefined_filters buffer: you specify the filter type for each scanline*/ LFS_PREDEFINED } LodePNGFilterStrategy; /*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), which helps decide which color model to use for encoding. Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ typedef struct LodePNGColorStats { unsigned colored; /*not grayscale*/ unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ unsigned short key_g; unsigned short key_b; unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ size_t numpixels; /*user settings for computing/using the stats*/ unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ } LodePNGColorStats; void lodepng_color_stats_init(LodePNGColorStats* stats); /*Get a LodePNGColorStats of the image. The stats must already have been inited. Returns error code (e.g. alloc fail) or 0 if ok.*/ unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, const unsigned char* image, unsigned w, unsigned h, const LodePNGColorMode* mode_in); /*Settings for the encoder.*/ typedef struct LodePNGEncoderSettings { LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ /*automatically choose output PNG color type. If false, must explicitly choose the output color type in state.info_png.color.colortype, info_png.color.bitdepth and optionally its palette. Default: true*/ unsigned auto_convert; /*If true, follows the suggestion in the PNG standard in chapter 'Filter selection': if the PNG uses a palette or lower than 8 bit depth, set all filters to zero. In other cases this will use the heuristic from the chosen filter_strategy. The PNG standard suggests LFS_MINSUM for those cases.*/ unsigned filter_palette_zero; /*Which filter strategy to use when not using zeroes due to filter_palette_zero. Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ LodePNGFilterStrategy filter_strategy; /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with the same length as the amount of scanlines in the image, and each value must <= 5. You have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ const unsigned char* predefined_filters; /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). If colortype is 3, PLTE is always created. If color type is explicitly set to a grayscale type (1 or 4), this is not done and is ignored. If enabling this, a palette must be present in the info_png. NOTE: enabling this may worsen compression if auto_convert is used to choose optimal color mode, because it cannot use grayscale color modes in this case*/ unsigned force_palette; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*add LodePNG identifier and version as a text chunk, for debugging*/ unsigned add_id; /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ unsigned text_compression; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } LodePNGEncoderSettings; void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); #endif /*LODEPNG_COMPILE_ENCODER*/ #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) /*The settings, state and information for extended encoding and decoding. Using this struct requires using lodepng_state_init to initialize it and using lodepng_state_cleanup to deconstruct it. If using C++, you can use lodepng::State instead which does those things automatically with RAII. While a LodePNGState can be reused once in a chain of lodepng_decode followed by lodepng_encode, it's not recommended to reuse it for multiple encode, decode or inspect calls, and if any such function returns an error code, the LodePNGState should not be reused at all as it can be in an unexpected state.. */ typedef struct LodePNGState { #ifdef LODEPNG_COMPILE_DECODER LodePNGDecoderSettings decoder; /*the decoding settings*/ #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER LodePNGEncoderSettings encoder; /*the encoding settings*/ #endif /*LODEPNG_COMPILE_ENCODER*/ LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ unsigned error; /*deprecated, use the return value of the encode/decode functions to check errors instead*/ } LodePNGState; /*init, cleanup and copy functions to use with this struct*/ void lodepng_state_init(LodePNGState* state); /*destructs the LodePNGState and brings it to invalid state, requiring lodepng_info_init again before reusing it*/ void lodepng_state_cleanup(LodePNGState* state); /*return value is error code (0 means no error)*/ unsigned lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); #endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ #ifdef LODEPNG_COMPILE_DECODER /* Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and getting much more information about the PNG image and color mode. */ unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize); /* Read the PNG header, but not the actual data. This returns only the information that is in the IHDR chunk of the PNG, such as width, height and color type. The information is placed in the info_png field of the LodePNGState. */ unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, const unsigned char* in, size_t insize); #endif /*LODEPNG_COMPILE_DECODER*/ /* Reads one metadata chunk (other than IHDR, which is handled by lodepng_inspect) of the PNG file and outputs what it read in the state. Returns error code on failure. Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const to find the desired chunk type, and if non null use lodepng_inspect_chunk (with chunk_pointer - start_of_file as pos). Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). Requirements: &in[pos] must point to start of a chunk, must use regular lodepng_inspect first since format of most other chunks depends on IHDR, and if there is a PLTE chunk, that one must be inspected before tRNS or bKGD. */ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, const unsigned char* in, size_t insize); #ifdef LODEPNG_COMPILE_ENCODER /*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ unsigned lodepng_encode(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, LodePNGState* state); #endif /*LODEPNG_COMPILE_ENCODER*/ /* The lodepng_chunk functions are normally not needed, except to traverse the unknown chunks stored in the LodePNGInfo struct, or add new ones to it. It also allows traversing the chunks of an encoded PNG file yourself. The chunk pointer always points to the beginning of the chunk itself, that is the first byte of the 4 length bytes. In the PNG file format, chunks have the following format: -4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) -4 bytes chunk type (ASCII a-z,A-Z only, see below) -length bytes of data (may be 0 bytes if length was 0) -4 bytes of CRC, computed on chunk name + data The first chunk starts at the 8th byte of the PNG file, the entire rest of the file exists out of concatenated chunks with the above format. PNG standard chunk ASCII naming conventions: -First byte: uppercase = critical, lowercase = ancillary -Second byte: uppercase = public, lowercase = private -Third byte: must be uppercase -Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy */ /* Gets the length of the data of the chunk. Total chunk length has 12 bytes more. There must be at least 4 bytes to read from. If the result value is too large, it may be corrupt data. */ unsigned lodepng_chunk_length(const unsigned char* chunk); /*puts the 4-byte type in null terminated string*/ void lodepng_chunk_type(char type[5], const unsigned char* chunk); /*check if the type is the given type*/ unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); /*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); /*0: public, 1: private (see PNG standard)*/ unsigned char lodepng_chunk_private(const unsigned char* chunk); /*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); /*get pointer to the data of the chunk, where the input points to the header of the chunk*/ unsigned char* lodepng_chunk_data(unsigned char* chunk); const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); /*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ unsigned lodepng_chunk_check_crc(const unsigned char* chunk); /*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ void lodepng_chunk_generate_crc(unsigned char* chunk); /* Iterate to next chunks, allows iterating through all chunks of the PNG file. Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, or the 8th byte of a PNG file which always has the first chunk), or alternatively may point to the first byte of the PNG file (which is not a chunk but the magic header, the function will then skip over it and return the first real chunk). Will output pointer to the start of the next chunk, or at or beyond end of the file if there is no more chunk after this or possibly if the chunk is corrupt. Start this process at the 8th byte of the PNG file. In a non-corrupt PNG file, the last chunk should have name "IEND". */ unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); /*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); /* Appends chunk to the data in out. The given chunk should already have its chunk header. The out variable and outsize are updated to reflect the new reallocated buffer. Returns error code (0 if it went ok) */ unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); /* Appends new chunk to out. The chunk to append is given by giving its length, type and data separately. The type is a 4-letter string. The out variable and outsize are updated to reflect the new reallocated buffer. Returns error code (0 if it went ok) */ unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, size_t length, const char* type, const unsigned char* data); /*Calculate CRC32 of buffer*/ unsigned lodepng_crc32(const unsigned char* buf, size_t len); #endif /*LODEPNG_COMPILE_PNG*/ #ifdef LODEPNG_COMPILE_ZLIB /* This zlib part can be used independently to zlib compress and decompress a buffer. It cannot be used to create gzip files however, and it only supports the part of zlib that is required for PNG, it does not support dictionaries. */ #ifdef LODEPNG_COMPILE_DECODER /*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ unsigned lodepng_inflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings); /* Decompresses Zlib data. Reallocates the out buffer and appends the data. The data must be according to the zlib specification. Either, *out must be NULL and *outsize must be 0, or, *out must be a valid buffer and *outsize its size in bytes. out must be freed by user after usage. */ unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* Compresses data with Zlib. Reallocates the out buffer and appends the data. Zlib adds a small header and trailer around the deflate data. The data is output in the format of the zlib specification. Either, *out must be NULL and *outsize must be 0, or, *out must be a valid buffer and *outsize its size in bytes. out must be freed by user after usage. */ unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings); /* Find length-limited Huffman code for given frequencies. This function is in the public interface only for tests, it's used internally by lodepng_deflate. */ unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, size_t numcodes, unsigned maxbitlen); /*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ unsigned lodepng_deflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, const LodePNGCompressSettings* settings); #endif /*LODEPNG_COMPILE_ENCODER*/ #endif /*LODEPNG_COMPILE_ZLIB*/ #ifdef LODEPNG_COMPILE_DISK /* Load a file from disk into buffer. The function allocates the out buffer, and after usage you should free it. out: output parameter, contains pointer to loaded buffer. outsize: output parameter, size of the allocated out buffer filename: the path to the file to load return value: error code (0 means ok) NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory. */ unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); /* Save a file from buffer to disk. Warning, if it exists, this function overwrites the file without warning! buffer: the buffer to write buffersize: size of the buffer to write filename: the path to the file to save to return value: error code (0 means ok) NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory */ unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); #endif /*LODEPNG_COMPILE_DISK*/ #ifdef LODEPNG_COMPILE_CPP /* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ namespace lodepng { #ifdef LODEPNG_COMPILE_PNG /* Wrapper around LodePNGState, which automatically calls lodepng_state_init in the constructor and lodepng_state_cleanup in the desctructor.*/ class State : public LodePNGState { public: State(); State(const State& other); ~State(); State& operator=(const State& other); }; #ifdef LODEPNG_COMPILE_DECODER /* Same as other lodepng::decode, but using a State for more settings and information. */ unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, const unsigned char* in, size_t insize); unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, const std::vector& in); #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER /* Same as other lodepng::encode, but using a State for more settings and information. */ unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, State& state); unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, State& state); #endif /*LODEPNG_COMPILE_ENCODER*/ #ifdef LODEPNG_COMPILE_DISK /* Load a file from disk into an std::vector. return value: error code (0 means ok) NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and decode in-memory */ unsigned load_file(std::vector& buffer, const std::string& filename); /* Save the binary data in an std::vector to a file on disk. The file is overwritten without warning. NOTE: Wide-character filenames are not supported, you can use an external method to handle such files and encode in-memory */ unsigned save_file(const std::vector& buffer, const std::string& filename); #endif /* LODEPNG_COMPILE_DISK */ #endif /* LODEPNG_COMPILE_PNG */ #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_DECODER /* Zlib-decompress an unsigned char buffer */ unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); /* Zlib-decompress an std::vector */ unsigned decompress(std::vector& out, const std::vector& in, const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); #endif /* LODEPNG_COMPILE_DECODER */ #ifdef LODEPNG_COMPILE_ENCODER /* Zlib-compress an unsigned char buffer */ unsigned compress(std::vector& out, const unsigned char* in, size_t insize, const LodePNGCompressSettings& settings = lodepng_default_compress_settings); /* Zlib-compress an std::vector */ unsigned compress(std::vector& out, const std::vector& in, const LodePNGCompressSettings& settings = lodepng_default_compress_settings); #endif /* LODEPNG_COMPILE_ENCODER */ #endif /* LODEPNG_COMPILE_ZLIB */ } /* namespace lodepng */ #endif /*LODEPNG_COMPILE_CPP*/ /* TODO: [.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often [.] check compatibility with various compilers - done but needs to be redone for every newer version [X] converting color to 16-bit per channel types [X] support color profile chunk types (but never let them touch RGB values by default) [ ] support all second edition public PNG chunk types (almost done except sPLT and hIST) [X] support non-animation third edition public PNG chunk types: eXIf, cICP, mDCV, cLLI [ ] make sure encoder generates no chunks with size > (2^31)-1 [ ] partial decoding (stream processing) [X] let the "isFullyOpaque" function check color keys and transparent palettes too [X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" [ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) [ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... [ ] error messages with line numbers (and version) [ ] errors in state instead of as return code? [ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk [ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes [ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... [ ] allow user to give data (void*) to custom allocator [X] provide alternatives for C library functions not present on some platforms (memcpy, ...) */ #endif /*LODEPNG_H inclusion guard*/ /* LodePNG Documentation --------------------- 0. table of contents -------------------- 1. about 1.1. supported features 1.2. features not supported 2. C and C++ version 3. security 4. decoding 5. encoding 6. color conversions 6.1. PNG color types 6.2. color conversions 6.3. padding bits 6.4. A note about 16-bits per channel and endianness 7. error values 8. chunks and PNG editing 9. compiler support 10. examples 10.1. decoder C++ example 10.2. decoder C example 11. state settings reference 12. changes 13. contact information 1. about -------- PNG is a file format to store raster images losslessly with good compression, supporting different color types and alpha channel. LodePNG is a PNG codec according to the Portable Network Graphics (PNG) Specification (Second Edition) - W3C Recommendation 10 November 2003. The specifications used are: *) Portable Network Graphics (PNG) Specification (Second Edition): http://www.w3.org/TR/2003/REC-PNG-20031110 *) RFC 1950 ZLIB Compressed Data Format version 3.3: http://www.gzip.org/zlib/rfc-zlib.html *) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: http://www.gzip.org/zlib/rfc-deflate.html The most recent version of LodePNG can currently be found at http://lodev.org/lodepng/ LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds extra functionality. LodePNG exists out of two files: -lodepng.h: the header file for both C and C++ -lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage If you want to start using LodePNG right away without reading this doc, get the examples from the LodePNG website to see how to use it in code, or check the smaller examples in chapter 13 here. LodePNG is simple but only supports the basic requirements. To achieve simplicity, the following design choices were made: There are no dependencies on any external library. There are functions to decode and encode a PNG with a single function call, and extended versions of these functions taking a LodePNGState struct allowing to specify or get more information. By default the colors of the raw image are always RGB or RGBA, no matter what color type the PNG file uses. To read and write files, there are simple functions to convert the files to/from buffers in memory. This all makes LodePNG suitable for loading textures in games, demos and small programs, ... It's less suitable for full fledged image editors, loading PNGs over network (it requires all the image data to be available before decoding can begin), life-critical systems, ... 1.1. supported features ----------------------- The following features are supported by the decoder: *) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, or the same color type as the PNG *) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image *) Adam7 interlace and deinterlace for any color type *) loading the image from harddisk or decoding it from a buffer from other sources than harddisk *) support for alpha channels, including RGBA color model, translucent palettes and color keying *) zlib decompression (inflate) *) zlib compression (deflate) *) CRC32 and ADLER32 checksums *) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. *) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. *) the following chunks are supported by both encoder and decoder: IHDR: header information PLTE: color palette IDAT: pixel data IEND: the final chunk tRNS: transparency for palettized images tEXt: textual information zTXt: compressed textual information iTXt: international textual information bKGD: suggested background color pHYs: physical dimensions tIME: modification time cHRM: RGB chromaticities gAMA: RGB gamma correction iCCP: ICC color profile sRGB: rendering intent sBIT: significant bits 1.2. features not supported --------------------------- The following features are not (yet) supported: *) some features needed to make a conformant PNG-Editor might be still missing. *) partial loading/stream processing. All data must be available and is processed in one call. *) The hIST and sPLT public chunks are not (yet) supported but treated as unknown chunks 2. C and C++ version -------------------- The C version uses buffers allocated with alloc that you need to free() yourself. You need to use init and cleanup functions for each struct whenever using a struct from the C version to avoid exploits and memory leaks. The C++ version has extra functions with std::vectors in the interface and the lodepng::State class which is a LodePNGState with constructor and destructor. These files work without modification for both C and C++ compilers because all the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers ignore it, and the C code is made to compile both with strict ISO C90 and C++. To use the C++ version, you need to rename the source file to lodepng.cpp (instead of lodepng.c), and compile it with a C++ compiler. To use the C version, you need to rename the source file to lodepng.c (instead of lodepng.cpp), and compile it with a C compiler. 3. Security ----------- Even if carefully designed, it's always possible that LodePNG contains possible exploits. If you discover one, please let me know, and it will be fixed. When using LodePNG, care has to be taken with the C version of LodePNG, as well as the C-style structs when working with C++. The following conventions are used for all C-style structs: -if a struct has a corresponding init function, always call the init function when making a new one -if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks -if a struct has a corresponding copy function, use the copy function instead of "=". The destination must also be inited already. 4. Decoding ----------- Decoding converts a PNG compressed image to a raw pixel buffer. Most documentation on using the decoder is at its declarations in the header above. For C, simple decoding can be done with functions such as lodepng_decode32, and more advanced decoding can be done with the struct LodePNGState and lodepng_decode. For C++, all decoding can be done with the various lodepng::decode functions, and lodepng::State can be used for advanced features. When using the LodePNGState, it uses the following fields for decoding: *) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here *) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get *) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use LodePNGInfo info_png -------------------- After decoding, this contains extra information of the PNG image, except the actual pixels, width and height because these are already gotten directly from the decoder functions. It contains for example the original color type of the PNG image, text comments, suggested background color, etc... More details about the LodePNGInfo struct are at its declaration documentation. LodePNGColorMode info_raw ------------------------- When decoding, here you can specify which color type you want the resulting raw image to be. If this is different from the colortype of the PNG, then the decoder will automatically convert the result. This conversion always works, except if you want it to convert a color PNG to grayscale or to a palette with missing colors. By default, 32-bit color is used for the result. LodePNGDecoderSettings decoder ------------------------------ The settings can be used to ignore the errors created by invalid CRC and Adler32 chunks, and to disable the decoding of tEXt chunks. There's also a setting color_convert, true by default. If false, no conversion is done, the resulting data will be as it was in the PNG (after decompression) and you'll have to puzzle the colors of the pixels together yourself using the color type information in the LodePNGInfo. 5. Encoding ----------- Encoding converts a raw pixel buffer to a PNG compressed image. Most documentation on using the encoder is at its declarations in the header above. For C, simple encoding can be done with functions such as lodepng_encode32, and more advanced decoding can be done with the struct LodePNGState and lodepng_encode. For C++, all encoding can be done with the various lodepng::encode functions, and lodepng::State can be used for advanced features. Like the decoder, the encoder can also give errors. However it gives less errors since the encoder input is trusted, the decoder input (a PNG image that could be forged by anyone) is not trusted. When using the LodePNGState, it uses the following fields for encoding: *) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. *) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has *) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use LodePNGInfo info_png -------------------- When encoding, you use this the opposite way as when decoding: for encoding, you fill in the values you want the PNG to have before encoding. By default it's not needed to specify a color type for the PNG since it's automatically chosen, but it's possible to choose it yourself given the right settings. The encoder will not always exactly match the LodePNGInfo struct you give, it tries as close as possible. Some things are ignored by the encoder. The encoder uses, for example, the following settings from it when applicable: colortype and bitdepth, text chunks, time chunk, the color key, the palette, the background color, the interlace method, unknown chunks, ... When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. If the palette contains any colors for which the alpha channel is not 255 (so there are translucent colors in the palette), it'll add a tRNS chunk. LodePNGColorMode info_raw ------------------------- You specify the color type of the raw image that you give to the input here, including a possible transparent color key and palette you happen to be using in your raw image data. By default, 32-bit color is assumed, meaning your input has to be in RGBA format with 4 bytes (unsigned chars) per pixel. LodePNGEncoderSettings encoder ------------------------------ The following settings are supported (some are in sub-structs): *) auto_convert: when this option is enabled, the encoder will automatically choose the smallest possible color mode (including color key) that can encode the colors of all pixels without information loss. *) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, 2 = dynamic huffman tree (best compression). Should be 2 for proper compression. *) use_lz77: whether or not to use LZ77 for compressed block types. Should be true for proper compression. *) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value 2048 by default, but can be set to 32768 for better, but slow, compression. *) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE chunk if force_palette is true. This can used as suggested palette to convert to by viewers that don't support more than 256 colors (if those still exist) *) add_id: add text chunk "Encoder: LodePNG " to the image. *) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. zTXt chunks use zlib compression on the text. This gives a smaller result on large texts but a larger result on small texts (such as a single program name). It's all tEXt or all zTXt though, there's no separate setting per text yet. 6. color conversions -------------------- An important thing to note about LodePNG, is that the color type of the PNG, and the color type of the raw image, are completely independent. By default, when you decode a PNG, you get the result as a raw image in the color type you want, no matter whether the PNG was encoded with a palette, grayscale or RGBA color. And if you encode an image, by default LodePNG will automatically choose the PNG color type that gives good compression based on the values of colors and amount of colors in the image. It can be configured to let you control it instead as well, though. To be able to do this, LodePNG does conversions from one color mode to another. It can convert from almost any color type to any other color type, except the following conversions: RGB to grayscale is not supported, and converting to a palette when the palette doesn't have a required color is not supported. This is not supported on purpose: this is information loss which requires a color reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray is easy, but there are multiple ways if you want to give some channels more weight). By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB color, no matter what color type the PNG has. And by default when encoding, LodePNG automatically picks the best color model for the output PNG, and expects the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control the color format of the images yourself, you can skip this chapter. 6.1. PNG color types -------------------- A PNG image can have many color types, ranging from 1-bit color to 64-bit color, as well as palettized color modes. After the zlib decompression and unfiltering in the PNG image is done, the raw pixel data will have that color type and thus a certain amount of bits per pixel. If you want the output raw image after decoding to have another color type, a conversion is done by LodePNG. The PNG specification gives the following color types: 0: grayscale, bit depths 1, 2, 4, 8, 16 2: RGB, bit depths 8 and 16 3: palette, bit depths 1, 2, 4 and 8 4: grayscale with alpha, bit depths 8 and 16 6: RGBA, bit depths 8 and 16 Bit depth is the amount of bits per pixel per color channel. So the total amount of bits per pixel is: amount of channels * bitdepth. 6.2. color conversions ---------------------- As explained in the sections about the encoder and decoder, you can specify color types and bit depths in info_png and info_raw to change the default behaviour. If, when decoding, you want the raw image to be something else than the default, you need to set the color type and bit depth you want in the LodePNGColorMode, or the parameters colortype and bitdepth of the simple decoding function. If, when encoding, you use another color type than the default in the raw input image, you need to specify its color type and bit depth in the LodePNGColorMode of the raw image, or use the parameters colortype and bitdepth of the simple encoding function. If, when encoding, you don't want LodePNG to choose the output PNG color type but control it yourself, you need to set auto_convert in the encoder settings to false, and specify the color type you want in the LodePNGInfo of the encoder (including palette: it can generate a palette if auto_convert is true, otherwise not). If the input and output color type differ (whether user chosen or auto chosen), LodePNG will do a color conversion, which follows the rules below, and may sometimes result in an error. To avoid some confusion: -the decoder converts from PNG to raw image -the encoder converts from raw image to PNG -the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image -the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG -when encoding, the color type in LodePNGInfo is ignored if auto_convert is enabled, it is automatically generated instead -when decoding, the color type in LodePNGInfo is set by the decoder to that of the original PNG image, but it can be ignored since the raw image has the color type you requested instead -if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion between the color types is done if the color types are supported. If it is not supported, an error is returned. If the types are the same, no conversion is done. -even though some conversions aren't supported, LodePNG supports loading PNGs from any colortype and saving PNGs to any colortype, sometimes it just requires preparing the raw image correctly before encoding. -both encoder and decoder use the same color converter. The function lodepng_convert does the color conversion. It is available in the interface but normally isn't needed since the encoder and decoder already call it. Non supported color conversions: -color to grayscale when non-gray pixels are present: no error is thrown, but the result will look ugly because only the red channel is taken (it assumes all three channels are the same in this case so ignores green and blue). The reason no error is given is to allow converting from three-channel grayscale images to one-channel even if there are numerical imprecisions. -anything to palette when the palette does not have an exact match for a from-color in it: in this case an error is thrown Supported color conversions: -anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA -any gray or gray+alpha, to gray or gray+alpha -anything to a palette, as long as the palette has the requested colors in it -removing alpha channel -higher to smaller bitdepth, and vice versa If you want no color conversion to be done (e.g. for speed or control): -In the encoder, you can make it save a PNG with any color type by giving the raw color mode and LodePNGInfo the same color mode, and setting auto_convert to false. -In the decoder, you can make it store the pixel data in the same color type as the PNG has, by setting the color_convert setting to false. Settings in info_raw are then ignored. 6.3. padding bits ----------------- In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines have a bit amount that isn't a multiple of 8, then padding bits are used so that each scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. The raw input image you give to the encoder, and the raw output image you get from the decoder will NOT have these padding bits, e.g. in the case of a 1-bit image with a width of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, not the first bit of a new byte. 6.4. A note about 16-bits per channel and endianness ---------------------------------------------------- LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like for any other color format. The 16-bit values are stored in big endian (most significant byte first) in these arrays. This is the opposite order of the little endian used by x86 CPU's. LodePNG always uses big endian because the PNG file format does so internally. Conversions to other formats than PNG uses internally are not supported by LodePNG on purpose, there are myriads of formats, including endianness of 16-bit colors, the order in which you store R, G, B and A, and so on. Supporting and converting to/from all that is outside the scope of LodePNG. This may mean that, depending on your use case, you may want to convert the big endian output of LodePNG to little endian with a for loop. This is certainly not always needed, many applications and libraries support big endian 16-bit colors anyway, but it means you cannot simply cast the unsigned char* buffer to an unsigned short* buffer on x86 CPUs. 7. error values --------------- All functions in LodePNG that return an error code, return 0 if everything went OK, or a non-zero code if there was an error. The meaning of the LodePNG error values can be retrieved with the function lodepng_error_text: given the numerical error code, it returns a description of the error in English as a string. Check the implementation of lodepng_error_text to see the meaning of each code. It is not recommended to use the numerical values to programmatically make different decisions based on error types as the numbers are not guaranteed to stay backwards compatible. They are for human consumption only. Programmatically only 0 or non-0 matter. 8. chunks and PNG editing ------------------------- If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG editor that should follow the rules about handling of unknown chunks, or if your program is able to read other types of chunks than the ones handled by LodePNG, then that's possible with the chunk functions of LodePNG. A PNG chunk has the following layout: 4 bytes length 4 bytes type name length bytes data 4 bytes CRC 8.1. iterating through chunks ----------------------------- If you have a buffer containing the PNG image data, then the first chunk (the IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the signature of the PNG and are not part of a chunk. But if you start at byte 8 then you have a chunk, and can check the following things of it. NOTE: none of these functions check for memory buffer boundaries. To avoid exploits, always make sure the buffer contains all the data of the chunks. When using lodepng_chunk_next, make sure the returned value is within the allocated memory. unsigned lodepng_chunk_length(const unsigned char* chunk): Get the length of the chunk's data. The total chunk length is this length + 12. void lodepng_chunk_type(char type[5], const unsigned char* chunk): unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): Get the type of the chunk or compare if it's a certain type unsigned char lodepng_chunk_critical(const unsigned char* chunk): unsigned char lodepng_chunk_private(const unsigned char* chunk): unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). Check if the chunk is private (public chunks are part of the standard, private ones not). Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your program doesn't handle that type of unknown chunk. unsigned char* lodepng_chunk_data(unsigned char* chunk): const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): Get a pointer to the start of the data of the chunk. unsigned lodepng_chunk_check_crc(const unsigned char* chunk): void lodepng_chunk_generate_crc(unsigned char* chunk): Check if the crc is correct or generate a correct one. unsigned char* lodepng_chunk_next(unsigned char* chunk): const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these functions do no boundary checking of the allocated data whatsoever, so make sure there is enough data available in the buffer to be able to go to the next chunk. unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, const char* type, const unsigned char* data): These functions are used to create new chunks that are appended to the data in *out that has length *outsize. The append function appends an existing chunk to the new data. The create function creates a new chunk with the given parameters and appends it. Type is the 4-letter name of the chunk. 8.2. chunks in info_png ----------------------- The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 buffers (each with size) to contain 3 types of unknown chunks: the ones that come before the PLTE chunk, the ones that come between the PLTE and the IDAT chunks, and the ones that come after the IDAT chunks. It's necessary to make the distinction between these 3 cases because the PNG standard forces to keep the ordering of unknown chunks compared to the critical chunks, but does not force any other ordering rules. info_png.unknown_chunks_data[0] is the chunks before PLTE info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT info_png.unknown_chunks_data[2] is the chunks after IDAT The chunks in these 3 buffers can be iterated through and read by using the same way described in the previous subchapter. When using the decoder to decode a PNG, you can make it store all unknown chunks if you set the option settings.remember_unknown_chunks to 1. By default, this option is off (0). The encoder will always encode unknown chunks that are stored in the info_png. If you need it to add a particular chunk that isn't known by LodePNG, you can use lodepng_chunk_append or lodepng_chunk_create to the chunk data in info_png.unknown_chunks_data[x]. Chunks that are known by LodePNG should not be added in that way. E.g. to make LodePNG add a bKGD chunk, set background_defined to true and add the correct parameters there instead. 9. compiler support ------------------- No libraries other than the current standard C library are needed to compile LodePNG. For the C++ version, only the standard C++ library is needed on top. Add the files lodepng.c(pp) and lodepng.h to your project, include lodepng.h where needed, and your program can read/write PNG files. It is compatible with C90 and up, and C++03 and up. If performance is important, use optimization when compiling! For both the encoder and decoder, this makes a large difference. Make sure that LodePNG is compiled with the same compiler of the same version and with the same settings as the rest of the program, or the interfaces with std::vectors and std::strings in C++ can be incompatible. CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. *) gcc and g++ LodePNG is developed in gcc so this compiler is natively supported. It gives no warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ version 4.7.1 on Linux, 32-bit and 64-bit. *) Clang Fully supported and warning-free. *) Mingw The Mingw compiler (a port of gcc for Windows) should be fully supported by LodePNG. *) Visual Studio and Visual C++ Express Edition LodePNG should be warning-free with warning level W4. Two warnings were disabled with pragmas though: warning 4244 about implicit conversions, and warning 4996 where it wants to use a non-standard function fopen_s instead of the standard C fopen. Visual Studio may want "stdafx.h" files to be included in each source file and give an error "unexpected end of file while looking for precompiled header". This is not standard C++ and will not be added to the stock LodePNG. You can disable it for lodepng.cpp only by right clicking it, Properties, C/C++, Precompiled Headers, and set it to Not Using Precompiled Headers there. NOTE: Modern versions of VS should be fully supported, but old versions, e.g. VS6, are not guaranteed to work. *) Compilers on Macintosh LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for C and C++. *) Other Compilers If you encounter problems on any compilers, feel free to let me know and I may try to fix it if the compiler is modern and standards compliant. 10. examples ------------ This decoder example shows the most basic usage of LodePNG. More complex examples can be found on the LodePNG website. NOTE: these examples do not support wide-character filenames, you can use an external method to handle such files and encode or decode in-memory 10.1. decoder C++ example ------------------------- #include "lodepng.h" #include int main(int argc, char *argv[]) { const char* filename = argc > 1 ? argv[1] : "test.png"; //load and decode std::vector image; unsigned width, height; unsigned error = lodepng::decode(image, width, height, filename); //if there's an error, display it if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... } 10.2. decoder C example ----------------------- #include "lodepng.h" int main(int argc, char *argv[]) { unsigned error; unsigned char* image; size_t width, height; const char* filename = argc > 1 ? argv[1] : "test.png"; error = lodepng_decode32_file(&image, &width, &height, filename); if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); / * use image here * / free(image); return 0; } 11. state settings reference ---------------------------- A quick reference of some settings to set on the LodePNGState For decoding: state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums state.decoder.zlibsettings.custom_...: use custom inflate function state.decoder.ignore_crc: ignore CRC checksums state.decoder.ignore_critical: ignore unknown critical chunks state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors state.decoder.color_convert: convert internal PNG color to chosen one state.decoder.read_text_chunks: whether to read in text metadata chunks state.decoder.remember_unknown_chunks: whether to read in unknown chunks state.info_raw.colortype: desired color type for decoded image state.info_raw.bitdepth: desired bit depth for decoded image state.info_raw....: more color settings, see struct LodePNGColorMode state.info_png....: no settings for decoder but output, see struct LodePNGInfo For encoding: state.encoder.zlibsettings.btype: disable compression by setting it to 0 state.encoder.zlibsettings.use_lz77: use LZ77 in compression state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching state.encoder.zlibsettings.lazymatching: try one more LZ77 matching state.encoder.zlibsettings.custom_...: use custom deflate function state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png state.encoder.filter_palette_zero: PNG filter strategy for palette state.encoder.filter_strategy: PNG filter strategy to encode with state.encoder.force_palette: add palette even if not encoding to one state.encoder.add_id: add LodePNG identifier and version as a text chunk state.encoder.text_compression: use compressed text chunks for metadata state.info_raw.colortype: color type of raw input image you provide state.info_raw.bitdepth: bit depth of raw input image you provide state.info_raw: more color settings, see struct LodePNGColorMode state.info_png.color.colortype: desired color type if auto_convert is false state.info_png.color.bitdepth: desired bit depth if auto_convert is false state.info_png.color....: more color settings, see struct LodePNGColorMode state.info_png....: more PNG related settings, see struct LodePNGInfo 12. changes ----------- The version number of LodePNG is the date of the change given in the format yyyymmdd. Some changes aren't backwards compatible. Those are indicated with a (!) symbol. Not all changes are listed here, the commit history in github lists more: https://github.com/lvandeve/lodepng *) 6 may 2025 (!): renamed mDCv to mDCV and cLLi to cLLI as per the recent rename in the draft png third edition spec. Please note that as long as the third edition is not finalized, backwards-incompatible changes to its features are possible. *) 23 dec 2024: added support for the mDCv and cLLi chunks (for png third edition spec) *) 22 dec 2024: added support for the cICP chunk (for png third edition spec) *) 15 dec 2024: added support for the eXIf chunk (for png third edition spec) *) 10 apr 2023: faster CRC32 implementation, but with larger lookup table. *) 13 jun 2022: added support for the sBIT chunk. *) 09 jan 2022: minor decoder speed improvements. *) 27 jun 2021: added warnings that file reading/writing functions don't support wide-character filenames (support for this is not planned, opening files is not the core part of PNG decoding/decoding and is platform dependent). *) 17 oct 2020: prevent decoding too large text/icc chunks by default. *) 06 mar 2020: simplified some of the dynamic memory allocations. *) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct overflow checks. *) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. *) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette if gray ICC profile) and non-ICC LodePNGColorProfile renamed to LodePNGColorStats. *) 30 dec 2018: code style changes only: removed newlines before opening braces. *) 10 sep 2018: added way to inspect metadata chunks without full decoding. *) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use palette index in case of palette. *) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This change is backwards compatible unless you relied on unknown_chunks for those. *) 11 jun 2018: less restrictive check for pixel size integer overflow *) 14 jan 2018: allow optionally ignoring a few more recoverable errors *) 17 sep 2017: fix memory leak for some encoder input error cases *) 27 nov 2016: grey+alpha auto color model detection bugfix *) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). *) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within the limits of pure C90). *) 08 dec 2015: Made load_file function return error if file can't be opened. *) 24 oct 2015: Bugfix with decoding to palette output. *) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. *) 24 aug 2014: Moved to github *) 23 aug 2014: Reduced needless memory usage of decoder. *) 28 jun 2014: Removed fix_png setting, always support palette OOB for simplicity. Made ColorProfile public. *) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. *) 22 dec 2013: Power of two windowsize required for optimization. *) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. *) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). *) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" prefix for the custom allocators and made it possible with a new #define to use custom ones in your project without needing to change lodepng's code. *) 28 jan 2013: Bugfix with color key. *) 27 oct 2012: Tweaks in text chunk keyword length error handling. *) 8 oct 2012: (!) Added new filter strategy (entropy) and new auto color mode. (no palette). Better deflate tree encoding. New compression tweak settings. Faster color conversions while decoding. Some internal cleanups. *) 23 sep 2012: Reduced warnings in Visual Studio a little bit. *) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions and made it work with function pointers instead. *) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc and free functions and toggle #defines from compiler flags. Small fixes. *) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. *) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed redundant C++ codec classes. Reduced amount of structs. Everything changed, but it is cleaner now imho and functionality remains the same. Also fixed several bugs and shrunk the implementation code. Made new samples. *) 6 nov 2011: (!) By default, the encoder now automatically chooses the best PNG color model and bit depth, based on the amount and type of colors of the raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. *) 9 oct 2011: simpler hash chain implementation for the encoder. *) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. *) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. A bug with the PNG filtertype heuristic was fixed, so that it chooses much better ones (it's quite significant). A setting to do an experimental, slow, brute force search for PNG filter types is added. *) 17 aug 2011: (!) changed some C zlib related function names. *) 16 aug 2011: made the code less wide (max 120 characters per line). *) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. *) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. *) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman to optimize long sequences of zeros. *) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and LodePNG_InfoColor_canHaveAlpha functions for convenience. *) 7 nov 2010: added LodePNG_error_text function to get error code description. *) 30 oct 2010: made decoding slightly faster *) 26 oct 2010: (!) changed some C function and struct names (more consistent). Reorganized the documentation and the declaration order in the header. *) 08 aug 2010: only changed some comments and external samples. *) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. *) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. *) 02 sep 2008: fixed bug where it could create empty tree that linux apps could read by ignoring the problem but windows apps couldn't. *) 06 jun 2008: added more error checks for out of memory cases. *) 26 apr 2008: added a few more checks here and there to ensure more safety. *) 06 mar 2008: crash with encoding of strings fixed *) 02 feb 2008: support for international text chunks added (iTXt) *) 23 jan 2008: small cleanups, and #defines to divide code in sections *) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. *) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. *) 17 jan 2008: ability to encode and decode compressed zTXt chunks added Also various fixes, such as in the deflate and the padding bits code. *) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved filtering code of encoder. *) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A C++ wrapper around this provides an interface almost identical to before. Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code are together in these files but it works both for C and C++ compilers. *) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks *) 30 aug 2007: bug fixed which makes this Borland C++ compatible *) 09 aug 2007: some VS2005 warnings removed again *) 21 jul 2007: deflate code placed in new namespace separate from zlib code *) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images *) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing invalid std::vector element [0] fixed, and level 3 and 4 warnings removed *) 02 jun 2007: made the encoder add a tag with version by default *) 27 may 2007: zlib and png code separated (but still in the same file), simple encoder/decoder functions added for more simple usage cases *) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), moved some examples from here to lodepng_examples.cpp *) 12 may 2007: palette decoding bug fixed *) 24 apr 2007: changed the license from BSD to the zlib license *) 11 mar 2007: very simple addition: ability to encode bKGD chunks. *) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding palettized PNG images. Plus little interface change with palette and texts. *) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. Fixed a bug where the end code of a block had length 0 in the Huffman tree. *) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented and supported by the encoder, resulting in smaller PNGs at the output. *) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. *) 24 jan 2007: gave encoder an error interface. Added color conversion from any greyscale type to 8-bit greyscale with or without alpha. *) 21 jan 2007: (!) Totally changed the interface. It allows more color types to convert to and is more uniform. See the manual for how it works now. *) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: encode/decode custom tEXt chunks, separate classes for zlib & deflate, and at last made the decoder give errors for incorrect Adler32 or Crc. *) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. *) 29 dec 2006: Added support for encoding images without alpha channel, and cleaned out code as well as making certain parts faster. *) 28 dec 2006: Added "Settings" to the encoder. *) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. Removed some code duplication in the decoder. Fixed little bug in an example. *) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. Fixed a bug of the decoder with 16-bit per color. *) 15 oct 2006: Changed documentation structure *) 09 oct 2006: Encoder class added. It encodes a valid PNG image from the given image buffer, however for now it's not compressed. *) 08 sep 2006: (!) Changed to interface with a Decoder class *) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different way. Renamed decodePNG to decodePNGGeneric. *) 29 jul 2006: (!) Changed the interface: image info is now returned as a struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. *) 28 jul 2006: Cleaned the code and added new error checks. Corrected terminology "deflate" into "inflate". *) 23 jun 2006: Added SDL example in the documentation in the header, this example allows easy debugging by displaying the PNG and its transparency. *) 22 jun 2006: (!) Changed way to obtain error value. Added loadFile function for convenience. Made decodePNG32 faster. *) 21 jun 2006: (!) Changed type of info vector to unsigned. Changed position of palette in info vector. Fixed an important bug that happened on PNGs with an uncompressed block. *) 16 jun 2006: Internally changed unsigned into unsigned where needed, and performed some optimizations. *) 07 jun 2006: (!) Renamed functions to decodePNG and placed them in LodePNG namespace. Changed the order of the parameters. Rewrote the documentation in the header. Renamed files to lodepng.cpp and lodepng.h *) 22 apr 2006: Optimized and improved some code *) 07 sep 2005: (!) Changed to std::vector interface *) 12 aug 2005: Initial release (C++, decoder only) */ jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/md5.c000066400000000000000000000210471523150047600215650ustar00rootroot00000000000000/* * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. * MD5 Message-Digest Algorithm (RFC 1321). * * Homepage: * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: * Alexander Peslyak, better known as Solar Designer * * This software was written by Alexander Peslyak in 2001. No copyright is * claimed, and the software is hereby placed in the public domain. * In case this attempt to disclaim copyright and place the software in the * public domain is deemed null and void, then the software is * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * (This is a heavily cut-down "BSD license".) * * This differs from Colin Plumb's older public domain implementation in that * no exactly 32-bit integer data type is required (any 32-bit or wider * unsigned integer data type will do), there's no compile-time endianness * configuration, and the function prototypes match OpenSSL's. No code from * Colin Plumb's implementation has been reused; this comment merely compares * the properties of the two independent implementations. * * The primary goals of this implementation are portability and ease of use. * It is meant to be fast, but not as fast as possible. Some known * optimizations are not included to reduce source code size and avoid * compile-time configuration. */ #ifndef HAVE_OPENSSL #include #include "md5.h" /* * The basic MD5 functions. * * F and G are optimized compared to their RFC 1321 definitions for * architectures that lack an AND-NOT instruction, just like in Colin Plumb's * implementation. */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) #define H(x, y, z) (((x) ^ (y)) ^ (z)) #define H2(x, y, z) ((x) ^ ((y) ^ (z))) #define I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ #define STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); /* * SET reads 4 input bytes in little-endian byte order and stores them in a * properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned memory * accesses is just an optimization. Nothing will break if it fails to detect * a suitable architecture. * * Unfortunately, this optimization may be a C strict aliasing rules violation * if the caller's data buffer has effective type that cannot be aliased by * MD5_u32plus. In practice, this problem may occur if these MD5 routines are * inlined into a calling function, or with future and dangerously advanced * link-time optimizations. For the time being, keeping these MD5 routines in * their own translation unit avoids the problem. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) #define SET(n) \ (*(MD5_u32plus *)&ptr[(n) * 4]) #define GET(n) \ SET(n) #else #define SET(n) \ (ctx->block[(n)] = \ (MD5_u32plus)ptr[(n) * 4] | \ ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) #define GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update the bit * counters. There are no alignment requirements. */ static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) { const unsigned char *ptr; MD5_u32plus a, b, c, d; MD5_u32plus saved_a, saved_b, saved_c, saved_d; ptr = (const unsigned char *)data; a = ctx->a; b = ctx->b; c = ctx->c; d = ctx->d; do { saved_a = a; saved_b = b; saved_c = c; saved_d = d; /* Round 1 */ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) STEP(F, c, d, a, b, SET(2), 0x242070db, 17) STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) /* Round 2 */ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) STEP(G, d, a, b, c, GET(10), 0x02441453, 9) STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) /* Round 3 */ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) /* Round 4 */ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; c += saved_c; d += saved_d; ptr += 64; } while (size -= 64); ctx->a = a; ctx->b = b; ctx->c = c; ctx->d = d; return ptr; } void MD5_Init(MD5_CTX *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; ctx->d = 0x10325476; ctx->lo = 0; ctx->hi = 0; } void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) { MD5_u32plus saved_lo; unsigned long used, available; saved_lo = ctx->lo; if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) ctx->hi++; ctx->hi += size >> 29; used = saved_lo & 0x3f; if (used) { available = 64 - used; if (size < available) { memcpy(&ctx->buffer[used], data, size); return; } memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; body(ctx, ctx->buffer, 64); } if (size >= 64) { data = body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } #define OUT(dst, src) \ (dst)[0] = (unsigned char)(src); \ (dst)[1] = (unsigned char)((src) >> 8); \ (dst)[2] = (unsigned char)((src) >> 16); \ (dst)[3] = (unsigned char)((src) >> 24); void MD5_Final(unsigned char *result, MD5_CTX *ctx) { unsigned long used, available; used = ctx->lo & 0x3f; ctx->buffer[used++] = 0x80; available = 64 - used; if (available < 8) { memset(&ctx->buffer[used], 0, available); body(ctx, ctx->buffer, 64); used = 0; available = 64; } memset(&ctx->buffer[used], 0, available - 8); ctx->lo <<= 3; OUT(&ctx->buffer[56], ctx->lo) OUT(&ctx->buffer[60], ctx->hi) body(ctx, ctx->buffer, 64); OUT(&result[0], ctx->a) OUT(&result[4], ctx->b) OUT(&result[8], ctx->c) OUT(&result[12], ctx->d) memset(ctx, 0, sizeof(*ctx)); } #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/md5.h000066400000000000000000000026021523150047600215660ustar00rootroot00000000000000/* * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. * MD5 Message-Digest Algorithm (RFC 1321). * * Homepage: * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: * Alexander Peslyak, better known as Solar Designer * * This software was written by Alexander Peslyak in 2001. No copyright is * claimed, and the software is hereby placed in the public domain. * In case this attempt to disclaim copyright and place the software in the * public domain is deemed null and void, then the software is * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * See md5.c for more information. */ #ifdef HAVE_OPENSSL #include #elif !defined(_MD5_H) #define _MD5_H /* Any 32-bit or wider unsigned integer data type will do */ typedef unsigned int MD5_u32plus; typedef struct { MD5_u32plus lo, hi; MD5_u32plus a, b, c, d; unsigned char buffer[64]; MD5_u32plus block[16]; } MD5_CTX; extern void MD5_Init(MD5_CTX *ctx); extern void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size); extern void MD5_Final(unsigned char *result, MD5_CTX *ctx); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/miniz/000077500000000000000000000000001523150047600220565ustar00rootroot00000000000000jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/miniz/ChangeLog.md000066400000000000000000000473231523150047600242400ustar00rootroot00000000000000## Changelog ### 3.1.2 - Fix central directory offset overflow when reading zip file header - Add tdefl_write_image_to_png_file_in_memory_ex parameter validation - Add fuzz targets for ZIP validation, writing, and compression APIs - Support cmake 4 - Guard against code_len==0 infinite loop in tinfl_decompress ### 3.1.1 - Declare function wrappers as inline static to fix warnings ### 3.1.0 - Fix warnings: Ensure correct integer promotion when adding - Fix Unicode paths on MinGW32 - Prevent min/max conflicts between windows.h and std namespace - Update miniz_tdef.c to enable compiling in forced-C++ mode - Fix missing large file support warning on 64-bit Linux - Bump cmake minimum version - Add some catch2 tests including CI - Remove parameter check in `tinfl_decompress` that breaks `tinfl_decompress_mem_to_heap` - Don't redefine `WIN32_LEAN_AND_MEAN` if already defined - Fix OSS-Fuzz build - Do not redefine `TDEFL_LESS_MEMORY` if already defined - Fix unused arg warnings when building with `MINIZ_NO_TIME` - Support Zip archives not starting at zero offset - Fix offset detection for MZ_ZIP_TYPE_USER - Avoid fdreopen if possible - cmake: new option BUILD_NO_STDIO to enable MINIZ_NO_STDIO - Add fuzzer for mz_zip_add_mem_to_archive_file_in_place function - Replace defines with function wrappers etc. as much as possible ### 3.0.2 - Fix buffer overrun in mz_utf8z_to_widechar on Windows ### 3.0.1 - Fix compilation error with MINIZ_USE_UNALIGNED_LOADS_AND_STORES=1 ### 3.0.0 - Reduce memory usage for inflate. This changes `struct tinfl_decompressor_tag` and therefore requires a major version bump (breaks ABI compatibility) - Add padding to structures so it continues to work if features differ. This also changes some structures - Use _ftelli64, _fseeki64 and stat with MinGW32 and OpenWatcom - Fix varios warnings with OpenWatcom compiler - Avoid using unaligned memory access in UBSan builds - Set MINIZ_LITTLE_ENDIAN only if not set - Add MINIZ_NO_DEFLATE_APIS and MINIZ_NO_INFLATE_APIS - Fix use of uninitialized memory in tinfl_decompress_mem_to_callback() - Use wfopen on windows - Use _wstat64 instead _stat64 on windows - Use level_and_flags after MZ_DEFAULT_COMPRESSION has been handled - Improve endianess detection - Don't use unaligned stores and loads per default - Fix function declaration if MINIZ_NO_STDIO is used - Fix MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 not being set - Remove total files check (its 32-bit uint) - tinfl_decompress: avoid NULL ptr arithmetic UB - miniz_zip: fix mz_zip_reader_extract_to_heap to read correct sizes - Eliminate 64-bit operations on 32-bit machines - Disable treating warnings as error with MSVC - Disable building shared lib via CMake by default - Fixed alignment problems on MacOS - Fixed get error string for MZ_ZIP_TOTAL_ERRORS - Write correct FLEVEL 2-bit value in zlib header - miniz.pc.in: fix include path not containing the "miniz" suffix - Fix compatibility with FreeBSD - pkg-config tweaks - Fix integer overflow in header corruption check - Fix some warnings - tdefl_compress_normal: Avoid NULL ptr arithmetic UB - replace use of stdint.h types with mz_ variants ### 2.2.0 - Fix examples with amalgamation - Modified cmake script to support shared library mode and find_package - Fix for misleading doc comment on `mz_zip_reader_init_cfile` function - Add include location tolerance and stop forcing `_GNU_SOURCE` - Fix: mz_zip_reader_locate_file_v2 returns an mz_bool - Fix large file system checks - Add #elif to enable an external mz_crc32() to be linked in - Write with dynamic size (size of file/data to be added not known before adding) - Added uncompress2 for zlib compatibility - Add support for building as a Meson subproject - Added OSSFuzz support; Integrate with CIFuzz - Add pkg-config file - Fixed use-of-uninitialized value msan error when copying dist bytes with no output bytes written. - mz_zip_validate_file(): fix memory leak on errors - Fixed MSAN use-of-uninitialized in tinfl_decompress when invalid dist is decoded. In this instance dist was 31 which s_dist_base translates as 0 - Add flag to set (compressed) size in local file header - avoid use of uninitialized value in tdefl_record_literal ### 2.1.0 - More instances of memcpy instead of cast and use memcpy per default - Remove inline for c90 support - New function to read files via callback functions when adding them - Fix out of bounds read while reading Zip64 extended information - guard memcpy when n == 0 because buffer may be NULL - Implement inflateReset() function - Move comp/decomp alloc/free prototypes under guarding #ifndef MZ_NO_MALLOC - Fix large file support under Windows - Don't warn if _LARGEFILE64_SOURCE is not defined to 1 - Fixes for MSVC warnings - Remove check that path of file added to archive contains ':' or '\' - Add !defined check on MINIZ_USE_ALIGNED_LOADS_AND_STORES ### 2.0.8 - Remove unimplemented functions (mz_zip_locate_file and mz_zip_locate_file_v2) - Add license, changelog, readme and example files to release zip - Fix heap overflow to user buffer in tinfl_status tinfl_decompress - Fix corrupt archive if uncompressed file smaller than 4 byte and the file is added by mz_zip_writer_add_mem* ### 2.0.7 - Removed need in C++ compiler in cmake build - Fixed a lot of uninitialized value errors found with Valgrind by memsetting m_dict to 0 in tdefl_init - Fix resource leak in mz_zip_reader_init_file_v2 - Fix assert with mz_zip_writer_add_mem* w/MZ_DEFAULT_COMPRESSION - cmake build: install library and headers - Remove _LARGEFILE64_SOURCE requirement from apple defines for large files ### 2.0.6 - Improve MZ_ZIP_FLAG_WRITE_ZIP64 documentation - Remove check for cur_archive_file_ofs > UINT_MAX because cur_archive_file_ofs is not used after this point - Add cmake debug configuration - Fix PNG height when creating png files - Add "iterative" file extraction method based on mz_zip_reader_extract_to_callback. - Option to use memcpy for unaligned data access - Define processor/arch macros as zero if not set to one ### 2.0.4/2.0.5 - Fix compilation with the various omission compile definitions ### 2.0.3 - Fix GCC/clang compile warnings - Added callback for periodic flushes (for ZIP file streaming) - Use UTF-8 for file names in ZIP files per default ### 2.0.2 - Fix source backwards compatibility with 1.x - Fix a ZIP bit not being set correctly ### 2.0.1 - Added some tests - Added CI - Make source code ANSI C compatible ### 2.0.0 beta - Matthew Sitton merged miniz 1.x to Rich Geldreich's vogl ZIP64 changes. Miniz is now licensed as MIT since the vogl code base is MIT licensed - Miniz is now split into several files - Miniz does now not seek backwards when creating ZIP files. That is the ZIP files can be streamed - Miniz automatically switches to the ZIP64 format when the created ZIP files goes over ZIP file limits - Similar to [SQLite](https://www.sqlite.org/amalgamation.html) the Miniz source code is amalgamated into one miniz.c/miniz.h pair in a build step (amalgamate.sh). Please use miniz.c/miniz.h in your projects - Miniz 2 is only source back-compatible with miniz 1.x. It breaks binary compatibility because structures changed ### v1.16 BETA Oct 19, 2013 Still testing, this release is downloadable from [here](http://www.tenacioussoftware.com/miniz_v116_beta_r1.7z). Two key inflator-only robustness and streaming related changes. Also merged in tdefl_compressor_alloc(), tdefl_compressor_free() helpers to make script bindings easier for rustyzip. I would greatly appreciate any help with testing or any feedback. The inflator in raw (non-zlib) mode is now usable on gzip or similar streams that have a bunch of bytes following the raw deflate data (problem discovered by rustyzip author williamw520). This version should never read beyond the last byte of the raw deflate data independent of how many bytes you pass into the input buffer. The inflator now has a new failure status TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS (-4). Previously, if the inflator was starved of bytes and could not make progress (because the input buffer was empty and the caller did not set the TINFL_FLAG_HAS_MORE_INPUT flag - say on truncated or corrupted compressed data stream) it would append all 0's to the input and try to soldier on. This is scary behavior if the caller didn't know when to stop accepting output (because it didn't know how much uncompressed data was expected, or didn't enforce a sane maximum). v1.16 will instead return TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS immediately if it needs 1 or more bytes to make progress, the input buf is empty, and the caller has indicated that no more input is available. This is a "soft" failure, so you can call the inflator again with more input and it will try to continue, or you can give up and fail. This could be very useful in network streaming scenarios. - The inflator coroutine func. is subtle and complex so I'm being cautious about this release. I would greatly appreciate any help with testing or any feedback. I feel good about these changes, and they've been through several hours of automated testing, but they will probably not fix anything for the majority of prev. users so I'm going to mark this release as beta for a few weeks and continue testing it at work/home on various things. - The inflator in raw (non-zlib) mode is now usable on gzip or similar data streams that have a bunch of bytes following the raw deflate data (problem discovered by rustyzip author williamw520). This version should *never* read beyond the last byte of the raw deflate data independent of how many bytes you pass into the input buffer. This issue was caused by the various Huffman bitbuffer lookahead optimizations, and would not be an issue if the caller knew and enforced the precise size of the raw compressed data *or* if the compressed data was in zlib format (i.e. always followed by the byte aligned zlib adler32). So in other words, you can now call the inflator on deflate streams that are followed by arbitrary amounts of data and it's guaranteed that decompression will stop exactly on the last byte. - The inflator now has a new failure status: TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS (-4). Previously, if the inflator was starved of bytes and could not make progress (because the input buffer was empty and the caller did not set the TINFL_FLAG_HAS_MORE_INPUT flag - say on truncated or corrupted compressed data stream) it would append all 0's to the input and try to soldier on. This is scary, because in the worst case, I believe it was possible for the prev. inflator to start outputting large amounts of literal data. If the caller didn't know when to stop accepting output (because it didn't know how much uncompressed data was expected, or didn't enforce a sane maximum) it could continue forever. v1.16 cannot fall into this failure mode, instead it'll return TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS immediately if it needs 1 or more bytes to make progress, the input buf is empty, and the caller has indicated that no more input is available. This is a "soft" failure, so you can call the inflator again with more input and it will try to continue, or you can give up and fail. This could be very useful in network streaming scenarios. - Added documentation to all the tinfl return status codes, fixed miniz_tester so it accepts double minus params for Linux, tweaked example1.c, added a simple "follower bytes" test to miniz_tester.cpp. ### v1.15 r4 STABLE - Oct 13, 2013 Merged over a few very minor bug fixes that I fixed in the zip64 branch. This is downloadable from [here](http://code.google.com/p/miniz/downloads/list) and also in SVN head (as of 10/19/13). ### v1.15 - Oct. 13, 2013 Interim bugfix release while I work on the next major release with zip64 and streaming compression/decompression support. Fixed the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com), which could cause the locate files func to not find files when this flag was specified. Also fixed a bug in mz_zip_reader_extract_to_mem_no_alloc() with user provided read buffers (thanks kymoon). I also merged lots of compiler fixes from various github repo branches and Google Code issue reports. I finally added cmake support (only tested under for Linux so far), compiled and tested with clang v3.3 and gcc 4.6 (under Linux), added defl_write_image_to_png_file_in_memory_ex() (supports Y flipping for OpenGL use, real-time compression), added a new PNG example (example6.c - Mandelbrot), and I added 64-bit file I/O support (stat64(), etc.) for glibc. - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug would only have occurred in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti - Merged MZ_FORCEINLINE fix from hdeanclark - Fix include before config #ifdef, thanks emil.brink - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can set it to 1 for real-time compression). - Merged in some compiler fixes from paulharris's github repro. - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch ### v1.14 - May 20, 2012 (SVN Only) Minor tweaks to get miniz.c compiling with the Tiny C Compiler, added #ifndef MINIZ_NO_TIME guards around utime.h includes. Adding mz_free() function, so the caller can free heap blocks returned by miniz using whatever heap functions it has been configured to use, MSVC specific fixes to use "safe" variants of several functions (localtime_s, fopen_s, freopen_s). MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). Compiler specific fixes, some from fermtect. I upgraded to TDM GCC 4.6.1 and now static __forceinline is giving it fits, so I'm changing all usage of __forceinline to MZ_FORCEINLINE and forcing gcc to use __attribute__((__always_inline__)) (and MSVC to use __forceinline). Also various fixes from fermtect for MinGW32: added #include , 64-bit ftell/fseek fixes. ### v1.13 - May 19, 2012 From jason@cornsyrup.org and kelwert@mtu.edu - Most importantly, fixed mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bits. Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. Other stuff: Eliminated a bunch of warnings when compiling with GCC 32-bit/64. Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) Fix ftell() usage in a few of the examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). Fix fail logic handling in mz_zip_add_mem_to_archive_file_in_place() so it always calls mz_zip_writer_finalize_archive() and mz_zip_writer_end(), even if the file add fails. - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). ### v1.12 - 4/12/12 More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. ### v1.11 - 5/28/11 Added statement from unlicense.org ### v1.10 - 5/27/11 - Substantial compressor optimizations: - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. - Refactored the compression code for better readability and maintainability. - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large drop in throughput on some files). ### v1.09 - 5/15/11 Initial stable release. jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/miniz/LICENSE000066400000000000000000000022401523150047600230610ustar00rootroot00000000000000Copyright 2013-2014 RAD Game Tools and Valve Software Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC 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 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. jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/miniz/miniz.c000066400000000000000000012542571523150047600233700ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200112 #include "miniz.h" /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * 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 * 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. * **************************************************************************/ typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; #ifdef __cplusplus extern "C" { #endif /* ------------------- zlib-style API's */ mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; if (!ptr) return MZ_ADLER32_INIT; 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; } return (s2 << 16) + s1; } /* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ #if 0 mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; mz_uint32 crcu32 = (mz_uint32)crc; if (!ptr) return MZ_CRC32_INIT; crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } return ~crcu32; } #elif defined(USE_EXTERNAL_MZCRC) /* If USE_EXTERNAL_CRC is defined, an external module will export the * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version. * Depending on the impl, it may be necessary to ~ the input/output crc values. */ mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len); #else /* Faster, but larger CPU cache footprint. */ mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { static const mz_uint32 s_crc_table[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }; mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; while (buf_len >= 4) { crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; pByte_buf += 4; buf_len -= 4; } while (buf_len) { crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; ++pByte_buf; --buf_len; } return ~crc32; } #endif void mz_free(void *p) { MZ_FREE(p); } MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } const char *mz_version(void) { return MZ_VERSION; } #ifndef MINIZ_NO_ZLIB_APIS #ifndef MINIZ_NO_DEFLATE_APIS int mz_deflateInit(mz_streamp pStream, int level) { return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); } int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) { tdefl_compressor *pComp; mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); if (!pStream) return MZ_STREAM_ERROR; if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; pStream->data_type = 0; pStream->adler = MZ_ADLER32_INIT; pStream->msg = NULL; pStream->reserved = 0; pStream->total_in = 0; pStream->total_out = 0; if (!pStream->zalloc) pStream->zalloc = miniz_def_alloc_func; if (!pStream->zfree) pStream->zfree = miniz_def_free_func; pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); if (!pComp) return MZ_MEM_ERROR; pStream->state = (struct mz_internal_state *)pComp; if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { mz_deflateEnd(pStream); return MZ_PARAM_ERROR; } return MZ_OK; } int mz_deflateReset(mz_streamp pStream) { if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; pStream->total_in = pStream->total_out = 0; tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); return MZ_OK; } int mz_deflate(mz_streamp pStream, int flush) { size_t in_bytes, out_bytes; mz_ulong orig_total_in, orig_total_out; int mz_status = MZ_OK; if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; if (!pStream->avail_out) return MZ_BUF_ERROR; if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; for (;;) { tdefl_status defl_status; in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; if (defl_status < 0) { mz_status = MZ_STREAM_ERROR; break; } else if (defl_status == TDEFL_STATUS_DONE) { mz_status = MZ_STREAM_END; break; } else if (!pStream->avail_out) break; else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) break; return MZ_BUF_ERROR; /* Can't make forward progress without some input. */ } } return mz_status; } int mz_deflateEnd(mz_streamp pStream) { if (!pStream) return MZ_STREAM_ERROR; if (pStream->state) { pStream->zfree(pStream->opaque, pStream->state); pStream->state = NULL; } return MZ_OK; } mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { (void)pStream; /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); } int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) { int status; mz_stream stream; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ if ((mz_uint64)(source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_deflateInit(&stream, level); if (status != MZ_OK) return status; status = mz_deflate(&stream, MZ_FINISH); if (status != MZ_STREAM_END) { mz_deflateEnd(&stream); return (status == MZ_OK) ? MZ_BUF_ERROR : status; } *pDest_len = stream.total_out; return mz_deflateEnd(&stream); } int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); } mz_ulong mz_compressBound(mz_ulong source_len) { return mz_deflateBound(NULL, source_len); } #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #ifndef MINIZ_NO_INFLATE_APIS typedef struct { tinfl_decompressor m_decomp; 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; 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 = miniz_def_alloc_func; if (!pStream->zfree) pStream->zfree = miniz_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; } int mz_inflateInit(mz_streamp pStream) { return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); } int mz_inflateReset(mz_streamp pStream) { inflate_state *pDecomp; if (!pStream) return MZ_STREAM_ERROR; pStream->data_type = 0; pStream->adler = 0; pStream->msg = NULL; pStream->total_in = 0; pStream->total_out = 0; pStream->reserved = 0; pDecomp = (inflate_state *)pStream->state; 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; } 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; } 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; } int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len) { mz_stream stream; int status; memset(&stream, 0, sizeof(stream)); /* In case mz_ulong is 64-bits (argh I hate longs). */ if ((mz_uint64)(*pSource_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; stream.next_in = pSource; stream.avail_in = (mz_uint32)*pSource_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; status = mz_inflateInit(&stream); if (status != MZ_OK) return status; status = mz_inflate(&stream, MZ_FINISH); *pSource_len = *pSource_len - stream.avail_in; if (status != MZ_STREAM_END) { mz_inflateEnd(&stream); return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; } *pDest_len = stream.total_out; return mz_inflateEnd(&stream); } int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { return mz_uncompress2(pDest, pDest_len, pSource, &source_len); } #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ const char *mz_error(int err) { static struct { int m_err; const char *m_pDesc; } s_error_descs[] = { { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } }; mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; return NULL; } #endif /*MINIZ_NO_ZLIB_APIS */ #ifdef __cplusplus } #endif /* 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 */ /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * 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 * 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. * **************************************************************************/ #ifndef MINIZ_NO_DEFLATE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Compression (independent from all decompression API's) */ /* Purposely making these tables static for faster init and thread safety. */ static const mz_uint16 s_tdefl_len_sym[256] = { 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285 }; static const mz_uint8 s_tdefl_len_extra[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0 }; static const mz_uint8 s_tdefl_small_dist_sym[512] = { 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 }; static const mz_uint8 s_tdefl_small_dist_extra[512] = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }; static const mz_uint8 s_tdefl_large_dist_sym[128] = { 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 }; static const mz_uint8 s_tdefl_large_dist_extra[128] = { 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 }; /* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) { mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_ARR(hist); for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { const mz_uint32 *pHist = &hist[pass << 8]; mz_uint offsets[256], cur_ofs = 0; for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; { tdefl_sym_freq *t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } } return pCur_syms; } /* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) { int root, leaf, next, avbl, used, dpth; if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; } A[0].m_key += A[1].m_key; root = 0; leaf = 2; for (next = 1; next < n - 1; next++) { if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (mz_uint16)next; } else A[next].m_key = A[leaf++].m_key; if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); A[root++].m_key = (mz_uint16)next; } else A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); } A[n - 2].m_key = 0; for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1; avbl = 1; used = dpth = 0; root = n - 2; next = n - 1; while (avbl > 0) { while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; } while (avbl > used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } avbl = 2 * used; dpth++; used = 0; } } /* Limits canonical Huffman code table's max code size. */ enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) { int i; mz_uint32 total = 0; if (code_list_len <= 1) return; for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); while (total != (1UL << max_code_size)) { pNum_codes[max_code_size]--; for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } total--; } } static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) { int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_ARR(num_codes); if (static_table) { for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; } else { tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; int num_used_syms = 0; const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); MZ_CLEAR_ARR(d->m_huff_code_sizes[table_num]); MZ_CLEAR_ARR(d->m_huff_codes[table_num]); for (i = 1, j = num_used_syms; i <= code_size_limit; i++) for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); } next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); for (i = 0; i < table_len; i++) { mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; } } #define TDEFL_PUT_BITS(b, l) \ do \ { \ mz_uint bits = b; \ mz_uint len = l; \ MZ_ASSERT(bits <= ((1U << len) - 1U)); \ d->m_bit_buffer |= (bits << d->m_bits_in); \ d->m_bits_in += len; \ while (d->m_bits_in >= 8) \ { \ if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ d->m_bit_buffer >>= 8; \ d->m_bits_in -= 8; \ } \ } \ MZ_MACRO_END #define TDEFL_RLE_PREV_CODE_SIZE() \ { \ if (rle_repeat_count) \ { \ if (rle_repeat_count < 3) \ { \ d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ while (rle_repeat_count--) \ packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ } \ else \ { \ d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 16; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ } \ rle_repeat_count = 0; \ } \ } #define TDEFL_RLE_ZERO_CODE_SIZE() \ { \ if (rle_z_count) \ { \ if (rle_z_count < 3) \ { \ d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ while (rle_z_count--) \ packed_code_sizes[num_packed_code_sizes++] = 0; \ } \ else if (rle_z_count <= 10) \ { \ d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 17; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ } \ else \ { \ d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ packed_code_sizes[num_packed_code_sizes++] = 18; \ packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ } \ rle_z_count = 0; \ } \ } static const mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static void tdefl_start_dynamic_block(tdefl_compressor *d) { int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; d->m_huff_count[0][256] = 1; tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); for (i = 0; i < total_code_sizes_to_pack; i++) { mz_uint8 code_size = code_sizes_to_pack[i]; if (!code_size) { TDEFL_RLE_PREV_CODE_SIZE(); if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } } else { TDEFL_RLE_ZERO_CODE_SIZE(); if (code_size != prev_code_size) { TDEFL_RLE_PREV_CODE_SIZE(); d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; } else if (++rle_repeat_count == 6) { TDEFL_RLE_PREV_CODE_SIZE(); } } prev_code_size = code_size; } if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); TDEFL_PUT_BITS(2, 2); TDEFL_PUT_BITS(num_lit_codes - 257, 5); TDEFL_PUT_BITS(num_dist_codes - 1, 5); for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) { mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); } } static void tdefl_start_static_block(tdefl_compressor *d) { mz_uint i; mz_uint8 *p = &d->m_huff_code_sizes[0][0]; 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; memset(d->m_huff_code_sizes[1], 5, 32); tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); TDEFL_PUT_BITS(1, 2); } static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; mz_uint8 *pOutput_buf = d->m_pOutput_buf; mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; mz_uint64 bit_buffer = d->m_bit_buffer; mz_uint bits_in = d->m_bits_in; #define TDEFL_PUT_BITS_FAST(b, l) \ { \ bit_buffer |= (((mz_uint64)(b)) << bits_in); \ bits_in += (l); \ } flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint s0, s1, n0, n1, sym, num_extra_bits; mz_uint match_len = pLZ_codes[0]; mz_uint match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ s0 = s_tdefl_small_dist_sym[match_dist & 511]; n0 = s_tdefl_small_dist_extra[match_dist & 511]; s1 = s_tdefl_large_dist_sym[match_dist >> 8]; n1 = s_tdefl_large_dist_extra[match_dist >> 8]; sym = (match_dist < 512) ? s0 : s1; num_extra_bits = (match_dist < 512) ? n0 : n1; MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { flags >>= 1; lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } } if (pOutput_buf >= d->m_pOutput_buf_end) return MZ_FALSE; memcpy(pOutput_buf, &bit_buffer, sizeof(mz_uint64)); pOutput_buf += (bits_in >> 3); bit_buffer >>= (bits_in & ~7); bits_in &= 7; } #undef TDEFL_PUT_BITS_FAST d->m_pOutput_buf = pOutput_buf; d->m_bits_in = 0; d->m_bit_buffer = 0; while (bits_in) { mz_uint32 n = MZ_MIN(bits_in, 16); TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); bit_buffer >>= n; bits_in -= n; } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #else static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { mz_uint flags; mz_uint8 *pLZ_codes; flags = 1; for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) { if (flags == 1) flags = *pLZ_codes++ | 0x100; if (flags & 1) { mz_uint sym, num_extra_bits; mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); if (match_dist < 512) { sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; } else { sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; } MZ_ASSERT(d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); } else { mz_uint lit = *pLZ_codes++; MZ_ASSERT(d->m_huff_code_sizes[0][lit]); TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); } } TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); return (d->m_pOutput_buf < d->m_pOutput_buf_end); } #endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) { if (static_block) tdefl_start_static_block(d); else tdefl_start_dynamic_block(d); return tdefl_compress_lz_codes(d); } static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; static int tdefl_flush_block(tdefl_compressor *d, int flush) { mz_uint saved_bit_buf, saved_bits_in; mz_uint8 *pSaved_output_buf; mz_bool comp_block_succeeded = MZ_FALSE; int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; d->m_pOutput_buf = pOutput_buf_start; d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; MZ_ASSERT(!d->m_output_flush_remaining); d->m_output_flush_ofs = 0; d->m_output_flush_remaining = 0; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { const mz_uint8 cmf = 0x78; mz_uint8 flg, flevel = 3; mz_uint header, i, mz_un = sizeof(s_tdefl_num_probes) / sizeof(mz_uint); /* Determine compression level by reversing the process in tdefl_create_comp_flags_from_zip_params() */ for (i = 0; i < mz_un; i++) if (s_tdefl_num_probes[i] == (d->m_flags & 0xFFF)) break; if (i < 2) flevel = 0; else if (i < 6) flevel = 1; else if (i == 6) flevel = 2; header = cmf << 8 | (flevel << 6); header += 31 - (header % 31); flg = header & 0xFF; TDEFL_PUT_BITS(cmf, 8); TDEFL_PUT_BITS(flg, 8); } TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; if (!use_raw_block) comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) { mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; TDEFL_PUT_BITS(0, 2); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); } for (i = 0; i < d->m_total_lz_bytes; ++i) { TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); } } /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ else if (!comp_block_succeeded) { d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; tdefl_compress_block(d, MZ_TRUE); } if (flush) { if (flush == TDEFL_FINISH) { if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } } else { mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } } } MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { if (d->m_pPut_buf_func) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); } else if (pOutput_buf_start == d->m_output_buf) { int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); d->m_out_buf_ofs += bytes_to_copy; if ((n -= bytes_to_copy) != 0) { d->m_output_flush_ofs = bytes_to_copy; d->m_output_flush_remaining = n; } } else { d->m_out_buf_ofs += n; } } return d->m_output_flush_remaining; } #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES #ifdef MINIZ_UNALIGNED_USE_MEMCPY static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8 *p) { mz_uint16 ret; memcpy(&ret, p, sizeof(mz_uint16)); return ret; } static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16 *p) { mz_uint16 ret; memcpy(&ret, p, sizeof(mz_uint16)); return ret; } #else #define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) #define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) #endif static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for (;;) { for (;;) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; q = (const mz_uint16 *)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) continue; p = s; probe_len = 32; do { } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); if (!probe_len) { *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); break; } else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); } } } #else static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) { mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; const mz_uint8 *s = d->m_dict + pos, *p, *q; mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; for (;;) { for (;;) { if (--num_probes_left == 0) return; #define TDEFL_PROBE \ next_probe_pos = d->m_next[probe_pos]; \ if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ return; \ probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ break; TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; } if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; if (probe_len > match_len) { *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; } } } #endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #ifdef MINIZ_UNALIGNED_USE_MEMCPY static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8 *p) { mz_uint32 ret; memcpy(&ret, p, sizeof(mz_uint32)); return ret; } #else #define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) #endif static mz_bool tdefl_compress_fast(tdefl_compressor *d) { /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); d->m_src_buf_left -= num_bytes_to_process; lookahead_size += num_bytes_to_process; while (num_bytes_to_process) { mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); memcpy(d->m_dict + dst_pos, d->m_pSrc, n); if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); d->m_pSrc += n; dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; num_bytes_to_process -= n; } dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; while (lookahead_size >= 4) { mz_uint cur_match_dist, cur_match_len = 1; mz_uint8 *pCur_dict = d->m_dict + cur_pos; mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; mz_uint probe_pos = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)lookahead_pos; if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) { const mz_uint16 *p = (const mz_uint16 *)pCur_dict; const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); mz_uint32 probe_len = 32; do { } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); if (!probe_len) cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) { cur_match_len = 1; *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } else { mz_uint32 s0, s1; cur_match_len = MZ_MIN(cur_match_len, lookahead_size); MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); cur_match_dist--; pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); #ifdef MINIZ_UNALIGNED_USE_MEMCPY memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); #else *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; #endif pLZ_code_buf += 3; *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; } } else { *pLZ_code_buf++ = (mz_uint8)first_trigram; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); d->m_huff_count[0][(mz_uint8)first_trigram]++; } if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } total_lz_bytes += cur_match_len; lookahead_pos += cur_match_len; dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; MZ_ASSERT(lookahead_size >= cur_match_len); lookahead_size -= cur_match_len; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } while (lookahead_size) { mz_uint8 lit = d->m_dict[cur_pos]; total_lz_bytes++; *pLZ_code_buf++ = lit; *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } d->m_huff_count[0][lit]++; lookahead_pos++; dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; lookahead_size--; if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { int n; d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; } } } d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; return MZ_TRUE; } #endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) { d->m_total_lz_bytes++; *d->m_pLZ_code_buf++ = lit; *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } d->m_huff_count[0][lit]++; } static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) { mz_uint32 s0, s1; MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); d->m_total_lz_bytes += match_len; d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); match_dist -= 1; d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; } static mz_bool tdefl_compress_normal(tdefl_compressor *d) { const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; tdefl_flush flush = d->m_flush; while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); const mz_uint8 *pSrc_end = pSrc ? pSrc + num_bytes_to_process : NULL; src_buf_left -= num_bytes_to_process; d->m_lookahead_size += num_bytes_to_process; while (pSrc != pSrc_end) { mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; } } else { while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { mz_uint8 c = *pSrc++; mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; src_buf_left--; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); } } } d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) break; /* Simple lazy/greedy parsing state machine. */ len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; } } else { tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); } if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { cur_match_dist = cur_match_len = 0; } if (d->m_saved_match_len) { if (cur_match_len > d->m_saved_match_len) { tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); if (cur_match_len >= 128) { tdefl_record_match(d, cur_match_len, cur_match_dist); d->m_saved_match_len = 0; len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } } else { tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; } } else if (!cur_match_dist) tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) { tdefl_record_match(d, cur_match_len, cur_match_dist); len_to_move = cur_match_len; } else { d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; } /* Move the lookahead forward by len_to_move bytes. */ d->m_lookahead_pos += len_to_move; MZ_ASSERT(d->m_lookahead_size >= len_to_move); d->m_lookahead_size -= len_to_move; d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); /* Check if it's time to flush the current LZ codes to the internal output buffer. */ if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) { int n; d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; if ((n = tdefl_flush_block(d, 0)) != 0) return (n < 0) ? MZ_FALSE : MZ_TRUE; } } d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; return MZ_TRUE; } static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) { if (d->m_pIn_buf_size) { *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; } if (d->m_pOut_buf_size) { size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); d->m_output_flush_ofs += (mz_uint)n; d->m_output_flush_remaining -= (mz_uint)n; d->m_out_buf_ofs += n; *d->m_pOut_buf_size = d->m_out_buf_ofs; } return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; } tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) { if (!d) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return TDEFL_STATUS_BAD_PARAM; } d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; d->m_out_buf_ofs = 0; d->m_flush = flush; if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) { if (pIn_buf_size) *pIn_buf_size = 0; if (pOut_buf_size) *pOut_buf_size = 0; return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); } d->m_wants_to_finish |= (flush == TDEFL_FINISH); if ((d->m_output_flush_remaining) || (d->m_finished)) return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) { if (!tdefl_compress_fast(d)) return d->m_prev_return_status; } else #endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ { if (!tdefl_compress_normal(d)) return d->m_prev_return_status; } if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) { if (tdefl_flush_block(d, flush) < 0) return d->m_prev_return_status; d->m_finished = (flush == TDEFL_FINISH); if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_ARR(d->m_hash); MZ_CLEAR_ARR(d->m_next); d->m_dict_size = 0; } } return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); } tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) { MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); } tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_ARR(d->m_hash); d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; *d->m_pLZ_flags = 0; d->m_num_flags_left = 8; d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_ARR(d->m_dict); memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); return TDEFL_STATUS_OKAY; } tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) { return d->m_prev_return_status; } mz_uint32 tdefl_get_adler32(tdefl_compressor *d) { return d->m_adler32; } mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); MZ_FREE(pComp); return succeeded; } typedef struct { size_t m_size, m_capacity; mz_uint8 *m_pBuf; mz_bool m_expandable; } tdefl_output_buffer; static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) { tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; size_t new_size = p->m_size + len; if (new_size > p->m_capacity) { size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; } memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; return MZ_TRUE; } void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; out_buf.m_expandable = MZ_TRUE; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; *pOut_len = out_buf.m_size; return out_buf.m_pBuf; } size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); if (!pOut_buf) return 0; out_buf.m_pBuf = (mz_uint8 *)pOut_buf; out_buf.m_capacity = out_buf_len; if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; return out_buf.m_size; } /* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) { mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; return comp_flags; } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ #endif /* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) { /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; if (!pComp) return NULL; if (w <= 0 || h <= 0 || w > 0xFFFF || h > 0xFFFF || num_chans < 1 || num_chans > 4) { MZ_FREE(pComp); return NULL; } MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } /* write dummy header */ for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); /* compress image data */ tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } /* write real header */ *pLen_out = out_buf.m_size - 41; { static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x44, 0x41, 0x54 }; pnghdr[18] = (mz_uint8)(w >> 8); pnghdr[19] = (mz_uint8)w; pnghdr[22] = (mz_uint8)(h >> 8); pnghdr[23] = (mz_uint8)h; pnghdr[25] = chans[num_chans]; pnghdr[33] = (mz_uint8)(*pLen_out >> 24); pnghdr[34] = (mz_uint8)(*pLen_out >> 16); pnghdr[35] = (mz_uint8)(*pLen_out >> 8); pnghdr[36] = (mz_uint8)*pLen_out; c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); for (i = 0; i < 4; ++i, c <<= 8) ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); memcpy(out_buf.m_pBuf, pnghdr, 41); } /* write footer (IDAT CRC-32, followed by IEND chunk) */ if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); for (i = 0; i < 4; ++i, c <<= 8) (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); /* compute final size of file, grab compressed data buffer and return */ *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; } void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) { /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); } #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ /* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ tdefl_compressor *tdefl_compressor_alloc(void) { return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); } void tdefl_compressor_free(tdefl_compressor *pComp) { MZ_FREE(pComp); } #endif #ifdef _MSC_VER #pragma warning(pop) #endif #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * 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 * 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. * **************************************************************************/ #ifndef MINIZ_NO_INFLATE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Decompression (completely independent from all compression API's) */ #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 } #define TINFL_GET_BYTE(state_index, c) \ do \ { \ while (pIn_buf_cur >= pIn_buf_end) \ { \ TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ } \ 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, pLookUp, pTree) \ do \ { \ temp = pLookUp[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 = pTree[~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. */ /* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ /* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ #define TINFL_HUFF_DECODE(state_index, sym, pLookUp, pTree) \ 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, pLookUp, pTree); \ } \ 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 = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ code_len = temp >> 9, temp &= 511; \ else \ { \ code_len = TINFL_FAST_LOOKUP_BITS; \ do \ { \ temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ } while (temp < 0); \ } \ sym = temp; \ bit_buf >>= code_len; \ num_bits -= code_len; \ } \ MZ_MACRO_END static void tinfl_clear_tree(tinfl_decompressor *r) { if (r->m_type == 0) MZ_CLEAR_ARR(r->m_tree_0); else if (r->m_type == 1) MZ_CLEAR_ARR(r->m_tree_1); else MZ_CLEAR_ARR(r->m_tree_2); } tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) { static const mz_uint16 s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; static const mz_uint8 s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; static const mz_uint16 s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; static const mz_uint8 s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static const mz_uint16 s_min_table_sizes[3] = { 257, 1, 4 }; mz_int16 *pTrees[3]; mz_uint8 *pCode_sizes[3]; tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next ? pOut_buf_next + *pOut_buf_size : NULL; size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ 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; } pTrees[0] = r->m_tree_0; pTrees[1] = r->m_tree_1; pTrees[2] = r->m_tree_2; pCode_sizes[0] = r->m_code_size_0; pCode_sizes[1] = r->m_code_size_1; pCode_sizes[2] = r->m_code_size_2; num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; 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)((size_t)1 << (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) { TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); } 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_code_size_0; mz_uint i; r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_code_size_1, 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_code_size_2); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_code_size_2[s_length_dezigzag[counter]] = (mz_uint8)s; } r->m_table_sizes[2] = 19; } for (; (int)r->m_type >= 0; r->m_type--) { int tree_next, tree_cur; mz_int16 *pLookUp; mz_int16 *pTree; mz_uint8 *pCode_size; mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pLookUp = r->m_look_up[r->m_type]; pTree = pTrees[r->m_type]; pCode_size = pCode_sizes[r->m_type]; MZ_CLEAR_ARR(total_syms); TINFL_MEMSET(pLookUp, 0, sizeof(r->m_look_up[0])); tinfl_clear_tree(r); for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pCode_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 = pCode_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) { pLookUp[rev_code] = k; rev_code += (1 << code_size); } continue; } if (0 == (tree_cur = pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pLookUp[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 (!pTree[-tree_cur - 1]) { pTree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTree[-tree_cur - 1]; } tree_cur -= ((rev_code >>= 1) & 1); pTree[-tree_cur - 1] = (mz_int16)sym_index; } if (r->m_type == 2) { for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { mz_uint s; TINFL_HUFF_DECODE(16, dist, r->m_look_up[2], r->m_tree_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_code_size_0, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_code_size_1, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); } } for (;;) { 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_look_up[0], r->m_tree_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_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } counter = sym2; bit_buf >>= code_len; num_bits -= code_len; if (code_len == 0) { TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); } 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_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) code_len = sym2 >> 9; else { code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); } bit_buf >>= code_len; num_bits -= code_len; if (code_len == 0) { TINFL_CR_RETURN_FOREVER(54, TINFL_STATUS_FAILED); } 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_look_up[1], r->m_tree_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 == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); } 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 { #ifdef MINIZ_UNALIGNED_USE_MEMCPY memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32) * 2); #else ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; #endif 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 while (counter > 2) { pOut_buf_cur[0] = pSrc[0]; pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur[2] = pSrc[2]; pOut_buf_cur += 3; pSrc += 3; counter -= 3; } if (counter > 0) { pOut_buf_cur[0] = pSrc[0]; if (counter > 1) pOut_buf_cur[1] = pSrc[1]; pOut_buf_cur += counter; } } } } while (!(r->m_final & 1)); /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ TINFL_SKIP_BITS(32, num_bits & 7); while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) { --pIn_buf_cur; num_bits -= 8; } bit_buf &= ~(~(tinfl_bit_buf_t)0 << num_bits); MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { 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: /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) { while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) { --pIn_buf_cur; num_bits -= 8; } } r->m_num_bits = num_bits; r->m_bit_buf = bit_buf & ~(~(tinfl_bit_buf_t)0 << num_bits); r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; 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; } /* Higher level helper functions. */ void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) { tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; *pOut_len = 0; tinfl_init(&decomp); for (;;) { size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } src_buf_ofs += src_buf_size; *pOut_len += dst_buf_size; if (status == TINFL_STATUS_DONE) break; new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); if (!pNew_buf) { MZ_FREE(pBuf); *pOut_len = 0; return NULL; } pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; } return pBuf; } size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) { tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; } int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) { int result = 0; tinfl_decompressor decomp; mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; if (!pDict) return TINFL_STATUS_FAILED; memset(pDict, 0, TINFL_LZ_DICT_SIZE); tinfl_init(&decomp); for (;;) { size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); in_buf_ofs += in_buf_size; if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) break; if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { result = (status == TINFL_STATUS_DONE); break; } dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); } MZ_FREE(pDict); *pIn_buf_size = in_buf_ofs; return result; } #ifndef MINIZ_NO_MALLOC tinfl_decompressor *tinfl_decompressor_alloc(void) { tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); if (pDecomp) tinfl_init(pDecomp); return pDecomp; } void tinfl_decompressor_free(tinfl_decompressor *pDecomp) { MZ_FREE(pDecomp); } #endif #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC * Copyright 2016 Martin Raiber * 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 * 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. * **************************************************************************/ #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- .ZIP archive reading */ #ifdef MINIZ_NO_STDIO #define MZ_FILE void * #else #include #if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef __cplusplus #define MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS 0 #endif #ifndef NOMINMAX #define NOMINMAX #endif #include static WCHAR *mz_utf8z_to_widechar(const char *str) { int reqChars = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); WCHAR *wStr = (WCHAR *)malloc(reqChars * sizeof(WCHAR)); MultiByteToWideChar(CP_UTF8, 0, str, -1, wStr, reqChars); return wStr; } static FILE *mz_fopen(const char *pFilename, const char *pMode) { WCHAR *wFilename = mz_utf8z_to_widechar(pFilename); WCHAR *wMode = mz_utf8z_to_widechar(pMode); FILE *pFile = NULL; errno_t err = _wfopen_s(&pFile, wFilename, wMode); free(wFilename); free(wMode); return err ? NULL : pFile; } static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { WCHAR *wPath = mz_utf8z_to_widechar(pPath); WCHAR *wMode = mz_utf8z_to_widechar(pMode); FILE *pFile = NULL; errno_t err = _wfreopen_s(&pFile, wPath, wMode, pStream); free(wPath); free(wMode); return err ? NULL : pFile; } #if defined(__MINGW32__) static int mz_stat(const char *path, struct _stat *buffer) { WCHAR *wPath = mz_utf8z_to_widechar(path); int res = _wstat(wPath, buffer); free(wPath); return res; } #else static int mz_stat64(const char *path, struct __stat64 *buffer) { WCHAR *wPath = mz_utf8z_to_widechar(path); int res = _wstat64(wPath, buffer); free(wPath); return res; } #endif #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN mz_fopen #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 #if defined(__MINGW32__) #define MZ_FILE_STAT_STRUCT _stat #define MZ_FILE_STAT mz_stat #else #define MZ_FILE_STAT_STRUCT _stat64 #define MZ_FILE_STAT mz_stat64 #endif #define MZ_FFLUSH fflush #define MZ_FREOPEN mz_freopen #define MZ_DELETE_FILE remove #elif defined(__WATCOMC__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__TINYC__) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftell #define MZ_FSEEK64 fseek #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #elif defined(__USE_LARGEFILE64) /* gcc, clang */ #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen64(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello64 #define MZ_FSEEK64 fseeko64 #define MZ_FILE_STAT_STRUCT stat64 #define MZ_FILE_STAT stat64 #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) #define MZ_DELETE_FILE remove #elif defined(__APPLE__) || defined(__FreeBSD__) || (defined(__linux__) && defined(__x86_64__)) #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #define MZ_FTELL64 ftello #define MZ_FSEEK64 fseeko #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(p, m, s) freopen(p, m, s) #define MZ_DELETE_FILE remove #else #pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") #ifndef MINIZ_NO_TIME #include #endif #define MZ_FOPEN(f, m) fopen(f, m) #define MZ_FCLOSE fclose #define MZ_FREAD fread #define MZ_FWRITE fwrite #ifdef __STRICT_ANSI__ #define MZ_FTELL64 ftell #define MZ_FSEEK64 fseek #else #define MZ_FTELL64 ftello #define MZ_FSEEK64 fseeko #endif #define MZ_FILE_STAT_STRUCT stat #define MZ_FILE_STAT stat #define MZ_FFLUSH fflush #define MZ_FREOPEN(f, m, s) freopen(f, m, s) #define MZ_DELETE_FILE remove #endif /* #ifdef _MSC_VER */ #endif /* #ifdef MINIZ_NO_STDIO */ #define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) /* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ enum { /* ZIP archive identifiers and record sizes */ MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, /* ZIP64 archive identifier and record sizes */ MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, /* Central directory header record offsets */ MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, /* Local directory header offsets */ MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, /* End of central directory offsets */ MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, /* ZIP64 End of central directory locator offsets */ MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ /* ZIP64 End of central directory header offsets */ MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 }; typedef struct { void *m_p; size_t m_size, m_capacity; mz_uint m_element_size; } mz_zip_array; struct mz_zip_internal_state_tag { mz_zip_array m_central_dir; mz_zip_array m_central_dir_offsets; mz_zip_array m_sorted_central_dir_offsets; /* The flags passed in when the archive is initially opened. */ mz_uint32 m_init_flags; /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ mz_bool m_zip64; /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ mz_bool m_zip64_has_extended_info_fields; /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ MZ_FILE *m_pFile; mz_uint64 m_file_archive_start_ofs; void *m_pMem; size_t m_mem_size; size_t m_mem_capacity; }; #define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size #if defined(DEBUG) || defined(_DEBUG) static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) { MZ_ASSERT(index < pArray->m_size); return index; } #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] #else #define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] #endif static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) { memset(pArray, 0, sizeof(mz_zip_array)); pArray->m_element_size = element_size; } static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) { pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); memset(pArray, 0, sizeof(mz_zip_array)); } static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) { void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) { if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) { if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } pArray->m_size = new_size; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) { return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); } static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) { size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; if (n > 0) memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); return MZ_TRUE; } #ifndef MINIZ_NO_TIME static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; return mktime(&tm); } #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) { #ifdef _MSC_VER struct tm tm_struct; struct tm *tm = &tm_struct; errno_t err = localtime_s(tm, &time); if (err) { *pDOS_date = 0; *pDOS_time = 0; return; } #else struct tm *tm = localtime(&time); #endif /* #ifdef _MSC_VER */ *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); } #endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ #ifndef MINIZ_NO_STDIO #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) { struct MZ_FILE_STAT_STRUCT file_stat; /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ if (MZ_FILE_STAT(pFilename, &file_stat) != 0) return MZ_FALSE; *pTime = file_stat.st_mtime; return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) { struct utimbuf t; memset(&t, 0, sizeof(t)); t.actime = access_time; t.modtime = modified_time; return !utime(pFilename, &t); } #endif /* #ifndef MINIZ_NO_STDIO */ #endif /* #ifndef MINIZ_NO_TIME */ static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) { if (pZip) pZip->m_last_error = err_num; return MZ_FALSE; } static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) { (void)flags; if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!pZip->m_pAlloc) pZip->m_pAlloc = miniz_def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = miniz_def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = miniz_def_realloc_func; pZip->m_archive_size = 0; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; pZip->m_last_error = MZ_ZIP_NO_ERROR; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); pZip->m_pState->m_init_flags = flags; pZip->m_pState->m_zip64 = MZ_FALSE; pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; pZip->m_zip_mode = MZ_ZIP_MODE_READING; return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (l_len < r_len) : (l < r); } #define MZ_SWAP_UINT32(a, b) \ do \ { \ mz_uint32 t = a; \ a = b; \ b = t; \ } \ MZ_MACRO_END /* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices; mz_uint32 start, end; const mz_uint32 size = pZip->m_total_files; if (size <= 1U) return; pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); start = (size - 2U) >> 1U; for (;;) { mz_uint64 child, root = start; for (;;) { if ((child = (root << 1U) + 1U) >= size) break; child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } if (!start) break; start--; } end = size - 1; while (end > 0) { mz_uint64 child, root = 0; MZ_SWAP_UINT32(pIndices[end], pIndices[0]); for (;;) { if ((child = (root << 1U) + 1U) >= end) break; child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) break; MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; } end--; } } static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) { mz_int64 cur_file_ofs; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; /* Basic sanity checks - reject files which are too small */ if (pZip->m_archive_size < record_size) return MZ_FALSE; /* Find the record by scanning the file from the end towards the beginning. */ cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); for (;;) { int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) return MZ_FALSE; for (i = n - 4; i >= 0; --i) { mz_uint s = MZ_READ_LE32(pBuf + i); if (s == record_sig) { if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) break; } } if (i >= 0) { cur_file_ofs += i; break; } /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= ((mz_uint64)(MZ_UINT16_MAX) + record_size))) return MZ_FALSE; cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); } *pOfs = cur_file_ofs; return MZ_TRUE; } static mz_bool mz_zip_reader_eocd64_valid(mz_zip_archive *pZip, uint64_t offset, uint8_t *buf) { if (pZip->m_pRead(pZip->m_pIO_opaque, offset, buf, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { if (MZ_READ_LE32(buf + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { return MZ_TRUE; } } return MZ_FALSE; } static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) { mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; mz_uint64 cdir_ofs = 0, eocd_ofs = 0, archive_ofs = 0; mz_int64 cur_file_ofs = 0; const mz_uint8 *p; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; mz_uint64 zip64_end_of_central_dir_ofs = 0; /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); eocd_ofs = cur_file_ofs; /* Read and verify the end of central directory record. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) { if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { pZip->m_pState->m_zip64 = MZ_TRUE; } } } if (pZip->m_pState->m_zip64) { /* Try locating the EOCD64 right before the EOCD64 locator. This works even * when the effective start of the zip header is not yet known. */ if (cur_file_ofs < MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); zip64_end_of_central_dir_ofs = cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; if (!mz_zip_reader_eocd64_valid(pZip, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir)) { /* That failed, try reading where the locator tells us to. */ zip64_end_of_central_dir_ofs = MZ_READ_LE64( pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (!mz_zip_reader_eocd64_valid(pZip, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir)) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); } } pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); if (pZip->m_pState->m_zip64) { mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (zip64_total_num_of_disks != 1U) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); /* Check for miniz's practical limits */ if (zip64_cdir_total_entries > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ if (zip64_size_of_central_directory > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); cdir_size = (mz_uint32)zip64_size_of_central_directory; num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); } if (pZip->m_total_files != cdir_entries_on_this_disk) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (cdir_size < (mz_uint64)pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (cdir_size> pZip->m_archive_size || cdir_ofs > pZip->m_archive_size - cdir_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (eocd_ofs < cdir_ofs + cdir_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); /* The end of central dir follows the central dir, unless the zip file has * some trailing data (e.g. it is appended to an executable file). */ archive_ofs = eocd_ofs - (cdir_ofs + cdir_size); if (pZip->m_pState->m_zip64) { if (archive_ofs < MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); archive_ofs -= MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; } /* Update the archive start position, but only if not specified. */ if ((pZip->m_zip_type == MZ_ZIP_TYPE_FILE || pZip->m_zip_type == MZ_ZIP_TYPE_CFILE || pZip->m_zip_type == MZ_ZIP_TYPE_USER) && pZip->m_pState->m_file_archive_start_ofs == 0) { pZip->m_pState->m_file_archive_start_ofs = archive_ofs; pZip->m_archive_size -= archive_ofs; } pZip->m_central_directory_file_ofs = cdir_ofs; if (pZip->m_total_files) { mz_uint i, n; /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (sort_central_dir) { if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); /* Now create an index into the central directory file records, do some basic sanity checking on each record */ p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; mz_uint64 comp_size, decomp_size, local_header_ofs; if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); if (sort_central_dir) MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && (ext_data_size) && (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) { /* Attempt to find zip64 extended information field in the entry's extra data */ mz_uint32 extra_size_remaining = ext_data_size; if (extra_size_remaining) { const mz_uint8 *pExtra_data; void *buf = NULL; if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) { buf = MZ_MALLOC(ext_data_size); if (buf == NULL) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } pExtra_data = (mz_uint8 *)buf; } else { pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; } do { mz_uint32 field_id; mz_uint32 field_data_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) { MZ_FREE(buf); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ pZip->m_pState->m_zip64 = MZ_TRUE; pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; break; } pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; } while (extra_size_remaining); MZ_FREE(buf); } } /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) { if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); if (comp_size != MZ_UINT32_MAX) { if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); n -= total_header_size; p += total_header_size; } } if (sort_central_dir) mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); return MZ_TRUE; } void mz_zip_zero_struct(mz_zip_archive *pZip) { if (pZip) MZ_CLEAR_PTR(pZip); } static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) { mz_bool status = MZ_TRUE; if (!pZip) return MZ_FALSE; if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) { if (set_last_error) pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } if (pZip->m_pState) { mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { if (MZ_FCLOSE(pState->m_pFile) == EOF) { if (set_last_error) pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; status = MZ_FALSE; } } pState->m_pFile = NULL; } #endif /* #ifndef MINIZ_NO_STDIO */ pZip->m_pFree(pZip->m_pAlloc_opaque, pState); } pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return status; } mz_bool mz_zip_reader_end(mz_zip_archive *pZip) { return mz_zip_reader_end_internal(pZip, MZ_TRUE); } mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) { if ((!pZip) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_USER; pZip->m_archive_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); return s; } mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) { if (!pMem) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; pZip->m_archive_size = size; pZip->m_pRead = mz_zip_mem_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pNeeds_keepalive = NULL; #ifdef __cplusplus pZip->m_pState->m_pMem = const_cast(pMem); #else pZip->m_pState->m_pMem = (void *)pMem; #endif pZip->m_pState->m_mem_size = size; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); file_ofs += pZip->m_pState->m_file_archive_start_ofs; if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) { return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); } mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) { mz_uint64 file_size; MZ_FILE *pFile; if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_READ_ALLOW_WRITING ) ? "r+b" : "rb"); if (!pFile) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); file_size = archive_size; if (!file_size) { if (MZ_FSEEK64(pFile, 0, SEEK_END)) { MZ_FCLOSE(pFile); return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); } file_size = MZ_FTELL64(pFile); } /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) { MZ_FCLOSE(pFile); return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); } if (!mz_zip_reader_init_internal(pZip, flags)) { MZ_FCLOSE(pFile); return MZ_FALSE; } pZip->m_zip_type = MZ_ZIP_TYPE_FILE; pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pState->m_pFile = pFile; pZip->m_archive_size = file_size; pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) { mz_uint64 cur_file_ofs; if ((!pZip) || (!pFile)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); cur_file_ofs = MZ_FTELL64(pFile); if (!archive_size) { if (MZ_FSEEK64(pFile, 0, SEEK_END)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); archive_size = MZ_FTELL64(pFile) - cur_file_ofs; if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); } if (!mz_zip_reader_init_internal(pZip, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; pZip->m_pState->m_pFile = pFile; pZip->m_archive_size = archive_size; pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; if (!mz_zip_reader_read_central_dir(pZip, flags)) { mz_zip_reader_end_internal(pZip, MZ_FALSE); return MZ_FALSE; } return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_STDIO */ static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) { if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) return NULL; return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); } mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) { mz_uint m_bit_flag; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; } mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) { mz_uint bit_flag; mz_uint method; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); if ((method != 0) && (method != MZ_DEFLATED)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); return MZ_FALSE; } if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); return MZ_FALSE; } if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) { mz_uint filename_len, attribute_mapping_id, external_attr; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_len) { if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') return MZ_TRUE; } /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ /* FIXME: Remove this check? Is it necessary - we already check the filename. */ attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; (void)attribute_mapping_id; external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) { return MZ_TRUE; } return MZ_FALSE; } static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) { mz_uint n; const mz_uint8 *p = pCentral_dir_header; if (pFound_zip64_extra_data) *pFound_zip64_extra_data = MZ_FALSE; if ((!p) || (!pStat)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Extract fields from the central directory record. */ pStat->m_file_index = file_index; pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); #ifndef MINIZ_NO_TIME pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); #endif pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); /* Copy as much of the filename and comment as possible. */ n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); pStat->m_comment_size = n; memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; /* Set some flags for convienance */ pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); /* See if we need to read any zip64 extended information fields. */ /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) { /* Attempt to find zip64 extended information field in the entry's extra data */ mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); if (extra_size_remaining) { const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); do { mz_uint32 field_id; mz_uint32 field_data_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; mz_uint32 field_data_remaining = field_data_size; if (pFound_zip64_extra_data) *pFound_zip64_extra_data = MZ_TRUE; if (pStat->m_uncomp_size == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_uncomp_size = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } if (pStat->m_comp_size == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_comp_size = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } if (pStat->m_local_header_ofs == MZ_UINT32_MAX) { if (field_data_remaining < sizeof(mz_uint64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); pField_data += sizeof(mz_uint64); field_data_remaining -= sizeof(mz_uint64); } break; } pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; } while (extra_size_remaining); } } return MZ_TRUE; } static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) { mz_uint i; if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) return 0 == memcmp(pA, pB, len); for (i = 0; i < len; ++i) if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) return MZ_FALSE; return MZ_TRUE; } static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) { const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); mz_uint8 l = 0, r = 0; pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pE = pL + MZ_MIN(l_len, r_len); while (pL < pE) { if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) break; pL++; pR++; } return (pL == pE) ? (int)(l_len - r_len) : (l - r); } static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) { mz_zip_internal_state *pState = pZip->m_pState; const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; const mz_zip_array *pCentral_dir = &pState->m_central_dir; mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); const mz_uint32 size = pZip->m_total_files; const mz_uint filename_len = (mz_uint)strlen(pFilename); if (pIndex) *pIndex = 0; if (size) { /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ mz_int64 l = 0, h = (mz_int64)size - 1; while (l <= h) { mz_int64 m = l + ((h - l) >> 1); mz_uint32 file_index = pIndices[(mz_uint32)m]; int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); if (!comp) { if (pIndex) *pIndex = file_index; return MZ_TRUE; } else if (comp < 0) l = m + 1; else h = m - 1; } } return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); } int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) { mz_uint32 index; if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) return -1; else return (int)index; } mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) { mz_uint file_index; size_t name_len, comment_len; if (pIndex) *pIndex = 0; if ((!pZip) || (!pZip->m_pState) || (!pName)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* See if we can use a binary search */ if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) { return mz_zip_locate_file_binary_search(pZip, pName, pIndex); } /* Locate the entry by scanning the entire central directory */ name_len = strlen(pName); if (name_len > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); comment_len = pComment ? strlen(pComment) : 0; if (comment_len > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); for (file_index = 0; file_index < pZip->m_total_files; file_index++) { const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; if (filename_len < name_len) continue; if (comment_len) { mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); const char *pFile_comment = pFilename + filename_len + file_extra_len; if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) continue; } if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { int ofs = filename_len - 1; do { if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) break; } while (--ofs >= 0); ofs++; pFilename += ofs; filename_len -= ofs; } if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) { if (pIndex) *pIndex = file_index; return MZ_TRUE; } } return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); } static mz_bool mz_zip_reader_extract_to_mem_no_alloc1(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size, const mz_zip_archive_file_stat *st) { int status = TINFL_STATUS_DONE; mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; mz_zip_archive_file_stat file_stat; void *pRead_buf; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; tinfl_decompressor inflator; if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (st) { file_stat = *st; } else if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); /* Ensure supplied output buffer is large enough. */ needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; if (buf_size < needed_size) return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); /* Read and parse the local directory entry. */ cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_file_ofs += (mz_uint64)(MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) { if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); } #endif return MZ_TRUE; } /* Decompress the file either directly from memory or from a file input buffer. */ tinfl_init(&inflator); if (pZip->m_pState->m_pMem) { /* Read directly from the archive in memory. */ pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else if (pUser_read_buf) { /* Use a user provided read buffer. */ if (!user_read_buf_size) return MZ_FALSE; pRead_buf = (mz_uint8 *)pUser_read_buf; read_buf_size = user_read_buf_size; read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } else { /* Temporarily allocate a read buffer. */ read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } do { /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { status = TINFL_STATUS_FAILED; mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; out_buf_ofs += out_buf_size; } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); if (status == TINFL_STATUS_DONE) { /* Make sure the entire file was decompressed, and check its CRC. */ if (out_buf_ofs != file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); status = TINFL_STATUS_FAILED; } #endif } if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); } mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); } mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, NULL, 0, NULL); } mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) { return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); } void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) { mz_zip_archive_file_stat file_stat; mz_uint64 alloc_size; void *pBuf; if (pSize) *pSize = 0; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return NULL; alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) { mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); return NULL; } if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return NULL; } if (!mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, (size_t)alloc_size, flags, NULL, 0, &file_stat)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return NULL; } if (pSize) *pSize = (size_t)alloc_size; return pBuf; } void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) { if (pSize) *pSize = 0; return MZ_FALSE; } return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); } mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { int status = TINFL_STATUS_DONE; #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS mz_uint file_crc32 = MZ_CRC32_INIT; #endif mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf = NULL; void *pWrite_buf = NULL; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ cur_file_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_file_ofs += (mz_uint64)(MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); /* Decompress the file either directly from memory or from a file input buffer. */ if (pZip->m_pState->m_pMem) { pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; read_buf_size = read_buf_avail = file_stat.m_comp_size; comp_remaining = 0; } else { read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); read_buf_avail = 0; comp_remaining = file_stat.m_comp_size; } if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data. */ if (pZip->m_pState->m_pMem) { if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; } else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); #endif } cur_file_ofs += file_stat.m_comp_size; out_buf_ofs += file_stat.m_comp_size; comp_remaining = 0; } else { while (comp_remaining) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); status = TINFL_STATUS_FAILED; break; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); } #endif if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; out_buf_ofs += read_buf_avail; comp_remaining -= read_buf_avail; } } } else { tinfl_decompressor inflator; tinfl_init(&inflator); if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); status = TINFL_STATUS_FAILED; } else { do { mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); status = TINFL_STATUS_FAILED; break; } cur_file_ofs += read_buf_avail; comp_remaining -= read_buf_avail; read_buf_ofs = 0; } in_buf_size = (size_t)read_buf_avail; status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); read_buf_avail -= in_buf_size; read_buf_ofs += in_buf_size; if (out_buf_size) { if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) { mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); status = TINFL_STATUS_FAILED; break; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); #endif if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); status = TINFL_STATUS_FAILED; break; } } } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); } } if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { /* Make sure the entire file was decompressed, and check its CRC. */ if (out_buf_ofs != file_stat.m_uncomp_size) { mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (file_crc32 != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); status = TINFL_STATUS_FAILED; } #endif } if (!pZip->m_pState->m_pMem) pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); if (pWrite_buf) pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); return status == TINFL_STATUS_DONE; } mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); } mz_zip_reader_extract_iter_state *mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) { mz_zip_reader_extract_iter_state *pState; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; /* Argument sanity check */ if ((!pZip) || (!pZip->m_pState)) return NULL; /* Allocate an iterator status structure */ pState = (mz_zip_reader_extract_iter_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); if (!pState) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return NULL; } /* Fetch file details */ if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Encryption and patch files are not supported. */ if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* This function only supports decompressing stored and deflate. */ if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) { mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Init state - save args */ pState->pZip = pZip; pState->flags = flags; /* Init state - reset variables to defaults */ pState->status = TINFL_STATUS_DONE; #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS pState->file_crc32 = MZ_CRC32_INIT; #endif pState->read_buf_ofs = 0; pState->out_buf_ofs = 0; pState->pRead_buf = NULL; pState->pWrite_buf = NULL; pState->out_blk_remain = 0; /* Read and parse the local directory entry. */ pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } pState->cur_file_ofs += (mz_uint64)(MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } /* Decompress the file either directly from memory or from a file input buffer. */ if (pZip->m_pState->m_pMem) { pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; pState->comp_remaining = pState->file_stat.m_comp_size; } else { if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) { /* Decompression required, therefore intermediate read buffer required */ pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } } else { /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ pState->read_buf_size = 0; } pState->read_buf_avail = 0; pState->comp_remaining = pState->file_stat.m_comp_size; } if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) { /* Decompression required, init decompressor */ tinfl_init(&pState->inflator); /* Allocate write buffer */ if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (pState->pRead_buf) pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); pZip->m_pFree(pZip->m_pAlloc_opaque, pState); return NULL; } } return pState; } mz_zip_reader_extract_iter_state *mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) { mz_uint32 file_index; /* Locate file index by name */ if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) return NULL; /* Construct iterator */ return mz_zip_reader_extract_iter_new(pZip, file_index, flags); } size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state *pState, void *pvBuf, size_t buf_size) { size_t copied_to_caller = 0; /* Argument sanity check */ if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) return 0; if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) { /* The file is stored or the caller has requested the compressed data, calc amount to return. */ copied_to_caller = (size_t)MZ_MIN(buf_size, pState->comp_remaining); /* Zip is in memory....or requires reading from a file? */ if (pState->pZip->m_pState->m_pMem) { /* Copy data to caller's buffer */ memcpy(pvBuf, pState->pRead_buf, copied_to_caller); pState->pRead_buf = ((mz_uint8 *)pState->pRead_buf) + copied_to_caller; } else { /* Read directly into caller's buffer */ if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) { /* Failed to read all that was asked for, flag failure and alert user */ mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); pState->status = TINFL_STATUS_FAILED; copied_to_caller = 0; } } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS /* Compute CRC if not returning compressed data only */ if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); #endif /* Advance offsets, dec counters */ pState->cur_file_ofs += copied_to_caller; pState->out_buf_ofs += copied_to_caller; pState->comp_remaining -= copied_to_caller; } else { do { /* Calc ptr to write buffer - given current output pos and block size */ mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); /* Calc max output size - given current output pos and block size */ size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); if (!pState->out_blk_remain) { /* Read more data from file if none available (and reading from file) */ if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) { /* Calc read size */ pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) { mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); pState->status = TINFL_STATUS_FAILED; break; } /* Advance offsets, dec counters */ pState->cur_file_ofs += pState->read_buf_avail; pState->comp_remaining -= pState->read_buf_avail; pState->read_buf_ofs = 0; } /* Perform decompression */ in_buf_size = (size_t)pState->read_buf_avail; pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); pState->read_buf_avail -= in_buf_size; pState->read_buf_ofs += in_buf_size; /* Update current output block size remaining */ pState->out_blk_remain = out_buf_size; } if (pState->out_blk_remain) { /* Calc amount to return. */ size_t to_copy = MZ_MIN((buf_size - copied_to_caller), pState->out_blk_remain); /* Copy data to caller's buffer */ memcpy((mz_uint8 *)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy); #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS /* Perform CRC */ pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); #endif /* Decrement data consumed from block */ pState->out_blk_remain -= to_copy; /* Inc output offset, while performing sanity check */ if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) { mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); pState->status = TINFL_STATUS_FAILED; break; } /* Increment counter of data copied to caller */ copied_to_caller += to_copy; } } while ((copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT))); } /* Return how many bytes were copied into user buffer */ return copied_to_caller; } mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state *pState) { int status; /* Argument sanity check */ if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) return MZ_FALSE; /* Was decompression completed and requested? */ if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { /* Make sure the entire file was decompressed, and check its CRC. */ if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) { mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); pState->status = TINFL_STATUS_FAILED; } #ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS else if (pState->file_crc32 != pState->file_stat.m_crc32) { mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); pState->status = TINFL_STATUS_FAILED; } #endif } /* Free buffers */ if (!pState->pZip->m_pState->m_pMem) pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); if (pState->pWrite_buf) pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); /* Save status */ status = pState->status; /* Free context */ pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); return status == TINFL_STATUS_DONE; } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) { (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); } mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) { mz_bool status; mz_zip_archive_file_stat file_stat; MZ_FILE *pFile; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); pFile = MZ_FOPEN(pDst_filename, "wb"); if (!pFile) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); if (MZ_FCLOSE(pFile) == EOF) { if (status) mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); status = MZ_FALSE; } #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) if (status) mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); #endif return status; } mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); } mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) { mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) return MZ_FALSE; if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); } mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) { mz_uint32 file_index; if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) return MZ_FALSE; return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); } #endif /* #ifndef MINIZ_NO_STDIO */ static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_uint32 *p = (mz_uint32 *)pOpaque; (void)file_ofs; *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); return n; } mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) { mz_zip_archive_file_stat file_stat; mz_zip_internal_state *pState; const mz_uint8 *pCentral_dir_header; mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; mz_uint64 local_header_ofs = 0; mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; mz_uint64 local_header_comp_size, local_header_uncomp_size; mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; mz_bool has_data_descriptor; mz_uint32 local_header_bit_flags; mz_zip_array file_data_array; mz_zip_array_init(&file_data_array, 1); if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (file_index > pZip->m_total_files) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) return MZ_FALSE; /* A directory or zero length file */ if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) return MZ_TRUE; /* Encryption and patch files are not supported. */ if (file_stat.m_is_encrypted) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); /* This function only supports stored and deflate. */ if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); if (!file_stat.m_is_supported) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); /* Read and parse the local directory entry. */ local_header_ofs = file_stat.m_local_header_ofs; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); has_data_descriptor = (local_header_bit_flags & 8) != 0; if (local_header_filename_len != strlen(file_stat.m_filename)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); goto handle_failure; } if (local_header_filename_len) { if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) { mz_uint32 extra_size_remaining = local_header_extra_len; const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); goto handle_failure; } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); goto handle_failure; } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); if (field_data_size < sizeof(mz_uint64) * 2) { mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); goto handle_failure; } local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); found_zip64_ext_data_in_ldir = MZ_TRUE; break; } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); } /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) { mz_uint8 descriptor_buf[32]; mz_bool has_id; const mz_uint8 *pSrc; mz_uint32 file_crc32; mz_uint64 comp_size = 0, uncomp_size = 0; mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); goto handle_failure; } has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; file_crc32 = MZ_READ_LE32(pSrc); if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) { comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); } else { comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); } if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } else { if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); goto handle_failure; } } mz_zip_array_clear(pZip, &file_data_array); if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) { if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) return MZ_FALSE; /* 1 more check to be sure, although the extract checks too. */ if (uncomp_crc32 != file_stat.m_crc32) { mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); return MZ_FALSE; } } return MZ_TRUE; handle_failure: mz_zip_array_clear(pZip, &file_data_array); return MZ_FALSE; } mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) { mz_zip_internal_state *pState; mz_uint32 i; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; /* Basic sanity checks */ if (!pState->m_zip64) { if (pZip->m_total_files > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (pZip->m_archive_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } else { if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } for (i = 0; i < pZip->m_total_files; i++) { if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) { mz_uint32 found_index; mz_zip_archive_file_stat stat; if (!mz_zip_reader_file_stat(pZip, i, &stat)) return MZ_FALSE; if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) return MZ_FALSE; /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ if (found_index != i) return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); } if (!mz_zip_validate_file(pZip, i, flags)) return MZ_FALSE; } return MZ_TRUE; } mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) { mz_bool success = MZ_TRUE; mz_zip_archive zip; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; if ((!pMem) || (!size)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } mz_zip_zero_struct(&zip); if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) { if (pErr) *pErr = zip.m_last_error; return MZ_FALSE; } if (!mz_zip_validate_archive(&zip, flags)) { actual_err = zip.m_last_error; success = MZ_FALSE; } if (!mz_zip_reader_end_internal(&zip, success)) { if (!actual_err) actual_err = zip.m_last_error; success = MZ_FALSE; } if (pErr) *pErr = actual_err; return success; } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) { mz_bool success = MZ_TRUE; mz_zip_archive zip; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; if (!pFilename) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } mz_zip_zero_struct(&zip); if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) { if (pErr) *pErr = zip.m_last_error; return MZ_FALSE; } if (!mz_zip_validate_archive(&zip, flags)) { actual_err = zip.m_last_error; success = MZ_FALSE; } if (!mz_zip_reader_end_internal(&zip, success)) { if (!actual_err) actual_err = zip.m_last_error; success = MZ_FALSE; } if (pErr) *pErr = actual_err; return success; } #endif /* #ifndef MINIZ_NO_STDIO */ /* ------------------- .ZIP archive writing */ #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) { mz_write_le32(p, (mz_uint32)v); mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); } #define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) #define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) #define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_zip_internal_state *pState = pZip->m_pState; mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); if (!n) return 0; /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); return 0; } if (new_size > pState->m_mem_capacity) { void *pNew_block; size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) { mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); return 0; } pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; } memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); pState->m_mem_size = (size_t)new_size; return n; } static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) { mz_zip_internal_state *pState; mz_bool status = MZ_TRUE; if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) { if (set_last_error) mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return MZ_FALSE; } pState = pZip->m_pState; pZip->m_pState = NULL; mz_zip_array_clear(pZip, &pState->m_central_dir); mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); #ifndef MINIZ_NO_STDIO if (pState->m_pFile) { if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { if (MZ_FCLOSE(pState->m_pFile) == EOF) { if (set_last_error) mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); status = MZ_FALSE; } } pState->m_pFile = NULL; } #endif /* #ifndef MINIZ_NO_STDIO */ if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); pState->m_pMem = NULL; } pZip->m_pFree(pZip->m_pAlloc_opaque, pState); pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; return status; } mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) { mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) { if (!pZip->m_pRead) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } if (pZip->m_file_offset_alignment) { /* Ensure user specified file offset alignment is a power of 2. */ if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } if (!pZip->m_pAlloc) pZip->m_pAlloc = miniz_def_alloc_func; if (!pZip->m_pFree) pZip->m_pFree = miniz_def_free_func; if (!pZip->m_pRealloc) pZip->m_pRealloc = miniz_def_realloc_func; pZip->m_archive_size = existing_size; pZip->m_central_directory_file_ofs = 0; pZip->m_total_files = 0; if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); pZip->m_pState->m_zip64 = zip64; pZip->m_pState->m_zip64_has_extended_info_fields = zip64; pZip->m_zip_type = MZ_ZIP_TYPE_USER; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; return MZ_TRUE; } mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) { return mz_zip_writer_init_v2(pZip, existing_size, 0); } mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) { pZip->m_pWrite = mz_zip_heap_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_mem_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) return MZ_FALSE; pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) { if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { mz_zip_writer_end_internal(pZip, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pZip->m_pState->m_mem_capacity = initial_allocation_size; } return MZ_TRUE; } mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) { return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); } #ifndef MINIZ_NO_STDIO static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) { mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); file_ofs += pZip->m_pState->m_file_archive_start_ofs; if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) { mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); return 0; } return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); } mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) { return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); } mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) { MZ_FILE *pFile; pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) return MZ_FALSE; if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) { mz_zip_writer_end(pZip); return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); } pZip->m_pState->m_pFile = pFile; pZip->m_zip_type = MZ_ZIP_TYPE_FILE; if (size_to_reserve_at_beginning) { mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_ARR(buf); do { size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { mz_zip_writer_end(pZip); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_ofs += n; size_to_reserve_at_beginning -= n; } while (size_to_reserve_at_beginning); } return MZ_TRUE; } mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) { pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) pZip->m_pRead = mz_zip_file_read_func; pZip->m_pIO_opaque = pZip; if (!mz_zip_writer_init_v2(pZip, 0, flags)) return MZ_FALSE; pZip->m_pState->m_pFile = pFile; pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; return MZ_TRUE; } #endif /* #ifndef MINIZ_NO_STDIO */ mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) { mz_zip_internal_state *pState; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) { /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ if (!pZip->m_pState->m_zip64) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } /* No sense in trying to write to an archive that's already at the support max size */ if (pZip->m_pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); } pState = pZip->m_pState; if (pState->m_pFile) { #ifdef MINIZ_NO_STDIO (void)pFilename; return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); #else if (pZip->m_pIO_opaque != pZip) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE && !(flags & MZ_ZIP_FLAG_READ_ALLOW_WRITING) ) { if (!pFilename) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ mz_zip_reader_end_internal(pZip, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); } } pZip->m_pWrite = mz_zip_file_write_func; pZip->m_pNeeds_keepalive = NULL; #endif /* #ifdef MINIZ_NO_STDIO */ } else if (pState->m_pMem) { /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ if (pZip->m_pIO_opaque != pZip) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState->m_mem_capacity = pState->m_mem_size; pZip->m_pWrite = mz_zip_heap_write_func; pZip->m_pNeeds_keepalive = NULL; } /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ else if (!pZip->m_pWrite) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Start writing new files at the archive's current central directory location. */ /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ pZip->m_archive_size = pZip->m_central_directory_file_ofs; pZip->m_central_directory_file_ofs = 0; /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ /* TODO: We could easily maintain the sorted central directory offsets. */ mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; return MZ_TRUE; } mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) { return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); } /* TODO: pArchive_name is a terrible name here! */ mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) { return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); } typedef struct { mz_zip_archive *m_pZip; mz_uint64 m_cur_archive_file_ofs; mz_uint64 m_comp_size; } mz_zip_writer_add_state; static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) { mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) return MZ_FALSE; pState->m_cur_archive_file_ofs += len; pState->m_comp_size += len; return MZ_TRUE; } #define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) #define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) { mz_uint8 *pDst = pBuf; mz_uint32 field_size = 0; MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); MZ_WRITE_LE16(pDst + 2, 0); pDst += sizeof(mz_uint16) * 2; if (pUncomp_size) { MZ_WRITE_LE64(pDst, *pUncomp_size); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } if (pComp_size) { MZ_WRITE_LE64(pDst, *pComp_size); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } if (pLocal_header_ofs) { MZ_WRITE_LE64(pDst, *pLocal_header_ofs); pDst += sizeof(mz_uint64); field_size += sizeof(mz_uint64); } MZ_WRITE_LE16(pBuf + 2, field_size); return (mz_uint32)(pDst - pBuf); } static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) { (void)pZip; memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); return MZ_TRUE; } static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { (void)pZip; memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); return MZ_TRUE; } static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes, const char *user_extra_data, mz_uint user_extra_data_len) { mz_zip_internal_state *pState = pZip->m_pState; mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; size_t orig_central_dir_size = pState->m_central_dir.m_size; mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; if (!pZip->m_pState->m_zip64) { if (local_header_ofs > 0xFFFFFFFF) return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); } /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) { /* Try to resize the central directory array back into its original state. */ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } return MZ_TRUE; } static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) { /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ if (*pArchive_name == '/') return MZ_FALSE; /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/ return MZ_TRUE; } static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) { mz_uint32 n; if (!pZip->m_file_offset_alignment) return 0; n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); } static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) { char buf[4096]; memset(buf, 0, MZ_MIN(sizeof(buf), n)); while (n) { mz_uint32 s = MZ_MIN(sizeof(buf), n); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_file_ofs += s; n -= s; } return MZ_TRUE; } mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) { return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); } mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { mz_uint16 method = 0, dos_time = 0, dos_date = 0; mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; tdefl_compressor *pComp = NULL; mz_bool store_data_uncompressed; mz_zip_internal_state *pState; mz_uint8 *pExtra_data = NULL; mz_uint32 extra_size = 0; mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; mz_uint16 bit_flags = 0; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; level = level_and_flags & 0xF; store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if (pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ } if (((mz_uint64)buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_validate_archive_name(pArchive_name)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); #ifndef MINIZ_NO_TIME if (last_modified != NULL) { mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); } else { MZ_TIME_T cur_time; time(&cur_time); mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); } #else (void)last_modified; #endif /* #ifndef MINIZ_NO_TIME */ if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); uncomp_size = buf_size; if (uncomp_size <= 3) { level = 0; store_data_uncompressed = MZ_TRUE; } } archive_name_size = strlen(pArchive_name); if (archive_name_size > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!pState->m_zip64) { /* Bail early if the archive would obviously become too large */ if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { /* Set DOS Subdirectory attribute bit. */ ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; /* Subdirectories cannot contain data. */ if ((buf_size) || (uncomp_size)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); } /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if ((!store_data_uncompressed) && (buf_size)) { if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return MZ_FALSE; } local_dir_header_ofs += num_alignment_padding_bytes; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } cur_archive_file_ofs += num_alignment_padding_bytes; MZ_CLEAR_ARR(local_dir_header); if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { method = MZ_DEFLATED; } if (pState->m_zip64) { if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) { pExtra_data = extra_data; extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; if (pExtra_data != NULL) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += extra_size; } } else { if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; } if (user_extra_data_len > 0) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += user_extra_data_len; } if (store_data_uncompressed) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += buf_size; comp_size = buf_size; } else if (buf_size) { mz_zip_writer_add_state state; state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); } comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pComp = NULL; if (uncomp_size) { mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); if (pExtra_data == NULL) { if (comp_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(local_dir_footer + 8, comp_size); MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); } else { MZ_WRITE_LE64(local_dir_footer + 8, comp_size); MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) return MZ_FALSE; cur_archive_file_ofs += local_dir_footer_size; } if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, user_extra_data_central, user_extra_data_central_len)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void *callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { mz_uint16 gen_flags; mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; size_t archive_name_size; mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; mz_uint8 *pExtra_data = NULL; mz_uint32 extra_size = 0; mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; mz_zip_internal_state *pState; mz_uint64 file_ofs = 0, cur_archive_header_file_ofs; if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; level = level_and_flags & 0xF; gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; /* Sanity checks */ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX)) { /* Source file is too large for non-zip64 */ /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ pState->m_zip64 = MZ_TRUE; } /* We could support this, but why? */ if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_validate_archive_name(pArchive_name)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); if (pState->m_zip64) { if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if (pZip->m_total_files == MZ_UINT16_MAX) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ } } archive_name_size = strlen(pArchive_name); if (archive_name_size > MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); if (!pState->m_zip64) { /* Bail early if the archive would obviously become too large */ if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) { pState->m_zip64 = MZ_TRUE; /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ } } #ifndef MINIZ_NO_TIME if (pFile_time) { mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); } #else (void)pFile_time; #endif if (max_size <= 3) level = 0; if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += num_alignment_padding_bytes; local_dir_header_ofs = cur_archive_file_ofs; if (pZip->m_file_offset_alignment) { MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } if (max_size && level) { method = MZ_DEFLATED; } MZ_CLEAR_ARR(local_dir_header); if (pState->m_zip64) { if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) { pExtra_data = extra_data; if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); else extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL, NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += extra_size; } else { if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_file_ofs += archive_name_size; } if (user_extra_data_len > 0) { if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_file_ofs += user_extra_data_len; } if (max_size) { void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); if (!pRead_buf) { return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!level) { while (1) { size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); if (n == 0) break; if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } file_ofs += n; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); cur_archive_file_ofs += n; } uncomp_size = file_ofs; comp_size = uncomp_size; } else { mz_bool result = MZ_FALSE; mz_zip_writer_add_state state; tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); if (!pComp) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } state.m_pZip = pZip; state.m_cur_archive_file_ofs = cur_archive_file_ofs; state.m_comp_size = 0; if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) { pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); } for (;;) { tdefl_status status; tdefl_flush flush = TDEFL_NO_FLUSH; size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) { mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); break; } file_ofs += n; uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) flush = TDEFL_FULL_FLUSH; if (n == 0) flush = TDEFL_FINISH; status = tdefl_compress_buffer(pComp, pRead_buf, n, flush); if (status == TDEFL_STATUS_DONE) { result = MZ_TRUE; break; } else if (status != TDEFL_STATUS_OKAY) { mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); break; } } pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); if (!result) { pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); return MZ_FALSE; } uncomp_size = file_ofs; comp_size = state.m_comp_size; cur_archive_file_ofs = state.m_cur_archive_file_ofs; } pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); } if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)) { mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); if (pExtra_data == NULL) { if (comp_size > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(local_dir_footer + 8, comp_size); MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); } else { MZ_WRITE_LE64(local_dir_footer + 8, comp_size); MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) return MZ_FALSE; cur_archive_file_ofs += local_dir_footer_size; } if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) { if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size, (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date)) return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); cur_archive_header_file_ofs = local_dir_header_ofs; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); if (pExtra_data != NULL) { cur_archive_header_file_ofs += sizeof(local_dir_header); if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size) { return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_archive_header_file_ofs += archive_name_size; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_archive_header_file_ofs += extra_size; } } if (pExtra_data != NULL) { extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); } if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, user_extra_data_central, user_extra_data_central_len)) return MZ_FALSE; pZip->m_total_files++; pZip->m_archive_size = cur_archive_file_ofs; return MZ_TRUE; } #ifndef MINIZ_NO_STDIO static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque; mz_int64 cur_ofs = MZ_FTELL64(pSrc_file); if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET)))) return 0; return MZ_FREAD(pBuf, 1, n, pSrc_file); } mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) { return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, max_size, pFile_time, pComment, comment_size, level_and_flags, user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); } mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { MZ_FILE *pSrc_file = NULL; mz_uint64 uncomp_size = 0; MZ_TIME_T file_modified_time; MZ_TIME_T *pFile_time = NULL; mz_bool status; memset(&file_modified_time, 0, sizeof(file_modified_time)); #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) pFile_time = &file_modified_time; if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); #endif pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); if (!pSrc_file) return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); MZ_FSEEK64(pSrc_file, 0, SEEK_END); uncomp_size = MZ_FTELL64(pSrc_file); MZ_FSEEK64(pSrc_file, 0, SEEK_SET); status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); MZ_FCLOSE(pSrc_file); return status; } #endif /* #ifndef MINIZ_NO_STDIO */ static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, mz_uint32 ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) { /* + 64 should be enough for any new zip64 data */ if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) { mz_uint8 new_ext_block[64]; mz_uint8 *pDst = new_ext_block; mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); mz_write_le16(pDst + sizeof(mz_uint16), 0); pDst += sizeof(mz_uint16) * 2; if (pUncomp_size) { mz_write_le64(pDst, *pUncomp_size); pDst += sizeof(mz_uint64); } if (pComp_size) { mz_write_le64(pDst, *pComp_size); pDst += sizeof(mz_uint64); } if (pLocal_header_ofs) { mz_write_le64(pDst, *pLocal_header_ofs); pDst += sizeof(mz_uint64); } if (pDisk_start) { mz_write_le32(pDst, *pDisk_start); pDst += sizeof(mz_uint32); } mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if ((pExt) && (ext_len)) { mz_uint32 extra_size_remaining = ext_len; const mz_uint8 *pExtra_data = pExt; do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); } return MZ_TRUE; } /* TODO: This func is now pretty freakin complex due to zip64, split it up? */ mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) { mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; size_t orig_central_dir_size; mz_zip_internal_state *pState; void *pBuf; const mz_uint8 *pSrc_central_header; mz_zip_archive_file_stat src_file_stat; mz_uint32 src_filename_len, src_comment_len, src_ext_len; mz_uint32 local_header_filename_size, local_header_extra_len; mz_uint64 local_header_comp_size, local_header_uncomp_size; mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; /* Sanity checks */ if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); /* Get pointer to the source central dir header and crack it */ if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); if (!pState->m_zip64) { if (pZip->m_total_files == MZ_UINT16_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ if (pZip->m_total_files == MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) return MZ_FALSE; cur_src_file_ofs = src_file_stat.m_local_header_ofs; cur_dst_file_ofs = pZip->m_archive_size; /* Read the source archive's local dir header */ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; /* Compute the total size we need to copy (filename+extra data+compressed data) */ local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); src_archive_bytes_remaining = src_file_stat.m_comp_size + local_header_filename_size + local_header_extra_len; /* Try to find a zip64 extended information field */ if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) { mz_zip_array file_data_array; const mz_uint8 *pExtra_data; mz_uint32 extra_size_remaining = local_header_extra_len; mz_zip_array_init(&file_data_array, 1); if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) { return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } pExtra_data = (const mz_uint8 *)file_data_array.m_p; do { mz_uint32 field_id, field_data_size, field_total_size; if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } field_id = MZ_READ_LE16(pExtra_data); field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); field_total_size = field_data_size + sizeof(mz_uint16) * 2; if (field_total_size > extra_size_remaining) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); if (field_data_size < sizeof(mz_uint64) * 2) { mz_zip_array_clear(pZip, &file_data_array); return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); } local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ found_zip64_ext_data_in_ldir = MZ_TRUE; break; } pExtra_data += field_total_size; extra_size_remaining -= field_total_size; } while (extra_size_remaining); mz_zip_array_clear(pZip, &file_data_array); } if (!pState->m_zip64) { /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ /* We also check when the archive is finalized so this doesn't need to be perfect. */ mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; if (approx_new_archive_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); } /* Write dest archive padding */ if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) return MZ_FALSE; cur_dst_file_ofs += num_alignment_padding_bytes; local_dir_header_ofs = cur_dst_file_ofs; if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); while (src_archive_bytes_remaining) { n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } cur_src_file_ofs += n; if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_dst_file_ofs += n; src_archive_bytes_remaining -= n; } /* Now deal with the optional data descriptor */ bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); if (bit_flags & 8) { /* Copy data descriptor */ if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) { /* src is zip64, dest must be zip64 */ /* name uint32_t's */ /* id 1 (optional in zip64?) */ /* crc 1 */ /* comp_size 2 */ /* uncomp_size 2 */ if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); } else { /* src is NOT zip64 */ mz_bool has_id; if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); } has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); if (pZip->m_pState->m_zip64) { /* dest is zip64, so upgrade the data descriptor */ const mz_uint8 *pSrc_descriptor = (const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0); const mz_uint32 src_crc32 = MZ_READ_LE32(pSrc_descriptor); const mz_uint64 src_comp_size = MZ_READ_LE32(pSrc_descriptor + sizeof(mz_uint32)); const mz_uint64 src_uncomp_size = MZ_READ_LE32(pSrc_descriptor + 2 * sizeof(mz_uint32)); mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); n = sizeof(mz_uint32) * 6; } else { /* dest is NOT zip64, just copy it as-is */ n = sizeof(mz_uint32) * (has_id ? 4 : 3); } } if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); } cur_src_file_ofs += n; cur_dst_file_ofs += n; } pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); /* Finally, add the new central dir header */ orig_central_dir_size = pState->m_central_dir.m_size; memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); if (pState->m_zip64) { /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; mz_zip_array new_ext_block; mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) { mz_zip_array_clear(pZip, &new_ext_block); return MZ_FALSE; } MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) { mz_zip_array_clear(pZip, &new_ext_block); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) { mz_zip_array_clear(pZip, &new_ext_block); mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } mz_zip_array_clear(pZip, &new_ext_block); } else { /* sanity checks */ if (cur_dst_file_ofs > MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); if (local_dir_header_ofs >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } } /* This shouldn't trigger unless we screwed up during the initial sanity checks */ if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) { /* TODO: Support central dirs >= 32-bits in size */ mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); } n = (mz_uint32)orig_central_dir_size; if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } pZip->m_total_files++; pZip->m_archive_size = cur_dst_file_ofs; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) { mz_zip_internal_state *pState; mz_uint64 central_dir_ofs, central_dir_size; mz_uint8 hdr[256]; if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); pState = pZip->m_pState; if (pState->m_zip64) { if ((mz_uint64)pState->m_central_dir.m_size >= MZ_UINT32_MAX) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } else { if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); } central_dir_ofs = 0; central_dir_size = 0; if (pZip->m_total_files) { /* Write central directory */ central_dir_ofs = pZip->m_archive_size; central_dir_size = pState->m_central_dir.m_size; pZip->m_central_directory_file_ofs = central_dir_ofs; if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += central_dir_size; } if (pState->m_zip64) { /* Write zip64 end of central directory header */ mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; /* Write zip64 end of central directory locator */ MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; } /* Write end of central directory record */ MZ_CLEAR_ARR(hdr); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); #ifndef MINIZ_NO_STDIO if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); #endif /* #ifndef MINIZ_NO_STDIO */ pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; return MZ_TRUE; } mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) { if ((!ppBuf) || (!pSize)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); *ppBuf = NULL; *pSize = 0; if ((!pZip) || (!pZip->m_pState)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (pZip->m_pWrite != mz_zip_heap_write_func) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); if (!mz_zip_writer_finalize_archive(pZip)) return MZ_FALSE; *ppBuf = pZip->m_pState->m_pMem; *pSize = pZip->m_pState->m_mem_size; pZip->m_pState->m_pMem = NULL; pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; return MZ_TRUE; } mz_bool mz_zip_writer_end(mz_zip_archive *pZip) { return mz_zip_writer_end_internal(pZip, MZ_TRUE); } #ifndef MINIZ_NO_STDIO mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) { return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); } mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) { mz_bool status, created_new_archive = MZ_FALSE; mz_zip_archive zip_archive; struct MZ_FILE_STAT_STRUCT file_stat; mz_zip_error actual_err = MZ_ZIP_NO_ERROR; mz_zip_zero_struct(&zip_archive); if ((int)level_and_flags < 0) level_and_flags = MZ_DEFAULT_LEVEL; if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return MZ_FALSE; } if (!mz_zip_writer_validate_archive_name(pArchive_name)) { if (pErr) *pErr = MZ_ZIP_INVALID_FILENAME; return MZ_FALSE; } /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { /* Create a new archive. */ if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) { if (pErr) *pErr = zip_archive.m_last_error; return MZ_FALSE; } created_new_archive = MZ_TRUE; } else { /* Append to an existing archive. */ if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY | MZ_ZIP_FLAG_READ_ALLOW_WRITING, 0, 0)) { if (pErr) *pErr = zip_archive.m_last_error; return MZ_FALSE; } if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_READ_ALLOW_WRITING)) { if (pErr) *pErr = zip_archive.m_last_error; mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); return MZ_FALSE; } } status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); actual_err = zip_archive.m_last_error; /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ if (!mz_zip_writer_finalize_archive(&zip_archive)) { if (!actual_err) actual_err = zip_archive.m_last_error; status = MZ_FALSE; } if (!mz_zip_writer_end_internal(&zip_archive, status)) { if (!actual_err) actual_err = zip_archive.m_last_error; status = MZ_FALSE; } if ((!status) && (created_new_archive)) { /* It's a new archive and something went wrong, so just delete it. */ int ignoredStatus = MZ_DELETE_FILE(pZip_filename); (void)ignoredStatus; } if (pErr) *pErr = actual_err; return status; } void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) { mz_uint32 file_index; mz_zip_archive zip_archive; void *p = NULL; if (pSize) *pSize = 0; if ((!pZip_filename) || (!pArchive_name)) { if (pErr) *pErr = MZ_ZIP_INVALID_PARAMETER; return NULL; } mz_zip_zero_struct(&zip_archive); if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) { if (pErr) *pErr = zip_archive.m_last_error; return NULL; } if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) { p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); } mz_zip_reader_end_internal(&zip_archive, p != NULL); if (pErr) *pErr = zip_archive.m_last_error; return p; } void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) { return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); } #endif /* #ifndef MINIZ_NO_STDIO */ #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ /* ------------------- Misc utils */ mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) { return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; } mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) { return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; } mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) { mz_zip_error prev_err; if (!pZip) return MZ_ZIP_INVALID_PARAMETER; prev_err = pZip->m_last_error; pZip->m_last_error = err_num; return prev_err; } mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) { if (!pZip) return MZ_ZIP_INVALID_PARAMETER; return pZip->m_last_error; } mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) { return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); } mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) { mz_zip_error prev_err; if (!pZip) return MZ_ZIP_INVALID_PARAMETER; prev_err = pZip->m_last_error; pZip->m_last_error = MZ_ZIP_NO_ERROR; return prev_err; } const char *mz_zip_get_error_string(mz_zip_error mz_err) { switch (mz_err) { case MZ_ZIP_NO_ERROR: return "no error"; case MZ_ZIP_UNDEFINED_ERROR: return "undefined error"; case MZ_ZIP_TOO_MANY_FILES: return "too many files"; case MZ_ZIP_FILE_TOO_LARGE: return "file too large"; case MZ_ZIP_UNSUPPORTED_METHOD: return "unsupported method"; case MZ_ZIP_UNSUPPORTED_ENCRYPTION: return "unsupported encryption"; case MZ_ZIP_UNSUPPORTED_FEATURE: return "unsupported feature"; case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: return "failed finding central directory"; case MZ_ZIP_NOT_AN_ARCHIVE: return "not a ZIP archive"; case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: return "invalid header or archive is corrupted"; case MZ_ZIP_UNSUPPORTED_MULTIDISK: return "unsupported multidisk archive"; case MZ_ZIP_DECOMPRESSION_FAILED: return "decompression failed or archive is corrupted"; case MZ_ZIP_COMPRESSION_FAILED: return "compression failed"; case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: return "unexpected decompressed size"; case MZ_ZIP_CRC_CHECK_FAILED: return "CRC-32 check failed"; case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: return "unsupported central directory size"; case MZ_ZIP_ALLOC_FAILED: return "allocation failed"; case MZ_ZIP_FILE_OPEN_FAILED: return "file open failed"; case MZ_ZIP_FILE_CREATE_FAILED: return "file create failed"; case MZ_ZIP_FILE_WRITE_FAILED: return "file write failed"; case MZ_ZIP_FILE_READ_FAILED: return "file read failed"; case MZ_ZIP_FILE_CLOSE_FAILED: return "file close failed"; case MZ_ZIP_FILE_SEEK_FAILED: return "file seek failed"; case MZ_ZIP_FILE_STAT_FAILED: return "file stat failed"; case MZ_ZIP_INVALID_PARAMETER: return "invalid parameter"; case MZ_ZIP_INVALID_FILENAME: return "invalid filename"; case MZ_ZIP_BUF_TOO_SMALL: return "buffer too small"; case MZ_ZIP_INTERNAL_ERROR: return "internal error"; case MZ_ZIP_FILE_NOT_FOUND: return "file not found"; case MZ_ZIP_ARCHIVE_TOO_LARGE: return "archive is too large"; case MZ_ZIP_VALIDATION_FAILED: return "validation failed"; case MZ_ZIP_WRITE_CALLBACK_FAILED: return "write callback failed"; case MZ_ZIP_TOTAL_ERRORS: return "total errors"; default: break; } return "unknown error"; } /* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return MZ_FALSE; return pZip->m_pState->m_zip64; } size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_central_dir.m_size; } mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) { return pZip ? pZip->m_total_files : 0; } mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) { if (!pZip) return 0; return pZip->m_archive_size; } mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_file_archive_start_ofs; } MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) { if ((!pZip) || (!pZip->m_pState)) return 0; return pZip->m_pState->m_pFile; } size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) { if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); } mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) { mz_uint n; const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); if (!p) { if (filename_buf_size) pFilename[0] = '\0'; mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); return 0; } n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); if (filename_buf_size) { n = MZ_MIN(n, filename_buf_size - 1); memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pFilename[n] = '\0'; } return n + 1; } mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) { return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); } mz_bool mz_zip_end(mz_zip_archive *pZip) { if (!pZip) return MZ_FALSE; if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) return mz_zip_reader_end(pZip); #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) return mz_zip_writer_end(pZip); #endif return MZ_FALSE; } #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/miniz/miniz.h000066400000000000000000002257401523150047600233670ustar00rootroot00000000000000#ifndef MINIZ_EXPORT #define MINIZ_EXPORT #endif /* miniz.c 3.1.2 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). * Low-level Deflate/Inflate implementation notes: Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses approximately as well as zlib. Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory block large enough to hold the entire file. The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. * zlib-style API notes: miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in zlib replacement in many apps: The z_stream struct, optional memory allocation callbacks deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound inflateInit/inflateInit2/inflate/inflateReset/inflateEnd compress, compress2, compressBound, uncompress CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. Supports raw deflate streams or standard zlib streams with adler-32 checking. Limitations: The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but there are no guarantees that miniz.c pulls this off perfectly. * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by Alex Evans. Supports 1-4 bytes/pixel images. * ZIP archive API notes: The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to get the job done with minimal fuss. There are simple API's to retrieve file information, read files from existing archives, create new archives, append new files to existing archives, or clone archive data from one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), or you can specify custom file read/write callbacks. - Archive reading: Just call this function to read a single file from a disk archive: void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); The locate operation can optionally check file comments too, which (as one example) can be used to identify multiple versions of the same file in an archive. This function uses a simple linear search through the central directory, so it's not very fast. Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and retrieve detailed info on each file by calling mz_zip_reader_file_stat(). - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data to disk and builds an exact image of the central directory in memory. The central directory image is written all at once at the end of the archive file when the archive is finalized. The archive writer can optionally align each file's local header and file data to any power of 2 alignment, which can be useful when the archive will be read from optical media. Also, the writer supports placing arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still readable by any ZIP tool. - Archive appending: The simple way to add a single file to an archive is to call this function: mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); The archive will be created if it doesn't already exist, otherwise it'll be appended to. Note the appending is done in-place and is not an atomic operation, so if something goes wrong during the operation it's possible the archive could be left without a central directory (although the local file headers and file data will be fine, so the archive will be recoverable). For more complex archive modification scenarios: 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and you're done. This is safe but requires a bunch of temporary disk space or heap memory. 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), append new files as needed, then finalize the archive which will write an updated central directory to the original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - ZIP archive support limitations: No spanning support. Extraction functions can only handle unencrypted, stored or deflated files. Requires streams capable of seeking. * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. * Important: For best perf. be sure to customize the below macros for your target platform: #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define MINIZ_LITTLE_ENDIAN 1 #define MINIZ_HAS_64BIT_REGISTERS 1 * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). */ #pragma once #if defined(_MSC_VER) #define MZ_FORCEINLINE __forceinline #elif defined(__GNUC__) #define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) #else #define MZ_FORCEINLINE inline #endif /* Defines to completely disable specific portions of miniz.c: If all macros here are defined the only functionality remaining will be CRC-32 and adler-32. */ /* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ /*#define MINIZ_NO_STDIO */ /* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ /* get/set file times, and the C run-time funcs that get/set times won't be called. */ /* The current downside is the times written to your archives will be from 1979. */ /*#define MINIZ_NO_TIME */ /* Define MINIZ_NO_DEFLATE_APIS to disable all compression API's. */ /*#define MINIZ_NO_DEFLATE_APIS */ /* Define MINIZ_NO_INFLATE_APIS to disable all decompression API's. */ /*#define MINIZ_NO_INFLATE_APIS */ /* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ /*#define MINIZ_NO_ARCHIVE_APIS */ /* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ /*#define MINIZ_NO_ARCHIVE_WRITING_APIS */ /* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ /*#define MINIZ_NO_ZLIB_APIS */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ /*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ /* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ /*#define MINIZ_NO_MALLOC */ #ifdef MINIZ_NO_INFLATE_APIS #define MINIZ_NO_ARCHIVE_APIS #endif #ifdef MINIZ_NO_DEFLATE_APIS #define MINIZ_NO_ARCHIVE_WRITING_APIS #endif #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) /* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ #define MINIZ_NO_TIME #endif #include #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) #include #endif #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) /* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ #define MINIZ_X86_OR_X64_CPU 1 #else #define MINIZ_X86_OR_X64_CPU 0 #endif /* Set MINIZ_LITTLE_ENDIAN only if not set */ #if !defined(MINIZ_LITTLE_ENDIAN) #if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) #if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ #define MINIZ_LITTLE_ENDIAN 1 #else #define MINIZ_LITTLE_ENDIAN 0 #endif #else #if MINIZ_X86_OR_X64_CPU #define MINIZ_LITTLE_ENDIAN 1 #else #define MINIZ_LITTLE_ENDIAN 0 #endif #endif #endif /* Using unaligned loads and stores causes errors when using UBSan */ #if defined(__has_feature) #if __has_feature(undefined_behavior_sanitizer) #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 #endif #endif /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) #if MINIZ_X86_OR_X64_CPU /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 #define MINIZ_UNALIGNED_USE_MEMCPY #else #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 #endif #endif #if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) /* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ #define MINIZ_HAS_64BIT_REGISTERS 1 #else #define MINIZ_HAS_64BIT_REGISTERS 0 #endif #ifdef __cplusplus extern "C" { #endif /* ------------------- zlib-style API Definitions. */ /* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ typedef unsigned long mz_ulong; /* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ MINIZ_EXPORT void mz_free(void *p); #define MZ_ADLER32_INIT (1) /* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); #define MZ_CRC32_INIT (0) /* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); /* Compression strategies. */ enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; /* Method */ #define MZ_DEFLATED 8 /* Heap allocation callbacks. Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */ typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); typedef void (*mz_free_func)(void *opaque, void *address); typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); /* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; #define MZ_VERSION "11.3.2" #define MZ_VERNUM 0xB302 #define MZ_VER_MAJOR 11 #define MZ_VER_MINOR 3 #define MZ_VER_REVISION 2 #define MZ_VER_SUBREVISION 0 #ifndef MINIZ_NO_ZLIB_APIS /* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ 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 }; /* 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; /* Returns the version string of miniz.c. */ MINIZ_EXPORT const char *mz_version(void); #ifndef MINIZ_NO_DEFLATE_APIS /* mz_deflateInit() initializes a compressor with default options: */ /* Parameters: */ /* pStream must point to an initialized mz_stream struct. */ /* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ /* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ /* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if the input parameters are bogus. */ /* MZ_MEM_ERROR on out of memory. */ MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level); /* mz_deflateInit2() is like mz_deflate(), except with more control: */ /* Additional parameters: */ /* method must be MZ_DEFLATED */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ /* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream); /* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ /* Parameters: */ /* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ /* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ /* Return values: */ /* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ /* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush); /* mz_deflateEnd() deinitializes a compressor: */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream); /* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); /* Single-call compression functions mz_compress() and mz_compress2(): */ /* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); /* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len); #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #ifndef MINIZ_NO_INFLATE_APIS /* Initializes a decompressor. */ MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream); /* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream); /* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ /* Parameters: */ /* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ /* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ /* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ /* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ /* Return values: */ /* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ /* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_DATA_ERROR if the deflate stream is invalid. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ /* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush); /* Deinitializes a decompressor. */ MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream); /* Single-call decompression. */ /* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len); #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ /* Returns a string description of the specified error code, or NULL if the error code is invalid. */ MINIZ_EXPORT const char *mz_error(int err); /* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES typedef unsigned char Byte; typedef unsigned int uInt; typedef mz_ulong uLong; typedef Byte Bytef; typedef uInt uIntf; typedef char charf; typedef int intf; typedef void *voidpf; typedef uLong uLongf; typedef void *voidp; typedef void *const voidpc; #define Z_NULL 0 #define Z_NO_FLUSH MZ_NO_FLUSH #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH #define Z_SYNC_FLUSH MZ_SYNC_FLUSH #define Z_FULL_FLUSH MZ_FULL_FLUSH #define Z_FINISH MZ_FINISH #define Z_BLOCK MZ_BLOCK #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 Z_PARAM_ERROR MZ_PARAM_ERROR #define Z_NO_COMPRESSION MZ_NO_COMPRESSION #define Z_BEST_SPEED MZ_BEST_SPEED #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY #define Z_FILTERED MZ_FILTERED #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY #define Z_RLE MZ_RLE #define Z_FIXED MZ_FIXED #define Z_DEFLATED MZ_DEFLATED #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS /* See mz_alloc_func */ typedef void *(*alloc_func)(void *opaque, size_t items, size_t size); /* See mz_free_func */ typedef void (*free_func)(void *opaque, void *address); #define internal_state mz_internal_state #define z_stream mz_stream #ifndef MINIZ_NO_DEFLATE_APIS /* Compatiblity with zlib API. See called functions for documentation */ static MZ_FORCEINLINE int deflateInit(mz_streamp pStream, int level) { return mz_deflateInit(pStream, level); } static MZ_FORCEINLINE int deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) { return mz_deflateInit2(pStream, level, method, window_bits, mem_level, strategy); } static MZ_FORCEINLINE int deflateReset(mz_streamp pStream) { return mz_deflateReset(pStream); } static MZ_FORCEINLINE int deflate(mz_streamp pStream, int flush) { return mz_deflate(pStream, flush); } static MZ_FORCEINLINE int deflateEnd(mz_streamp pStream) { return mz_deflateEnd(pStream); } static MZ_FORCEINLINE mz_ulong deflateBound(mz_streamp pStream, mz_ulong source_len) { return mz_deflateBound(pStream, source_len); } static MZ_FORCEINLINE int compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) { return mz_compress(pDest, pDest_len, pSource, source_len); } static MZ_FORCEINLINE int compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) { return mz_compress2(pDest, pDest_len, pSource, source_len, level); } static MZ_FORCEINLINE mz_ulong compressBound(mz_ulong source_len) { return mz_compressBound(source_len); } #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #ifndef MINIZ_NO_INFLATE_APIS /* Compatiblity with zlib API. See called functions for documentation */ static MZ_FORCEINLINE int inflateInit(mz_streamp pStream) { return mz_inflateInit(pStream); } static MZ_FORCEINLINE int inflateInit2(mz_streamp pStream, int window_bits) { return mz_inflateInit2(pStream, window_bits); } static MZ_FORCEINLINE int inflateReset(mz_streamp pStream) { return mz_inflateReset(pStream); } static MZ_FORCEINLINE int inflate(mz_streamp pStream, int flush) { return mz_inflate(pStream, flush); } static MZ_FORCEINLINE int inflateEnd(mz_streamp pStream) { return mz_inflateEnd(pStream); } static MZ_FORCEINLINE int uncompress(unsigned char* pDest, mz_ulong* pDest_len, const unsigned char* pSource, mz_ulong source_len) { return mz_uncompress(pDest, pDest_len, pSource, source_len); } static MZ_FORCEINLINE int uncompress2(unsigned char* pDest, mz_ulong* pDest_len, const unsigned char* pSource, mz_ulong* pSource_len) { return mz_uncompress2(pDest, pDest_len, pSource, pSource_len); } #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ static MZ_FORCEINLINE mz_ulong crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len) { return mz_crc32(crc, ptr, buf_len); } static MZ_FORCEINLINE mz_ulong adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { return mz_adler32(adler, ptr, buf_len); } #define MAX_WBITS 15 #define MAX_MEM_LEVEL 9 static MZ_FORCEINLINE const char* zError(int err) { return mz_error(err); } #define ZLIB_VERSION MZ_VERSION #define ZLIB_VERNUM MZ_VERNUM #define ZLIB_VER_MAJOR MZ_VER_MAJOR #define ZLIB_VER_MINOR MZ_VER_MINOR #define ZLIB_VER_REVISION MZ_VER_REVISION #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION #define zlibVersion mz_version #define zlib_version mz_version() #endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ #endif /* MINIZ_NO_ZLIB_APIS */ #ifdef __cplusplus } #endif #pragma once #include #include #include #include /* ------------------- Types and macros */ typedef unsigned char mz_uint8; typedef int16_t mz_int16; typedef uint16_t mz_uint16; typedef uint32_t mz_uint32; typedef uint32_t mz_uint; typedef int64_t mz_int64; typedef uint64_t mz_uint64; typedef int mz_bool; #define MZ_FALSE (0) #define MZ_TRUE (1) /* 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 #ifdef MINIZ_NO_STDIO #define MZ_FILE void * #else #include #define MZ_FILE FILE #endif /* #ifdef MINIZ_NO_STDIO */ #ifdef MINIZ_NO_TIME typedef struct mz_dummy_time_t_tag { mz_uint32 m_dummy1; mz_uint32 m_dummy2; } mz_dummy_time_t; #define MZ_TIME_T mz_dummy_time_t #else #define MZ_TIME_T time_t #endif #define MZ_ASSERT(x) assert(x) #ifdef MINIZ_NO_MALLOC #define MZ_MALLOC(x) NULL #define MZ_FREE(x) (void)x, ((void)0) #define MZ_REALLOC(p, x) NULL #else #define MZ_MALLOC(x) malloc(x) #define MZ_FREE(x) free(x) #define MZ_REALLOC(p, x) realloc(p, x) #endif #define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) #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 MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) #ifdef __cplusplus extern "C" { #endif extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address); extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); #define MZ_UINT16_MAX (0xFFFFU) #define MZ_UINT32_MAX (0xFFFFFFFFU) #ifdef __cplusplus } #endif #pragma once #ifndef MINIZ_NO_DEFLATE_APIS #ifdef __cplusplus extern "C" { #endif /* ------------------- Low-level Compression API Definitions */ /* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */ #ifndef TDEFL_LESS_MEMORY #define TDEFL_LESS_MEMORY 0 #endif /* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */ /* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */ enum { TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF }; /* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */ /* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */ /* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */ /* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */ /* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */ /* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */ /* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */ /* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */ /* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */ enum { TDEFL_WRITE_ZLIB_HEADER = 0x01000, TDEFL_COMPUTE_ADLER32 = 0x02000, TDEFL_GREEDY_PARSING_FLAG = 0x04000, TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, TDEFL_RLE_MATCHES = 0x10000, TDEFL_FILTER_MATCHES = 0x20000, TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 }; /* High level compression functions: */ /* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */ /* On entry: */ /* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */ /* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */ /* On return: */ /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must free() the returned block when it's no longer needed. */ MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ /* Returns 0 on failure. */ MINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* Compresses an image to a compressed PNG file in memory. */ /* On entry: */ /* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */ /* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */ /* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */ /* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */ /* On return: */ /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pLen_out will be set to the size of the PNG image file. */ /* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); /* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); /* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ MINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; /* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */ #if TDEFL_LESS_MEMORY enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #else enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (mz_uint)((TDEFL_LZ_CODE_BUF_SIZE * 13) / 10), TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; #endif /* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */ typedef enum { TDEFL_STATUS_BAD_PARAM = -2, TDEFL_STATUS_PUT_BUF_FAILED = -1, TDEFL_STATUS_OKAY = 0, TDEFL_STATUS_DONE = 1 } tdefl_status; /* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */ typedef enum { TDEFL_NO_FLUSH = 0, TDEFL_SYNC_FLUSH = 2, TDEFL_FULL_FLUSH = 3, TDEFL_FINISH = 4 } tdefl_flush; /* tdefl's compression state structure. */ typedef struct { tdefl_put_buf_func_ptr m_pPut_buf_func; void *m_pPut_buf_user; mz_uint m_flags, m_max_probes[2]; int m_greedy_parsing; mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; tdefl_status m_prev_return_status; const void *m_pIn_buf; void *m_pOut_buf; size_t *m_pIn_buf_size, *m_pOut_buf_size; tdefl_flush m_flush; const mz_uint8 *m_pSrc; size_t m_src_buf_left, m_out_buf_ofs; mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; } tdefl_compressor; /* Initializes the compressor. */ /* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */ /* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ /* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ /* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); /* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ MINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); /* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ /* tdefl_compress_buffer() always consumes the entire input buffer. */ MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d); /* Create tdefl_compress() flags given zlib-style compression parameters. */ /* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ /* window_bits may be -15 (raw deflate) or 15 (zlib) */ /* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor structure in C so that */ /* non-C language bindings to tdefl_ API don't need to worry about */ /* structure size and allocation mechanism. */ MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void); MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp); #endif #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #pragma once /* ------------------- Low-level Decompression API Definitions */ #ifndef MINIZ_NO_INFLATE_APIS #ifdef __cplusplus extern "C" { #endif /* Decompression flags used by tinfl_decompress(). */ /* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ /* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ /* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ /* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ 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 }; /* High level decompression functions: */ /* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */ /* On entry: */ /* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */ /* On return: */ /* Function returns a pointer to the decompressed data, or NULL on failure. */ /* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must call mz_free() on the returned block when it's no longer needed. */ MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ /* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) MINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); /* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ /* Returns 1 on success or 0 on failure. */ typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); MINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; #ifndef MINIZ_NO_MALLOC /* Allocate the tinfl_decompressor structure in C so that */ /* non-C language bindings to tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void); MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp); #endif /* Max size of LZ dictionary. */ #define TINFL_LZ_DICT_SIZE 32768 /* Return status. */ typedef enum { /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ TINFL_STATUS_BAD_PARAM = -3, /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ TINFL_STATUS_ADLER32_MISMATCH = -2, /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ TINFL_STATUS_FAILED = -1, /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ TINFL_STATUS_DONE = 0, /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ TINFL_STATUS_NEEDS_MORE_INPUT = 1, /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ /* so I may need to add some code to address this. */ 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. */ MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); /* Internal/private bits follow. */ enum { 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 }; #if MINIZ_HAS_64BIT_REGISTERS #define TINFL_USE_64BIT_BITBUF 1 #else #define TINFL_USE_64BIT_BITBUF 0 #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; mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE]; mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2]; mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2]; mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0]; mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1]; mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2]; mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; #ifdef __cplusplus } #endif #endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ #pragma once /* ------------------- ZIP archive reading/writing */ #ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus extern "C" { #endif enum { /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 }; typedef struct { /* Central directory file index. */ mz_uint32 m_file_index; /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ mz_uint64 m_central_dir_ofs; /* These fields are copied directly from the zip's central dir. */ mz_uint16 m_version_made_by; mz_uint16 m_version_needed; mz_uint16 m_bit_flag; mz_uint16 m_method; /* CRC-32 of uncompressed data. */ mz_uint32 m_crc32; /* File's compressed size. */ mz_uint64 m_comp_size; /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ mz_uint64 m_uncomp_size; /* Zip internal and external file attributes. */ mz_uint16 m_internal_attr; mz_uint32 m_external_attr; /* Entry's local header file offset in bytes. */ mz_uint64 m_local_header_ofs; /* Size of comment in bytes. */ mz_uint32 m_comment_size; /* MZ_TRUE if the entry appears to be a directory. */ mz_bool m_is_directory; /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ mz_bool m_is_encrypted; /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ mz_bool m_is_supported; /* Filename. If string ends in '/' it's a subdirectory entry. */ /* Guaranteed to be zero terminated, may be truncated to fit. */ char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; /* Comment field. */ /* Guaranteed to be zero terminated, may be truncated to fit. */ char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; #ifdef MINIZ_NO_TIME MZ_TIME_T m_padding; #else MZ_TIME_T m_time; #endif } mz_zip_archive_file_stat; typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); struct mz_zip_internal_state_tag; typedef struct mz_zip_internal_state_tag mz_zip_internal_state; typedef enum { MZ_ZIP_MODE_INVALID = 0, MZ_ZIP_MODE_READING = 1, MZ_ZIP_MODE_WRITING = 2, MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 } mz_zip_mode; typedef enum { MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000, /*After adding a compressed file, seek back to local file header and set the correct sizes*/ MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000, MZ_ZIP_FLAG_READ_ALLOW_WRITING = 0x40000 } mz_zip_flags; typedef enum { MZ_ZIP_TYPE_INVALID = 0, MZ_ZIP_TYPE_USER, MZ_ZIP_TYPE_MEMORY, MZ_ZIP_TYPE_HEAP, MZ_ZIP_TYPE_FILE, MZ_ZIP_TYPE_CFILE, MZ_ZIP_TOTAL_TYPES } mz_zip_type; /* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ typedef enum { MZ_ZIP_NO_ERROR = 0, MZ_ZIP_UNDEFINED_ERROR, MZ_ZIP_TOO_MANY_FILES, MZ_ZIP_FILE_TOO_LARGE, MZ_ZIP_UNSUPPORTED_METHOD, MZ_ZIP_UNSUPPORTED_ENCRYPTION, MZ_ZIP_UNSUPPORTED_FEATURE, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, MZ_ZIP_NOT_AN_ARCHIVE, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, MZ_ZIP_UNSUPPORTED_MULTIDISK, MZ_ZIP_DECOMPRESSION_FAILED, MZ_ZIP_COMPRESSION_FAILED, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, MZ_ZIP_CRC_CHECK_FAILED, MZ_ZIP_UNSUPPORTED_CDIR_SIZE, MZ_ZIP_ALLOC_FAILED, MZ_ZIP_FILE_OPEN_FAILED, MZ_ZIP_FILE_CREATE_FAILED, MZ_ZIP_FILE_WRITE_FAILED, MZ_ZIP_FILE_READ_FAILED, MZ_ZIP_FILE_CLOSE_FAILED, MZ_ZIP_FILE_SEEK_FAILED, MZ_ZIP_FILE_STAT_FAILED, MZ_ZIP_INVALID_PARAMETER, MZ_ZIP_INVALID_FILENAME, MZ_ZIP_BUF_TOO_SMALL, MZ_ZIP_INTERNAL_ERROR, MZ_ZIP_FILE_NOT_FOUND, MZ_ZIP_ARCHIVE_TOO_LARGE, MZ_ZIP_VALIDATION_FAILED, MZ_ZIP_WRITE_CALLBACK_FAILED, MZ_ZIP_TOTAL_ERRORS } mz_zip_error; typedef struct { mz_uint64 m_archive_size; mz_uint64 m_central_directory_file_ofs; /* We only support up to UINT32_MAX files in zip64 mode. */ mz_uint32 m_total_files; mz_zip_mode m_zip_mode; mz_zip_type m_zip_type; mz_zip_error m_last_error; mz_uint64 m_file_offset_alignment; mz_alloc_func m_pAlloc; mz_free_func m_pFree; mz_realloc_func m_pRealloc; void *m_pAlloc_opaque; mz_file_read_func m_pRead; mz_file_write_func m_pWrite; mz_file_needs_keepalive m_pNeeds_keepalive; void *m_pIO_opaque; mz_zip_internal_state *m_pState; } mz_zip_archive; typedef struct { mz_zip_archive *pZip; mz_uint flags; int status; mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; mz_zip_archive_file_stat file_stat; void *pRead_buf; void *pWrite_buf; size_t out_blk_remain; tinfl_decompressor inflator; #ifdef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS mz_uint padding; #else mz_uint file_crc32; #endif } mz_zip_reader_extract_iter_state; /* -------- ZIP reading */ /* Inits a ZIP archive reader. */ /* These functions read and validate the archive's central directory. */ MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); #ifndef MINIZ_NO_STDIO /* Read a archive from a disk file. */ /* file_start_ofs is the file offset where the archive actually begins, or 0. */ /* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ MINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); /* Read an archive from an already opened FILE, beginning at the current file position. */ /* The archive is assumed to be archive_size bytes long. If archive_size is 0, then the entire rest of the file is assumed to contain the archive. */ /* The FILE will NOT be closed when mz_zip_reader_end() is called. */ MINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); #endif /* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip); /* -------- ZIP reading or writing */ /* Clears a mz_zip_archive struct to all zeros. */ /* Important: This must be done before passing the struct to any mz_zip functions. */ MINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip); MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); /* Returns the total number of files in the archive. */ MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); MINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); /* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); /* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ /* Note that the m_last_error functionality is not thread safe. */ MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err); /* MZ_TRUE if the archive file entry is a directory entry. */ MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); /* MZ_TRUE if the file is encrypted/strong encrypted. */ MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); /* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); /* Retrieves the filename of an archive file entry. */ /* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); /* Attempts to locates a file in the archive's central directory. */ /* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ /* Returns -1 if the file cannot be found. */ MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); /* Returns detailed information about an archive file entry. */ MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); /* MZ_TRUE if the file is in zip64 format. */ /* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); /* Returns the total central directory size in bytes. */ /* The current max supported size is <= MZ_UINT32_MAX. */ MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); /* Extracts a archive file to a memory buffer using no memory allocation. */ /* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); /* Extracts a archive file to a memory buffer. */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); /* Extracts a archive file to a dynamically allocated heap buffer. */ /* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ /* Returns NULL and sets the last error on failure. */ MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); /* Extracts a archive file using a callback function to output the file's data. */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); /* Extract a file iteratively */ MINIZ_EXPORT mz_zip_reader_extract_iter_state *mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); MINIZ_EXPORT mz_zip_reader_extract_iter_state *mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); MINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state *pState, void *pvBuf, size_t buf_size); MINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state *pState); #ifndef MINIZ_NO_STDIO /* Extracts a archive file to a disk file and sets its last accessed and modified times. */ /* This function only extracts files, not archive directory records. */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); /* Extracts a archive file starting at the current position in the destination FILE stream. */ MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); #endif #if 0 /* TODO */ typedef void *mz_zip_streaming_extract_state_ptr; mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); mz_uint64 mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); mz_uint64 mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, mz_uint64 new_ofs); size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); #endif /* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ /* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ MINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); /* Validates an entire archive by calling mz_zip_validate_file() on each file. */ MINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); /* Misc utils/helpers, valid for ZIP reading or writing */ MINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); #ifndef MINIZ_NO_STDIO MINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); #endif /* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip); /* -------- ZIP writing */ #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS /* Inits a ZIP archive writer. */ /*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ /*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ MINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); MINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); MINIZ_EXPORT mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); #ifndef MINIZ_NO_STDIO MINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); MINIZ_EXPORT mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); MINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); #endif /* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ /* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ /* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ /* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ /* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ /* the archive is finalized the file's central directory will be hosed. */ MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); /* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ /* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); /* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ /* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); /* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ /* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ MINIZ_EXPORT mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void *callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); #ifndef MINIZ_NO_STDIO /* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); /* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len); #endif /* Adds a file to an archive by fully cloning the data from another archive. */ /* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ MINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); /* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ /* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ /* An archive must be manually finalized by calling this function for it to be valid. */ MINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); /* Finalizes a heap archive, returning a pointer to the heap block and its size. */ /* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ MINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); /* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ /* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ MINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip); /* -------- Misc. high-level helper functions: */ /* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ /* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ /* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ /* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); #ifndef MINIZ_NO_STDIO /* Reads a single file from an archive into a heap block. */ /* If pComment is not NULL, only the file with the specified comment will be extracted. */ /* Returns NULL on failure. */ MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); #endif #endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ #ifdef __cplusplus } #endif #endif /* MINIZ_NO_ARCHIVE_APIS */ jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/miniz/readme.md000066400000000000000000000112601523150047600236350ustar00rootroot00000000000000## Miniz Miniz is a lossless, high performance data compression library in a single source file that implements the zlib (RFC 1950) and Deflate (RFC 1951) compressed data format specification standards. It supports the most commonly used functions exported by the zlib library, but is a completely independent implementation so zlib's licensing requirements do not apply. Miniz also contains simple to use functions for writing .PNG format image files and reading/writing/appending .ZIP format archives. Miniz's compression speed has been tuned to be comparable to zlib's, and it also has a specialized real-time compressor function designed to compare well against fastlz/minilzo. ## Usage Releases are available at the [releases page](https://github.com/richgel999/miniz/releases) as a pair of `miniz.c`/`miniz.h` files which can be simply added to a project. To create this file pair the different source and header files are [amalgamated](https://www.sqlite.org/amalgamation.html) during build. Alternatively use as cmake or meson module (or build system of your choice). ## Features * MIT licensed * A portable, single source and header file library written in plain C. Tested with GCC, clang and Visual Studio. * Easily tuned and trimmed down by defines * A drop-in replacement for zlib's most used API's (tested in several open source projects that use zlib, such as libpng and libzip). * Fills a single threaded performance vs. compression ratio gap between several popular real-time compressors and zlib. For example, at level 1, miniz.c compresses around 5-9% better than minilzo, but is approx. 35% slower. At levels 2-9, miniz.c is designed to compare favorably against zlib's ratio and speed. See the miniz performance comparison page for example timings. * Not a block based compressor: miniz.c fully supports stream based processing using a coroutine-style implementation. The zlib-style API functions can be called a single byte at a time if that's all you've got. * Easy to use. The low-level compressor (tdefl) and decompressor (tinfl) have simple state structs which can be saved/restored as needed with simple memcpy's. The low-level codec API's don't use the heap in any way. * Entire inflater (including optional zlib header parsing and Adler-32 checking) is implemented in a single function as a coroutine, which is separately available in a small (~550 line) source file: miniz_tinfl.c * A fairly complete (but totally optional) set of .ZIP archive manipulation and extraction API's. The archive functionality is intended to solve common problems encountered in embedded, mobile, or game development situations. (The archive API's are purposely just powerful enough to write an entire archiver given a bit of additional higher-level logic.) ## Building miniz - Using vcpkg You can download and install miniz using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg integrate install ./vcpkg install miniz The miniz port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. ## Known Problems * No support for encrypted archives. Not sure how useful this stuff is in practice. * Minimal documentation. The assumption is that the user is already familiar with the basic zlib API. I need to write an API wiki - for now I've tried to place key comments before each enum/API, and I've included 6 examples that demonstrate how to use the module's major features. ## Special Thanks Thanks to Alex Evans for the PNG writer function. Also, thanks to Paul Holden and Thorsten Scheuermann for feedback and testing, Matt Pritchard for all his encouragement, and Sean Barrett's various public domain libraries for inspiration (and encouraging me to write miniz.c in C, which was much more enjoyable and less painful than I thought it would be considering I've been programming in C++ for so long). Thanks to Bruce Dawson for reporting a problem with the level_and_flags archive API parameter (which is fixed in v1.12) and general feedback, and Janez Zemva for indirectly encouraging me into writing more examples. ## Patents I was recently asked if miniz avoids patent issues. miniz purposely uses the same core algorithms as the ones used by zlib. The compressor uses vanilla hash chaining as described [here](https://datatracker.ietf.org/doc/html/rfc1951#section-4). Also see the [gzip FAQ](https://web.archive.org/web/20160308045258/http://www.gzip.org/#faq11). In my opinion, if miniz falls prey to a patent attack then zlib/gzip are likely to be at serious risk too. jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/musl_getopt.c000066400000000000000000000146451523150047600234500ustar00rootroot00000000000000/* Copyright © 2011-2023 Rich Felker, et al. 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. */ #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include "musl_getopt.h" char *optarg; int optind=1, opterr=1, optopt, __optpos, optreset=0; #define optpos __optpos static void __getopt_msg(const char *a, const char *b, const char *c, size_t l) { FILE *f = stderr; #if !defined(__MINGW32__) && !defined(__MINGW64__) flockfile(f); #endif fputs(a, f); fwrite(b, strlen(b), 1, f); fwrite(c, 1, l, f); fputc('\n', f); #if !defined(__MINGW32__) && !defined(__MINGW64__) funlockfile(f); #endif } int getopt(int argc, char * const argv[], const char *optstring) { int i; wchar_t c, d; int k, l; char *optchar; if (!optind || optreset) { optreset = 0; __optpos = 0; optind = 1; } if (optind >= argc || !argv[optind]) return -1; if (argv[optind][0] != '-') { if (optstring[0] == '-') { optarg = argv[optind++]; return 1; } return -1; } if (!argv[optind][1]) return -1; if (argv[optind][1] == '-' && !argv[optind][2]) return optind++, -1; if (!optpos) optpos++; if ((k = mbtowc(&c, argv[optind]+optpos, MB_LEN_MAX)) < 0) { k = 1; c = 0xfffd; /* replacement char */ } optchar = argv[optind]+optpos; optpos += k; if (!argv[optind][optpos]) { optind++; optpos = 0; } if (optstring[0] == '-' || optstring[0] == '+') optstring++; i = 0; d = 0; do { l = mbtowc(&d, optstring+i, MB_LEN_MAX); if (l>0) i+=l; else i++; } while (l && d != c); if (d != c || c == ':') { optopt = c; if (optstring[0] != ':' && opterr) __getopt_msg(argv[0], ": unrecognized option: ", optchar, k); return '?'; } if (optstring[i] == ':') { optarg = 0; if (optstring[i+1] != ':' || optpos) { optarg = argv[optind++]; if (optpos) optarg += optpos; optpos = 0; } if (optind > argc) { optopt = c; if (optstring[0] == ':') return ':'; if (opterr) __getopt_msg(argv[0], ": option requires an argument: ", optchar, k); return '?'; } } return c; } static void permute(char *const *argv, int dest, int src) { char **av = (char **)argv; char *tmp = av[src]; int i; for (i=src; i>dest; i--) av[i] = av[i-1]; av[dest] = tmp; } static int __getopt_long_core(int argc, char *const *argv, const char *optstring, const struct option *longopts, int *idx, int longonly) { optarg = 0; if (longopts && argv[optind][0] == '-' && ((longonly && argv[optind][1] && argv[optind][1] != '-') || (argv[optind][1] == '-' && argv[optind][2]))) { int colon = optstring[optstring[0]=='+'||optstring[0]=='-']==':'; int i, cnt, match; char *arg, *opt, *start = argv[optind]+1; for (cnt=i=0; longopts[i].name; i++) { const char *name = longopts[i].name; opt = start; if (*opt == '-') opt++; while (*opt && *opt != '=' && *opt == *name) name++, opt++; if (*opt && *opt != '=') continue; arg = opt; match = i; if (!*name) { cnt = 1; break; } cnt++; } if (cnt==1 && longonly && arg-start == mblen(start, MB_LEN_MAX)) { int l = arg-start; for (i=0; optstring[i]; i++) { int j; for (j=0; j= argc || !argv[optind]) return -1; skipped = optind; if (optstring[0] != '+' && optstring[0] != '-') { int i; for (i=optind; ; i++) { if (i >= argc || !argv[i]) return -1; if (argv[i][0] == '-' && argv[i][1]) break; } optind = i; } resumed = optind; ret = __getopt_long_core(argc, argv, optstring, longopts, idx, longonly); if (resumed > skipped) { int i, cnt = optind-resumed; for (i=0; i #include #include "musl_memmem.h" static char *twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) { uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++) if (hw == nw) return (char *)h-2; return hw == nw ? (char *)h-2 : 0; } static char *threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) { uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8; uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8; for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8) if (hw == nw) return (char *)h-3; return hw == nw ? (char *)h-3 : 0; } static char *fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) { uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++) if (hw == nw) return (char *)h-4; return hw == nw ? (char *)h-4 : 0; } #define MAX(a,b) ((a)>(b)?(a):(b)) #define MIN(a,b) ((a)<(b)?(a):(b)) #define BITOP(a,b,op) \ ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) static char *twoway_memmem(const unsigned char *h, const unsigned char *z, const unsigned char *n, size_t l) { size_t i, ip, jp, k, p, ms, p0, mem, mem0; size_t byteset[32 / sizeof(size_t)] = { 0 }; size_t shift[256]; /* Computing length of needle and fill shift table */ for (i=0; i n[jp+k]) { jp += k; k = 1; p = jp - ip; } else { ip = jp++; k = p = 1; } } ms = ip; p0 = p; /* And with the opposite comparison */ ip = -1; jp = 0; k = p = 1; while (jp+k ms+1) ms = ip; else p = p0; /* Periodic needle? */ if (memcmp(n, n+p, ms+1)) { mem0 = 0; p = MAX(ms, l-ms-1) + 1; } else mem0 = l-p; mem = 0; /* Search loop */ for (;;) { /* If remainder of haystack is shorter than needle, done */ if ((size_t)(z-h) < l) return 0; /* Check last byte first; advance by shift on mismatch */ if (BITOP(byteset, h[l-1], &)) { k = l-shift[h[l-1]]; if (k) { if (k < mem) k = mem; h += k; mem = 0; continue; } } else { h += l; mem = 0; continue; } /* Compare right half */ for (k=MAX(ms+1,mem); kmem && n[k-1] == h[k-1]; k--); if (k <= mem) return (char *)h; h += p; mem = mem0; } } void *mmemmem(const void *h0, size_t k, const void *n0, size_t l) { const unsigned char *h = h0, *n = n0; /* Return immediately on empty needle */ if (!l) return (void *)h; /* Return immediately when needle is longer than haystack */ if (k #include #include #include #include #include #include /* Apparently sscanf is not implemented in some "standard" libraries, so don't use it, if you * don't have to. */ #ifdef sscanf #undef sscanf #define sscanf THINK_TWICE_ABOUT_USING_SSCANF #endif /* strcpy is unsafe */ #ifdef strcpy #undef strcpy #endif #define strcpy USE_MEMCPY_INSTEAD_OF_STRCPY #define STARTING_CAPACITY 16 #define MAX_NESTING 2048 #ifndef PARSON_DEFAULT_FLOAT_FORMAT #define PARSON_DEFAULT_FLOAT_FORMAT "%1.17g" /* do not increase precision without incresing NUM_BUF_SIZE */ #endif #ifndef PARSON_NUM_BUF_SIZE #define PARSON_NUM_BUF_SIZE 64 /* double printed with "%1.17g" shouldn't be longer than 25 bytes so let's be paranoid and use 64 */ #endif #ifndef PARSON_INDENT_STR #define PARSON_INDENT_STR " " #endif #define SIZEOF_TOKEN(a) (sizeof(a) - 1) #define SKIP_CHAR(str) ((*str)++) #define SKIP_WHITESPACES(str) while (isspace((unsigned char)(**str))) { SKIP_CHAR(str); } #define MAX(a, b) ((a) > (b) ? (a) : (b)) #undef malloc #undef free #if defined(isnan) && defined(isinf) #define IS_NUMBER_INVALID(x) (isnan((x)) || isinf((x))) #else #define IS_NUMBER_INVALID(x) (((x) * 0.0) != 0.0) #endif #define OBJECT_INVALID_IX ((size_t)-1) static JSON_Malloc_Function parson_malloc = malloc; static JSON_Free_Function parson_free = free; static int parson_escape_slashes = 1; static char *parson_float_format = NULL; static JSON_Number_Serialization_Function parson_number_serialization_function = NULL; #define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */ typedef int parson_bool_t; #define PARSON_TRUE 1 #define PARSON_FALSE 0 typedef struct json_string { char *chars; size_t length; } JSON_String; /* Type definitions */ typedef union json_value_value { JSON_String string; double number; JSON_Object *object; JSON_Array *array; int boolean; int null; } JSON_Value_Value; struct json_value_t { JSON_Value *parent; JSON_Value_Type type; JSON_Value_Value value; }; struct json_object_t { JSON_Value *wrapping_value; size_t *cells; unsigned long *hashes; char **names; JSON_Value **values; size_t *cell_ixs; size_t count; size_t item_capacity; size_t cell_capacity; }; struct json_array_t { JSON_Value *wrapping_value; JSON_Value **items; size_t count; size_t capacity; }; /* Various */ static char * read_file(const char *filename); static void remove_comments(char *string, const char *start_token, const char *end_token); static char * parson_strndup(const char *string, size_t n); static char * parson_strdup(const char *string); static int parson_snprintf(char * s, size_t sz, const char * format, ...); static int hex_char_to_int(char c); static JSON_Status parse_utf16_hex(const char *string, unsigned int *result); static int num_bytes_in_utf8_sequence(unsigned char c); static JSON_Status verify_utf8_sequence(const unsigned char *string, int *len); static parson_bool_t is_valid_utf8(const char *string, size_t string_len); static parson_bool_t is_decimal(const char *string, size_t length); static unsigned long hash_string(const char *string, size_t n); /* JSON Object */ static JSON_Object * json_object_make(JSON_Value *wrapping_value); static JSON_Status json_object_init(JSON_Object *object, size_t capacity); static void json_object_deinit(JSON_Object *object, parson_bool_t free_keys, parson_bool_t free_values); static JSON_Status json_object_grow_and_rehash(JSON_Object *object); static size_t json_object_get_cell_ix(const JSON_Object *object, const char *key, size_t key_len, unsigned long hash, parson_bool_t *out_found); static JSON_Status json_object_add(JSON_Object *object, char *name, JSON_Value *value); static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len); static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, parson_bool_t free_value); static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, parson_bool_t free_value); static void json_object_free(JSON_Object *object); /* JSON Array */ static JSON_Array * json_array_make(JSON_Value *wrapping_value); static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value); static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity); static void json_array_free(JSON_Array *array); /* JSON Value */ static JSON_Value * json_value_init_string_no_copy(char *string, size_t length); static const JSON_String * json_value_get_string_desc(const JSON_Value *value); /* Parser */ static JSON_Status skip_quotes(const char **string); static JSON_Status parse_utf16(const char **unprocessed, char **processed); static char * process_string(const char *input, size_t input_len, size_t *output_len); static char * get_quoted_string(const char **string, size_t *output_string_len); static JSON_Value * parse_object_value(const char **string, size_t nesting); static JSON_Value * parse_array_value(const char **string, size_t nesting); static JSON_Value * parse_string_value(const char **string); static JSON_Value * parse_boolean_value(const char **string); static JSON_Value * parse_number_value(const char **string); static JSON_Value * parse_null_value(const char **string); static JSON_Value * parse_value(const char **string, size_t nesting); /* Serialization */ static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, parson_bool_t is_pretty, char *num_buf); static int json_serialize_string(const char *string, size_t len, char *buf); /* Various */ static char * read_file(const char * filename) { FILE *fp = fopen(filename, "r"); size_t size_to_read = 0; size_t size_read = 0; long pos; char *file_contents; if (!fp) { return NULL; } fseek(fp, 0L, SEEK_END); pos = ftell(fp); if (pos < 0) { fclose(fp); return NULL; } size_to_read = pos; rewind(fp); file_contents = (char*)parson_malloc(sizeof(char) * (size_to_read + 1)); if (!file_contents) { fclose(fp); return NULL; } size_read = fread(file_contents, 1, size_to_read, fp); if (size_read == 0 || ferror(fp)) { fclose(fp); parson_free(file_contents); return NULL; } fclose(fp); file_contents[size_read] = '\0'; return file_contents; } static void remove_comments(char *string, const char *start_token, const char *end_token) { parson_bool_t in_string = PARSON_FALSE, escaped = PARSON_FALSE; size_t i; char *ptr = NULL, current_char; size_t start_token_len = strlen(start_token); size_t end_token_len = strlen(end_token); if (start_token_len == 0 || end_token_len == 0) { return; } while ((current_char = *string) != '\0') { if (current_char == '\\' && !escaped) { escaped = PARSON_TRUE; string++; continue; } else if (current_char == '\"' && !escaped) { in_string = !in_string; } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) { for(i = 0; i < start_token_len; i++) { string[i] = ' '; } string = string + start_token_len; ptr = strstr(string, end_token); if (!ptr) { return; } for (i = 0; i < (ptr - string) + end_token_len; i++) { string[i] = ' '; } string = ptr + end_token_len - 1; } escaped = PARSON_FALSE; string++; } } static char * parson_strndup(const char *string, size_t n) { /* We expect the caller has validated that 'n' fits within the input buffer. */ char *output_string = (char*)parson_malloc(n + 1); if (!output_string) { return NULL; } output_string[n] = '\0'; memcpy(output_string, string, n); return output_string; } static char * parson_strdup(const char *string) { return parson_strndup(string, strlen(string)); } static int parson_snprintf(char * s, size_t sz, const char * format, ...) { int result; va_list args; va_start(args, format); #if defined(__APPLE__) && defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif result = vsnprintf(s, sz, format, args); #if defined(__APPLE__) && defined(__clang__) #pragma clang diagnostic pop #endif va_end(args); return result; } static int hex_char_to_int(char c) { if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } else if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } return -1; } static JSON_Status parse_utf16_hex(const char *s, unsigned int *result) { int x1, x2, x3, x4; if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0' || s[3] == '\0') { return JSONFailure; } x1 = hex_char_to_int(s[0]); x2 = hex_char_to_int(s[1]); x3 = hex_char_to_int(s[2]); x4 = hex_char_to_int(s[3]); if (x1 == -1 || x2 == -1 || x3 == -1 || x4 == -1) { return JSONFailure; } *result = (unsigned int)((x1 << 12) | (x2 << 8) | (x3 << 4) | x4); return JSONSuccess; } static int num_bytes_in_utf8_sequence(unsigned char c) { if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) { return 0; } else if ((c & 0x80) == 0) { /* 0xxxxxxx */ return 1; } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */ return 2; } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */ return 3; } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */ return 4; } return 0; /* won't happen */ } static JSON_Status verify_utf8_sequence(const unsigned char *string, int *len) { unsigned int cp = 0; *len = num_bytes_in_utf8_sequence(string[0]); if (*len == 1) { cp = string[0]; } else if (*len == 2 && IS_CONT(string[1])) { cp = string[0] & 0x1F; cp = (cp << 6) | (string[1] & 0x3F); } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) { cp = ((unsigned char)string[0]) & 0xF; cp = (cp << 6) | (string[1] & 0x3F); cp = (cp << 6) | (string[2] & 0x3F); } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) { cp = string[0] & 0x7; cp = (cp << 6) | (string[1] & 0x3F); cp = (cp << 6) | (string[2] & 0x3F); cp = (cp << 6) | (string[3] & 0x3F); } else { return JSONFailure; } /* overlong encodings */ if ((cp < 0x80 && *len > 1) || (cp < 0x800 && *len > 2) || (cp < 0x10000 && *len > 3)) { return JSONFailure; } /* invalid unicode */ if (cp > 0x10FFFF) { return JSONFailure; } /* surrogate halves */ if (cp >= 0xD800 && cp <= 0xDFFF) { return JSONFailure; } return JSONSuccess; } static int is_valid_utf8(const char *string, size_t string_len) { int len = 0; const char *string_end = string + string_len; while (string < string_end) { if (verify_utf8_sequence((const unsigned char*)string, &len) != JSONSuccess) { return PARSON_FALSE; } string += len; } return PARSON_TRUE; } static parson_bool_t is_decimal(const char *string, size_t length) { if (length > 1 && string[0] == '0' && string[1] != '.') { return PARSON_FALSE; } if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') { return PARSON_FALSE; } while (length--) { if (strchr("xX", string[length])) { return PARSON_FALSE; } } return PARSON_TRUE; } static unsigned long hash_string(const char *string, size_t n) { #ifdef PARSON_FORCE_HASH_COLLISIONS (void)string; (void)n; return 0; #else unsigned long hash = 5381; unsigned char c; size_t i = 0; for (i = 0; i < n; i++) { c = string[i]; if (c == '\0') { break; } hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ } return hash; #endif } /* JSON Object */ static JSON_Object * json_object_make(JSON_Value *wrapping_value) { JSON_Status res = JSONFailure; JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object)); if (new_obj == NULL) { return NULL; } new_obj->wrapping_value = wrapping_value; res = json_object_init(new_obj, 0); if (res != JSONSuccess) { parson_free(new_obj); return NULL; } return new_obj; } static JSON_Status json_object_init(JSON_Object *object, size_t capacity) { unsigned int i = 0; object->cells = NULL; object->names = NULL; object->values = NULL; object->cell_ixs = NULL; object->hashes = NULL; object->count = 0; object->cell_capacity = capacity; object->item_capacity = (unsigned int)(capacity * 7/10); if (capacity == 0) { return JSONSuccess; } object->cells = (size_t*)parson_malloc(object->cell_capacity * sizeof(*object->cells)); object->names = (char**)parson_malloc(object->item_capacity * sizeof(*object->names)); object->values = (JSON_Value**)parson_malloc(object->item_capacity * sizeof(*object->values)); object->cell_ixs = (size_t*)parson_malloc(object->item_capacity * sizeof(*object->cell_ixs)); object->hashes = (unsigned long*)parson_malloc(object->item_capacity * sizeof(*object->hashes)); if (object->cells == NULL || object->names == NULL || object->values == NULL || object->cell_ixs == NULL || object->hashes == NULL) { goto error; } for (i = 0; i < object->cell_capacity; i++) { object->cells[i] = OBJECT_INVALID_IX; } return JSONSuccess; error: parson_free(object->cells); parson_free(object->names); parson_free(object->values); parson_free(object->cell_ixs); parson_free(object->hashes); return JSONFailure; } static void json_object_deinit(JSON_Object *object, parson_bool_t free_keys, parson_bool_t free_values) { unsigned int i = 0; for (i = 0; i < object->count; i++) { if (free_keys) { parson_free(object->names[i]); } if (free_values) { json_value_free(object->values[i]); } } object->count = 0; object->item_capacity = 0; object->cell_capacity = 0; parson_free(object->cells); parson_free(object->names); parson_free(object->values); parson_free(object->cell_ixs); parson_free(object->hashes); object->cells = NULL; object->names = NULL; object->values = NULL; object->cell_ixs = NULL; object->hashes = NULL; } static JSON_Status json_object_grow_and_rehash(JSON_Object *object) { JSON_Value *wrapping_value = NULL; JSON_Object new_object; char *key = NULL; JSON_Value *value = NULL; unsigned int i = 0; size_t new_capacity = MAX(object->cell_capacity * 2, STARTING_CAPACITY); JSON_Status res = json_object_init(&new_object, new_capacity); if (res != JSONSuccess) { return JSONFailure; } wrapping_value = json_object_get_wrapping_value(object); new_object.wrapping_value = wrapping_value; for (i = 0; i < object->count; i++) { key = object->names[i]; value = object->values[i]; res = json_object_add(&new_object, key, value); if (res != JSONSuccess) { json_object_deinit(&new_object, PARSON_FALSE, PARSON_FALSE); return JSONFailure; } value->parent = wrapping_value; } json_object_deinit(object, PARSON_FALSE, PARSON_FALSE); *object = new_object; return JSONSuccess; } static size_t json_object_get_cell_ix(const JSON_Object *object, const char *key, size_t key_len, unsigned long hash, parson_bool_t *out_found) { size_t cell_ix = hash & (object->cell_capacity - 1); size_t cell = 0; size_t ix = 0; unsigned int i = 0; unsigned long hash_to_check = 0; const char *key_to_check = NULL; size_t key_to_check_len = 0; *out_found = PARSON_FALSE; for (i = 0; i < object->cell_capacity; i++) { ix = (cell_ix + i) & (object->cell_capacity - 1); cell = object->cells[ix]; if (cell == OBJECT_INVALID_IX) { return ix; } hash_to_check = object->hashes[cell]; if (hash != hash_to_check) { continue; } key_to_check = object->names[cell]; key_to_check_len = strlen(key_to_check); if (key_to_check_len == key_len && strncmp(key, key_to_check, key_len) == 0) { *out_found = PARSON_TRUE; return ix; } } return OBJECT_INVALID_IX; } static JSON_Status json_object_add(JSON_Object *object, char *name, JSON_Value *value) { unsigned long hash = 0; parson_bool_t found = PARSON_FALSE; size_t cell_ix = 0; JSON_Status res = JSONFailure; if (!object || !name || !value) { return JSONFailure; } hash = hash_string(name, strlen(name)); found = PARSON_FALSE; cell_ix = json_object_get_cell_ix(object, name, strlen(name), hash, &found); if (found) { return JSONFailure; } if (object->count >= object->item_capacity) { res = json_object_grow_and_rehash(object); if (res != JSONSuccess) { return JSONFailure; } cell_ix = json_object_get_cell_ix(object, name, strlen(name), hash, &found); } object->names[object->count] = name; object->cells[cell_ix] = object->count; object->values[object->count] = value; object->cell_ixs[object->count] = cell_ix; object->hashes[object->count] = hash; object->count++; value->parent = json_object_get_wrapping_value(object); return JSONSuccess; } static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len) { unsigned long hash = 0; parson_bool_t found = PARSON_FALSE; size_t cell_ix = 0; size_t item_ix = 0; if (!object || !name) { return NULL; } hash = hash_string(name, name_len); found = PARSON_FALSE; cell_ix = json_object_get_cell_ix(object, name, name_len, hash, &found); if (!found) { return NULL; } item_ix = object->cells[cell_ix]; return object->values[item_ix]; } static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, parson_bool_t free_value) { unsigned long hash = 0; parson_bool_t found = PARSON_FALSE; size_t cell = 0; size_t item_ix = 0; size_t last_item_ix = 0; size_t i = 0; size_t j = 0; size_t x = 0; size_t k = 0; JSON_Value *val = NULL; if (object == NULL) { return JSONFailure; } hash = hash_string(name, strlen(name)); found = PARSON_FALSE; cell = json_object_get_cell_ix(object, name, strlen(name), hash, &found); if (!found) { return JSONFailure; } item_ix = object->cells[cell]; if (free_value) { val = object->values[item_ix]; json_value_free(val); val = NULL; } parson_free(object->names[item_ix]); last_item_ix = object->count - 1; if (item_ix < last_item_ix) { object->names[item_ix] = object->names[last_item_ix]; object->values[item_ix] = object->values[last_item_ix]; object->cell_ixs[item_ix] = object->cell_ixs[last_item_ix]; object->hashes[item_ix] = object->hashes[last_item_ix]; object->cells[object->cell_ixs[item_ix]] = item_ix; } object->count--; i = cell; j = i; for (x = 0; x < (object->cell_capacity - 1); x++) { j = (j + 1) & (object->cell_capacity - 1); if (object->cells[j] == OBJECT_INVALID_IX) { break; } k = object->hashes[object->cells[j]] & (object->cell_capacity - 1); if ((j > i && (k <= i || k > j)) || (j < i && (k <= i && k > j))) { object->cell_ixs[object->cells[j]] = i; object->cells[i] = object->cells[j]; i = j; } } object->cells[i] = OBJECT_INVALID_IX; return JSONSuccess; } static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, parson_bool_t free_value) { JSON_Value *temp_value = NULL; JSON_Object *temp_object = NULL; const char *dot_pos = strchr(name, '.'); if (!dot_pos) { return json_object_remove_internal(object, name, free_value); } temp_value = json_object_getn_value(object, name, dot_pos - name); if (json_value_get_type(temp_value) != JSONObject) { return JSONFailure; } temp_object = json_value_get_object(temp_value); return json_object_dotremove_internal(temp_object, dot_pos + 1, free_value); } static void json_object_free(JSON_Object *object) { json_object_deinit(object, PARSON_TRUE, PARSON_TRUE); parson_free(object); } /* JSON Array */ static JSON_Array * json_array_make(JSON_Value *wrapping_value) { JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array)); if (new_array == NULL) { return NULL; } new_array->wrapping_value = wrapping_value; new_array->items = (JSON_Value**)NULL; new_array->capacity = 0; new_array->count = 0; return new_array; } static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) { if (array->count >= array->capacity) { size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); if (json_array_resize(array, new_capacity) != JSONSuccess) { return JSONFailure; } } value->parent = json_array_get_wrapping_value(array); array->items[array->count] = value; array->count++; return JSONSuccess; } static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) { JSON_Value **new_items = NULL; if (new_capacity == 0) { return JSONFailure; } new_items = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*)); if (new_items == NULL) { return JSONFailure; } if (array->items != NULL && array->count > 0) { memcpy(new_items, array->items, array->count * sizeof(JSON_Value*)); } parson_free(array->items); array->items = new_items; array->capacity = new_capacity; return JSONSuccess; } static void json_array_free(JSON_Array *array) { size_t i; for (i = 0; i < array->count; i++) { json_value_free(array->items[i]); } parson_free(array->items); parson_free(array); } /* JSON Value */ static JSON_Value * json_value_init_string_no_copy(char *string, size_t length) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONString; new_value->value.string.chars = string; new_value->value.string.length = length; return new_value; } /* Parser */ static JSON_Status skip_quotes(const char **string) { if (**string != '\"') { return JSONFailure; } SKIP_CHAR(string); while (**string != '\"') { if (**string == '\0') { return JSONFailure; } else if (**string == '\\') { SKIP_CHAR(string); if (**string == '\0') { return JSONFailure; } } SKIP_CHAR(string); } SKIP_CHAR(string); return JSONSuccess; } static JSON_Status parse_utf16(const char **unprocessed, char **processed) { unsigned int cp, lead, trail; char *processed_ptr = *processed; const char *unprocessed_ptr = *unprocessed; JSON_Status status = JSONFailure; unprocessed_ptr++; /* skips u */ status = parse_utf16_hex(unprocessed_ptr, &cp); if (status != JSONSuccess) { return JSONFailure; } if (cp < 0x80) { processed_ptr[0] = (char)cp; /* 0xxxxxxx */ } else if (cp < 0x800) { processed_ptr[0] = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */ processed_ptr[1] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr += 1; } else if (cp < 0xD800 || cp > 0xDFFF) { processed_ptr[0] = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */ processed_ptr[1] = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr[2] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr += 2; } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */ lead = cp; unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */ if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u') { return JSONFailure; } status = parse_utf16_hex(unprocessed_ptr, &trail); if (status != JSONSuccess || trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */ return JSONFailure; } cp = ((((lead - 0xD800) & 0x3FF) << 10) | ((trail - 0xDC00) & 0x3FF)) + 0x010000; processed_ptr[0] = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */ processed_ptr[1] = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr[2] = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr[3] = (((cp) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr += 3; } else { /* trail surrogate before lead surrogate */ return JSONFailure; } unprocessed_ptr += 3; *processed = processed_ptr; *unprocessed = unprocessed_ptr; return JSONSuccess; } /* Copies and processes passed string up to supplied length. Example: "\u006Corem ipsum" -> lorem ipsum */ static char* process_string(const char *input, size_t input_len, size_t *output_len) { const char *input_ptr = input; size_t initial_size = (input_len + 1) * sizeof(char); size_t final_size = 0; char *output = NULL, *output_ptr = NULL, *resized_output = NULL; output = (char*)parson_malloc(initial_size); if (output == NULL) { goto error; } output_ptr = output; while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < input_len) { if (*input_ptr == '\\') { input_ptr++; switch (*input_ptr) { case '\"': *output_ptr = '\"'; break; case '\\': *output_ptr = '\\'; break; case '/': *output_ptr = '/'; break; case 'b': *output_ptr = '\b'; break; case 'f': *output_ptr = '\f'; break; case 'n': *output_ptr = '\n'; break; case 'r': *output_ptr = '\r'; break; case 't': *output_ptr = '\t'; break; case 'u': if (parse_utf16(&input_ptr, &output_ptr) != JSONSuccess) { goto error; } break; default: goto error; } } else if ((unsigned char)*input_ptr < 0x20) { goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ } else { *output_ptr = *input_ptr; } output_ptr++; input_ptr++; } *output_ptr = '\0'; /* resize to new length */ final_size = (size_t)(output_ptr-output) + 1; /* todo: don't resize if final_size == initial_size */ resized_output = (char*)parson_malloc(final_size); if (resized_output == NULL) { goto error; } memcpy(resized_output, output, final_size); *output_len = final_size - 1; parson_free(output); return resized_output; error: parson_free(output); return NULL; } /* Return processed contents of a string between quotes and skips passed argument to a matching quote. */ static char * get_quoted_string(const char **string, size_t *output_string_len) { const char *string_start = *string; size_t input_string_len = 0; JSON_Status status = skip_quotes(string); if (status != JSONSuccess) { return NULL; } input_string_len = *string - string_start - 2; /* length without quotes */ return process_string(string_start + 1, input_string_len, output_string_len); } static JSON_Value * parse_value(const char **string, size_t nesting) { if (nesting > MAX_NESTING) { return NULL; } SKIP_WHITESPACES(string); switch (**string) { case '{': return parse_object_value(string, nesting + 1); case '[': return parse_array_value(string, nesting + 1); case '\"': return parse_string_value(string); case 'f': case 't': return parse_boolean_value(string); case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return parse_number_value(string); case 'n': return parse_null_value(string); default: return NULL; } } static JSON_Value * parse_object_value(const char **string, size_t nesting) { JSON_Status status = JSONFailure; JSON_Value *output_value = NULL, *new_value = NULL; JSON_Object *output_object = NULL; char *new_key = NULL; output_value = json_value_init_object(); if (output_value == NULL) { return NULL; } if (**string != '{') { json_value_free(output_value); return NULL; } output_object = json_value_get_object(output_value); SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == '}') { /* empty object */ SKIP_CHAR(string); return output_value; } while (**string != '\0') { size_t key_len = 0; new_key = get_quoted_string(string, &key_len); /* We do not support key names with embedded \0 chars */ if (!new_key) { json_value_free(output_value); return NULL; } if (key_len != strlen(new_key)) { parson_free(new_key); json_value_free(output_value); return NULL; } SKIP_WHITESPACES(string); if (**string != ':') { parson_free(new_key); json_value_free(output_value); return NULL; } SKIP_CHAR(string); new_value = parse_value(string, nesting); if (new_value == NULL) { parson_free(new_key); json_value_free(output_value); return NULL; } status = json_object_add(output_object, new_key, new_value); if (status != JSONSuccess) { parson_free(new_key); json_value_free(new_value); json_value_free(output_value); return NULL; } SKIP_WHITESPACES(string); if (**string != ',') { break; } SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == '}') { break; } } SKIP_WHITESPACES(string); if (**string != '}') { json_value_free(output_value); return NULL; } SKIP_CHAR(string); return output_value; } static JSON_Value * parse_array_value(const char **string, size_t nesting) { JSON_Value *output_value = NULL, *new_array_value = NULL; JSON_Array *output_array = NULL; output_value = json_value_init_array(); if (output_value == NULL) { return NULL; } if (**string != '[') { json_value_free(output_value); return NULL; } output_array = json_value_get_array(output_value); SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == ']') { /* empty array */ SKIP_CHAR(string); return output_value; } while (**string != '\0') { new_array_value = parse_value(string, nesting); if (new_array_value == NULL) { json_value_free(output_value); return NULL; } if (json_array_add(output_array, new_array_value) != JSONSuccess) { json_value_free(new_array_value); json_value_free(output_value); return NULL; } SKIP_WHITESPACES(string); if (**string != ',') { break; } SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == ']') { break; } } SKIP_WHITESPACES(string); if (**string != ']' || /* Trim array after parsing is over */ json_array_resize(output_array, json_array_get_count(output_array)) != JSONSuccess) { json_value_free(output_value); return NULL; } SKIP_CHAR(string); return output_value; } static JSON_Value * parse_string_value(const char **string) { JSON_Value *value = NULL; size_t new_string_len = 0; char *new_string = get_quoted_string(string, &new_string_len); if (new_string == NULL) { return NULL; } value = json_value_init_string_no_copy(new_string, new_string_len); if (value == NULL) { parson_free(new_string); return NULL; } return value; } static JSON_Value * parse_boolean_value(const char **string) { size_t true_token_size = SIZEOF_TOKEN("true"); size_t false_token_size = SIZEOF_TOKEN("false"); if (strncmp("true", *string, true_token_size) == 0) { *string += true_token_size; return json_value_init_boolean(1); } else if (strncmp("false", *string, false_token_size) == 0) { *string += false_token_size; return json_value_init_boolean(0); } return NULL; } static JSON_Value * parse_number_value(const char **string) { char *end; double number = 0; errno = 0; number = strtod(*string, &end); if (errno == ERANGE && (number <= -HUGE_VAL || number >= HUGE_VAL)) { return NULL; } if ((errno && errno != ERANGE) || !is_decimal(*string, end - *string)) { return NULL; } *string = end; return json_value_init_number(number); } static JSON_Value * parse_null_value(const char **string) { size_t token_size = SIZEOF_TOKEN("null"); if (strncmp("null", *string, token_size) == 0) { *string += token_size; return json_value_init_null(); } return NULL; } /* Serialization */ /* APPEND_STRING() is only called on string literals. It's a bit hacky because it makes plenty of assumptions about the external state and should eventually be tidied up into a function (same goes for APPEND_INDENT) */ #define APPEND_STRING(str) do {\ written = SIZEOF_TOKEN((str));\ if (buf != NULL) {\ memcpy(buf, (str), written);\ buf[written] = '\0';\ buf += written;\ }\ written_total += written;\ } while (0) #define APPEND_INDENT(level) do {\ int level_i = 0;\ for (level_i = 0; level_i < (level); level_i++) {\ APPEND_STRING(PARSON_INDENT_STR);\ }\ } while (0) static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, parson_bool_t is_pretty, char *num_buf) { const char *key = NULL, *string = NULL; JSON_Value *temp_value = NULL; JSON_Array *array = NULL; JSON_Object *object = NULL; size_t i = 0, count = 0; double num = 0.0; int written = -1, written_total = 0; size_t len = 0; switch (json_value_get_type(value)) { case JSONArray: array = json_value_get_array(value); count = json_array_get_count(array); APPEND_STRING("["); if (count > 0 && is_pretty) { APPEND_STRING("\n"); } for (i = 0; i < count; i++) { if (is_pretty) { APPEND_INDENT(level+1); } temp_value = json_array_get_value(array, i); written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; if (i < (count - 1)) { APPEND_STRING(","); } if (is_pretty) { APPEND_STRING("\n"); } } if (count > 0 && is_pretty) { APPEND_INDENT(level); } APPEND_STRING("]"); return written_total; case JSONObject: object = json_value_get_object(value); count = json_object_get_count(object); APPEND_STRING("{"); if (count > 0 && is_pretty) { APPEND_STRING("\n"); } for (i = 0; i < count; i++) { key = json_object_get_name(object, i); if (key == NULL) { return -1; } if (is_pretty) { APPEND_INDENT(level+1); } /* We do not support key names with embedded \0 chars */ written = json_serialize_string(key, strlen(key), buf); if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; APPEND_STRING(":"); if (is_pretty) { APPEND_STRING(" "); } temp_value = json_object_get_value_at(object, i); written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf); if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; if (i < (count - 1)) { APPEND_STRING(","); } if (is_pretty) { APPEND_STRING("\n"); } } if (count > 0 && is_pretty) { APPEND_INDENT(level); } APPEND_STRING("}"); return written_total; case JSONString: string = json_value_get_string(value); if (string == NULL) { return -1; } len = json_value_get_string_len(value); written = json_serialize_string(string, len, buf); if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; return written_total; case JSONBoolean: if (json_value_get_boolean(value)) { APPEND_STRING("true"); } else { APPEND_STRING("false"); } return written_total; case JSONNumber: num = json_value_get_number(value); if (buf != NULL) { num_buf = buf; } if (parson_number_serialization_function) { written = parson_number_serialization_function(num, num_buf); } else { const char *float_format = parson_float_format ? parson_float_format : PARSON_DEFAULT_FLOAT_FORMAT; written = parson_snprintf(num_buf, PARSON_NUM_BUF_SIZE, float_format, num); } if (written < 0) { return -1; } if (buf != NULL) { buf += written; } written_total += written; return written_total; case JSONNull: APPEND_STRING("null"); return written_total; case JSONError: return -1; default: return -1; } } static int json_serialize_string(const char *string, size_t len, char *buf) { size_t i = 0; char c = '\0'; int written = -1, written_total = 0; APPEND_STRING("\""); for (i = 0; i < len; i++) { c = string[i]; switch (c) { case '\"': APPEND_STRING("\\\""); break; case '\\': APPEND_STRING("\\\\"); break; case '\b': APPEND_STRING("\\b"); break; case '\f': APPEND_STRING("\\f"); break; case '\n': APPEND_STRING("\\n"); break; case '\r': APPEND_STRING("\\r"); break; case '\t': APPEND_STRING("\\t"); break; case '\x00': APPEND_STRING("\\u0000"); break; case '\x01': APPEND_STRING("\\u0001"); break; case '\x02': APPEND_STRING("\\u0002"); break; case '\x03': APPEND_STRING("\\u0003"); break; case '\x04': APPEND_STRING("\\u0004"); break; case '\x05': APPEND_STRING("\\u0005"); break; case '\x06': APPEND_STRING("\\u0006"); break; case '\x07': APPEND_STRING("\\u0007"); break; /* '\x08' duplicate: '\b' */ /* '\x09' duplicate: '\t' */ /* '\x0a' duplicate: '\n' */ case '\x0b': APPEND_STRING("\\u000b"); break; /* '\x0c' duplicate: '\f' */ /* '\x0d' duplicate: '\r' */ case '\x0e': APPEND_STRING("\\u000e"); break; case '\x0f': APPEND_STRING("\\u000f"); break; case '\x10': APPEND_STRING("\\u0010"); break; case '\x11': APPEND_STRING("\\u0011"); break; case '\x12': APPEND_STRING("\\u0012"); break; case '\x13': APPEND_STRING("\\u0013"); break; case '\x14': APPEND_STRING("\\u0014"); break; case '\x15': APPEND_STRING("\\u0015"); break; case '\x16': APPEND_STRING("\\u0016"); break; case '\x17': APPEND_STRING("\\u0017"); break; case '\x18': APPEND_STRING("\\u0018"); break; case '\x19': APPEND_STRING("\\u0019"); break; case '\x1a': APPEND_STRING("\\u001a"); break; case '\x1b': APPEND_STRING("\\u001b"); break; case '\x1c': APPEND_STRING("\\u001c"); break; case '\x1d': APPEND_STRING("\\u001d"); break; case '\x1e': APPEND_STRING("\\u001e"); break; case '\x1f': APPEND_STRING("\\u001f"); break; case '/': if (parson_escape_slashes) { APPEND_STRING("\\/"); /* to make json embeddable in xml\/html */ } else { APPEND_STRING("/"); } break; default: if (buf != NULL) { buf[0] = c; buf += 1; } written_total += 1; break; } } APPEND_STRING("\""); return written_total; } #undef APPEND_STRING #undef APPEND_INDENT /* Parser API */ JSON_Value * json_parse_file(const char *filename) { char *file_contents = read_file(filename); JSON_Value *output_value = NULL; if (file_contents == NULL) { return NULL; } output_value = json_parse_string(file_contents); parson_free(file_contents); return output_value; } JSON_Value * json_parse_file_with_comments(const char *filename) { char *file_contents = read_file(filename); JSON_Value *output_value = NULL; if (file_contents == NULL) { return NULL; } output_value = json_parse_string_with_comments(file_contents); parson_free(file_contents); return output_value; } JSON_Value * json_parse_string(const char *string) { if (string == NULL) { return NULL; } if (string[0] == '\xEF' && string[1] == '\xBB' && string[2] == '\xBF') { string = string + 3; /* Support for UTF-8 BOM */ } return parse_value((const char**)&string, 0); } JSON_Value * json_parse_string_with_comments(const char *string) { JSON_Value *result = NULL; char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL; string_mutable_copy = parson_strdup(string); if (string_mutable_copy == NULL) { return NULL; } remove_comments(string_mutable_copy, "/*", "*/"); remove_comments(string_mutable_copy, "//", "\n"); string_mutable_copy_ptr = string_mutable_copy; result = parse_value((const char**)&string_mutable_copy_ptr, 0); parson_free(string_mutable_copy); return result; } /* JSON Object API */ JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) { if (object == NULL || name == NULL) { return NULL; } return json_object_getn_value(object, name, strlen(name)); } const char * json_object_get_string(const JSON_Object *object, const char *name) { return json_value_get_string(json_object_get_value(object, name)); } size_t json_object_get_string_len(const JSON_Object *object, const char *name) { return json_value_get_string_len(json_object_get_value(object, name)); } double json_object_get_number(const JSON_Object *object, const char *name) { return json_value_get_number(json_object_get_value(object, name)); } JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) { return json_value_get_object(json_object_get_value(object, name)); } JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) { return json_value_get_array(json_object_get_value(object, name)); } int json_object_get_boolean(const JSON_Object *object, const char *name) { return json_value_get_boolean(json_object_get_value(object, name)); } JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) { const char *dot_position = strchr(name, '.'); if (!dot_position) { return json_object_get_value(object, name); } object = json_value_get_object(json_object_getn_value(object, name, dot_position - name)); return json_object_dotget_value(object, dot_position + 1); } const char * json_object_dotget_string(const JSON_Object *object, const char *name) { return json_value_get_string(json_object_dotget_value(object, name)); } size_t json_object_dotget_string_len(const JSON_Object *object, const char *name) { return json_value_get_string_len(json_object_dotget_value(object, name)); } double json_object_dotget_number(const JSON_Object *object, const char *name) { return json_value_get_number(json_object_dotget_value(object, name)); } JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) { return json_value_get_object(json_object_dotget_value(object, name)); } JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) { return json_value_get_array(json_object_dotget_value(object, name)); } int json_object_dotget_boolean(const JSON_Object *object, const char *name) { return json_value_get_boolean(json_object_dotget_value(object, name)); } size_t json_object_get_count(const JSON_Object *object) { return object ? object->count : 0; } const char * json_object_get_name(const JSON_Object *object, size_t index) { if (object == NULL || index >= json_object_get_count(object)) { return NULL; } return object->names[index]; } JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index) { if (object == NULL || index >= json_object_get_count(object)) { return NULL; } return object->values[index]; } JSON_Value *json_object_get_wrapping_value(const JSON_Object *object) { if (!object) { return NULL; } return object->wrapping_value; } int json_object_has_value (const JSON_Object *object, const char *name) { return json_object_get_value(object, name) != NULL; } int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { JSON_Value *val = json_object_get_value(object, name); return val != NULL && json_value_get_type(val) == type; } int json_object_dothas_value (const JSON_Object *object, const char *name) { return json_object_dotget_value(object, name) != NULL; } int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { JSON_Value *val = json_object_dotget_value(object, name); return val != NULL && json_value_get_type(val) == type; } /* JSON Array API */ JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) { if (array == NULL || index >= json_array_get_count(array)) { return NULL; } return array->items[index]; } const char * json_array_get_string(const JSON_Array *array, size_t index) { return json_value_get_string(json_array_get_value(array, index)); } size_t json_array_get_string_len(const JSON_Array *array, size_t index) { return json_value_get_string_len(json_array_get_value(array, index)); } double json_array_get_number(const JSON_Array *array, size_t index) { return json_value_get_number(json_array_get_value(array, index)); } JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) { return json_value_get_object(json_array_get_value(array, index)); } JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) { return json_value_get_array(json_array_get_value(array, index)); } int json_array_get_boolean(const JSON_Array *array, size_t index) { return json_value_get_boolean(json_array_get_value(array, index)); } size_t json_array_get_count(const JSON_Array *array) { return array ? array->count : 0; } JSON_Value * json_array_get_wrapping_value(const JSON_Array *array) { if (!array) { return NULL; } return array->wrapping_value; } /* JSON Value API */ JSON_Value_Type json_value_get_type(const JSON_Value *value) { return value ? value->type : JSONError; } JSON_Object * json_value_get_object(const JSON_Value *value) { return json_value_get_type(value) == JSONObject ? value->value.object : NULL; } JSON_Array * json_value_get_array(const JSON_Value *value) { return json_value_get_type(value) == JSONArray ? value->value.array : NULL; } static const JSON_String * json_value_get_string_desc(const JSON_Value *value) { return json_value_get_type(value) == JSONString ? &value->value.string : NULL; } const char * json_value_get_string(const JSON_Value *value) { const JSON_String *str = json_value_get_string_desc(value); return str ? str->chars : NULL; } size_t json_value_get_string_len(const JSON_Value *value) { const JSON_String *str = json_value_get_string_desc(value); return str ? str->length : 0; } double json_value_get_number(const JSON_Value *value) { return json_value_get_type(value) == JSONNumber ? value->value.number : 0; } int json_value_get_boolean(const JSON_Value *value) { return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1; } JSON_Value * json_value_get_parent (const JSON_Value *value) { return value ? value->parent : NULL; } void json_value_free(JSON_Value *value) { switch (json_value_get_type(value)) { case JSONObject: json_object_free(value->value.object); break; case JSONString: parson_free(value->value.string.chars); break; case JSONArray: json_array_free(value->value.array); break; default: break; } parson_free(value); } JSON_Value * json_value_init_object(void) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONObject; new_value->value.object = json_object_make(new_value); if (!new_value->value.object) { parson_free(new_value); return NULL; } return new_value; } JSON_Value * json_value_init_array(void) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONArray; new_value->value.array = json_array_make(new_value); if (!new_value->value.array) { parson_free(new_value); return NULL; } return new_value; } JSON_Value * json_value_init_string(const char *string) { if (string == NULL) { return NULL; } return json_value_init_string_with_len(string, strlen(string)); } JSON_Value * json_value_init_string_with_len(const char *string, size_t length) { char *copy = NULL; JSON_Value *value; if (string == NULL) { return NULL; } if (!is_valid_utf8(string, length)) { return NULL; } copy = parson_strndup(string, length); if (copy == NULL) { return NULL; } value = json_value_init_string_no_copy(copy, length); if (value == NULL) { parson_free(copy); } return value; } JSON_Value * json_value_init_number(double number) { JSON_Value *new_value = NULL; if (IS_NUMBER_INVALID(number)) { return NULL; } new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (new_value == NULL) { return NULL; } new_value->parent = NULL; new_value->type = JSONNumber; new_value->value.number = number; return new_value; } JSON_Value * json_value_init_boolean(int boolean) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONBoolean; new_value->value.boolean = boolean ? 1 : 0; return new_value; } JSON_Value * json_value_init_null(void) { JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value)); if (!new_value) { return NULL; } new_value->parent = NULL; new_value->type = JSONNull; return new_value; } JSON_Value * json_value_deep_copy(const JSON_Value *value) { size_t i = 0; JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL; const JSON_String *temp_string = NULL; const char *temp_key = NULL; char *temp_string_copy = NULL; JSON_Array *temp_array = NULL, *temp_array_copy = NULL; JSON_Object *temp_object = NULL, *temp_object_copy = NULL; JSON_Status res = JSONFailure; char *key_copy = NULL; switch (json_value_get_type(value)) { case JSONArray: temp_array = json_value_get_array(value); return_value = json_value_init_array(); if (return_value == NULL) { return NULL; } temp_array_copy = json_value_get_array(return_value); for (i = 0; i < json_array_get_count(temp_array); i++) { temp_value = json_array_get_value(temp_array, i); temp_value_copy = json_value_deep_copy(temp_value); if (temp_value_copy == NULL) { json_value_free(return_value); return NULL; } if (json_array_add(temp_array_copy, temp_value_copy) != JSONSuccess) { json_value_free(return_value); json_value_free(temp_value_copy); return NULL; } } return return_value; case JSONObject: temp_object = json_value_get_object(value); return_value = json_value_init_object(); if (!return_value) { return NULL; } temp_object_copy = json_value_get_object(return_value); for (i = 0; i < json_object_get_count(temp_object); i++) { temp_key = json_object_get_name(temp_object, i); temp_value = json_object_get_value(temp_object, temp_key); temp_value_copy = json_value_deep_copy(temp_value); if (!temp_value_copy) { json_value_free(return_value); return NULL; } key_copy = parson_strdup(temp_key); if (!key_copy) { json_value_free(temp_value_copy); json_value_free(return_value); return NULL; } res = json_object_add(temp_object_copy, key_copy, temp_value_copy); if (res != JSONSuccess) { parson_free(key_copy); json_value_free(temp_value_copy); json_value_free(return_value); return NULL; } } return return_value; case JSONBoolean: return json_value_init_boolean(json_value_get_boolean(value)); case JSONNumber: return json_value_init_number(json_value_get_number(value)); case JSONString: temp_string = json_value_get_string_desc(value); if (temp_string == NULL) { return NULL; } temp_string_copy = parson_strndup(temp_string->chars, temp_string->length); if (temp_string_copy == NULL) { return NULL; } return_value = json_value_init_string_no_copy(temp_string_copy, temp_string->length); if (return_value == NULL) { parson_free(temp_string_copy); } return return_value; case JSONNull: return json_value_init_null(); case JSONError: return NULL; default: return NULL; } } size_t json_serialization_size(const JSON_Value *value) { char num_buf[PARSON_NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ int res = json_serialize_to_buffer_r(value, NULL, 0, PARSON_FALSE, num_buf); return res < 0 ? 0 : (size_t)(res) + 1; } JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { int written = -1; size_t needed_size_in_bytes = json_serialization_size(value); if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { return JSONFailure; } written = json_serialize_to_buffer_r(value, buf, 0, PARSON_FALSE, NULL); if (written < 0) { return JSONFailure; } return JSONSuccess; } JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) { JSON_Status return_code = JSONSuccess; FILE *fp = NULL; char *serialized_string = json_serialize_to_string(value); if (serialized_string == NULL) { return JSONFailure; } fp = fopen(filename, "w"); if (fp == NULL) { json_free_serialized_string(serialized_string); return JSONFailure; } if (fputs(serialized_string, fp) == EOF) { return_code = JSONFailure; } if (fclose(fp) == EOF) { return_code = JSONFailure; } json_free_serialized_string(serialized_string); return return_code; } char * json_serialize_to_string(const JSON_Value *value) { JSON_Status serialization_result = JSONFailure; size_t buf_size_bytes = json_serialization_size(value); char *buf = NULL; if (buf_size_bytes == 0) { return NULL; } buf = (char*)parson_malloc(buf_size_bytes); if (buf == NULL) { return NULL; } serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes); if (serialization_result != JSONSuccess) { json_free_serialized_string(buf); return NULL; } return buf; } size_t json_serialization_size_pretty(const JSON_Value *value) { char num_buf[PARSON_NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ int res = json_serialize_to_buffer_r(value, NULL, 0, PARSON_TRUE, num_buf); return res < 0 ? 0 : (size_t)(res) + 1; } JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { int written = -1; size_t needed_size_in_bytes = json_serialization_size_pretty(value); if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { return JSONFailure; } written = json_serialize_to_buffer_r(value, buf, 0, PARSON_TRUE, NULL); if (written < 0) { return JSONFailure; } return JSONSuccess; } JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) { JSON_Status return_code = JSONSuccess; FILE *fp = NULL; char *serialized_string = json_serialize_to_string_pretty(value); if (serialized_string == NULL) { return JSONFailure; } fp = fopen(filename, "w"); if (fp == NULL) { json_free_serialized_string(serialized_string); return JSONFailure; } if (fputs(serialized_string, fp) == EOF) { return_code = JSONFailure; } if (fclose(fp) == EOF) { return_code = JSONFailure; } json_free_serialized_string(serialized_string); return return_code; } char * json_serialize_to_string_pretty(const JSON_Value *value) { JSON_Status serialization_result = JSONFailure; size_t buf_size_bytes = json_serialization_size_pretty(value); char *buf = NULL; if (buf_size_bytes == 0) { return NULL; } buf = (char*)parson_malloc(buf_size_bytes); if (buf == NULL) { return NULL; } serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes); if (serialization_result != JSONSuccess) { json_free_serialized_string(buf); return NULL; } return buf; } void json_free_serialized_string(char *string) { parson_free(string); } JSON_Status json_array_remove(JSON_Array *array, size_t ix) { size_t to_move_bytes = 0; if (array == NULL || ix >= json_array_get_count(array)) { return JSONFailure; } json_value_free(json_array_get_value(array, ix)); to_move_bytes = (json_array_get_count(array) - 1 - ix) * sizeof(JSON_Value*); memmove(array->items + ix, array->items + ix + 1, to_move_bytes); array->count -= 1; return JSONSuccess; } JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) { if (array == NULL || value == NULL || value->parent != NULL || ix >= json_array_get_count(array)) { return JSONFailure; } json_value_free(json_array_get_value(array, ix)); value->parent = json_array_get_wrapping_value(array); array->items[ix] = value; return JSONSuccess; } JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) { JSON_Value *value = json_value_init_string(string); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_string_with_len(JSON_Array *array, size_t i, const char *string, size_t len) { JSON_Value *value = json_value_init_string_with_len(string, len); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) { JSON_Value *value = json_value_init_number(number); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) { JSON_Value *value = json_value_init_boolean(boolean); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_null(JSON_Array *array, size_t i) { JSON_Value *value = json_value_init_null(); if (value == NULL) { return JSONFailure; } if (json_array_replace_value(array, i, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_clear(JSON_Array *array) { size_t i = 0; if (array == NULL) { return JSONFailure; } for (i = 0; i < json_array_get_count(array); i++) { json_value_free(json_array_get_value(array, i)); } array->count = 0; return JSONSuccess; } JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) { if (array == NULL || value == NULL || value->parent != NULL) { return JSONFailure; } return json_array_add(array, value); } JSON_Status json_array_append_string(JSON_Array *array, const char *string) { JSON_Value *value = json_value_init_string(string); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_string_with_len(JSON_Array *array, const char *string, size_t len) { JSON_Value *value = json_value_init_string_with_len(string, len); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_number(JSON_Array *array, double number) { JSON_Value *value = json_value_init_number(number); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) { JSON_Value *value = json_value_init_boolean(boolean); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_null(JSON_Array *array) { JSON_Value *value = json_value_init_null(); if (value == NULL) { return JSONFailure; } if (json_array_append_value(array, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) { unsigned long hash = 0; parson_bool_t found = PARSON_FALSE; size_t cell_ix = 0; size_t item_ix = 0; JSON_Value *old_value = NULL; char *key_copy = NULL; if (!object || !name || !value || value->parent) { return JSONFailure; } hash = hash_string(name, strlen(name)); found = PARSON_FALSE; cell_ix = json_object_get_cell_ix(object, name, strlen(name), hash, &found); if (found) { item_ix = object->cells[cell_ix]; old_value = object->values[item_ix]; json_value_free(old_value); object->values[item_ix] = value; value->parent = json_object_get_wrapping_value(object); return JSONSuccess; } if (object->count >= object->item_capacity) { JSON_Status res = json_object_grow_and_rehash(object); if (res != JSONSuccess) { return JSONFailure; } cell_ix = json_object_get_cell_ix(object, name, strlen(name), hash, &found); } key_copy = parson_strdup(name); if (!key_copy) { return JSONFailure; } object->names[object->count] = key_copy; object->cells[cell_ix] = object->count; object->values[object->count] = value; object->cell_ixs[object->count] = cell_ix; object->hashes[object->count] = hash; object->count++; value->parent = json_object_get_wrapping_value(object); return JSONSuccess; } JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) { JSON_Value *value = json_value_init_string(string); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_set_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len) { JSON_Value *value = json_value_init_string_with_len(string, len); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) { JSON_Value *value = json_value_init_number(number); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) { JSON_Value *value = json_value_init_boolean(boolean); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_set_null(JSON_Object *object, const char *name) { JSON_Value *value = json_value_init_null(); JSON_Status status = json_object_set_value(object, name, value); if (status != JSONSuccess) { json_value_free(value); } return status; } JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) { const char *dot_pos = NULL; JSON_Value *temp_value = NULL, *new_value = NULL; JSON_Object *temp_object = NULL, *new_object = NULL; JSON_Status status = JSONFailure; size_t name_len = 0; char *name_copy = NULL; if (object == NULL || name == NULL || value == NULL) { return JSONFailure; } dot_pos = strchr(name, '.'); if (dot_pos == NULL) { return json_object_set_value(object, name, value); } name_len = dot_pos - name; temp_value = json_object_getn_value(object, name, name_len); if (temp_value) { /* Don't overwrite existing non-object (unlike json_object_set_value, but it shouldn't be changed at this point) */ if (json_value_get_type(temp_value) != JSONObject) { return JSONFailure; } temp_object = json_value_get_object(temp_value); return json_object_dotset_value(temp_object, dot_pos + 1, value); } new_value = json_value_init_object(); if (new_value == NULL) { return JSONFailure; } new_object = json_value_get_object(new_value); status = json_object_dotset_value(new_object, dot_pos + 1, value); if (status != JSONSuccess) { json_value_free(new_value); return JSONFailure; } name_copy = parson_strndup(name, name_len); if (!name_copy) { json_object_dotremove_internal(new_object, dot_pos + 1, 0); json_value_free(new_value); return JSONFailure; } status = json_object_add(object, name_copy, new_value); if (status != JSONSuccess) { parson_free(name_copy); json_object_dotremove_internal(new_object, dot_pos + 1, 0); json_value_free(new_value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) { JSON_Value *value = json_value_init_string(string); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len) { JSON_Value *value = json_value_init_string_with_len(string, len); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) { JSON_Value *value = json_value_init_number(number); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) { JSON_Value *value = json_value_init_boolean(boolean); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) { JSON_Value *value = json_value_init_null(); if (value == NULL) { return JSONFailure; } if (json_object_dotset_value(object, name, value) != JSONSuccess) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_remove(JSON_Object *object, const char *name) { return json_object_remove_internal(object, name, PARSON_TRUE); } JSON_Status json_object_dotremove(JSON_Object *object, const char *name) { return json_object_dotremove_internal(object, name, PARSON_TRUE); } JSON_Status json_object_clear(JSON_Object *object) { size_t i = 0; if (object == NULL) { return JSONFailure; } for (i = 0; i < json_object_get_count(object); i++) { parson_free(object->names[i]); object->names[i] = NULL; json_value_free(object->values[i]); object->values[i] = NULL; } object->count = 0; for (i = 0; i < object->cell_capacity; i++) { object->cells[i] = OBJECT_INVALID_IX; } return JSONSuccess; } JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) { JSON_Value *temp_schema_value = NULL, *temp_value = NULL; JSON_Array *schema_array = NULL, *value_array = NULL; JSON_Object *schema_object = NULL, *value_object = NULL; JSON_Value_Type schema_type = JSONError, value_type = JSONError; const char *key = NULL; size_t i = 0, count = 0; if (schema == NULL || value == NULL) { return JSONFailure; } schema_type = json_value_get_type(schema); value_type = json_value_get_type(value); if (schema_type != value_type && schema_type != JSONNull) { /* null represents all values */ return JSONFailure; } switch (schema_type) { case JSONArray: schema_array = json_value_get_array(schema); value_array = json_value_get_array(value); count = json_array_get_count(schema_array); if (count == 0) { return JSONSuccess; /* Empty array allows all types */ } /* Get first value from array, rest is ignored */ temp_schema_value = json_array_get_value(schema_array, 0); for (i = 0; i < json_array_get_count(value_array); i++) { temp_value = json_array_get_value(value_array, i); if (json_validate(temp_schema_value, temp_value) != JSONSuccess) { return JSONFailure; } } return JSONSuccess; case JSONObject: schema_object = json_value_get_object(schema); value_object = json_value_get_object(value); count = json_object_get_count(schema_object); if (count == 0) { return JSONSuccess; /* Empty object allows all objects */ } else if (json_object_get_count(value_object) < count) { return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */ } for (i = 0; i < count; i++) { key = json_object_get_name(schema_object, i); temp_schema_value = json_object_get_value(schema_object, key); temp_value = json_object_get_value(value_object, key); if (temp_value == NULL) { return JSONFailure; } if (json_validate(temp_schema_value, temp_value) != JSONSuccess) { return JSONFailure; } } return JSONSuccess; case JSONString: case JSONNumber: case JSONBoolean: case JSONNull: return JSONSuccess; /* equality already tested before switch */ case JSONError: default: return JSONFailure; } } int json_value_equals(const JSON_Value *a, const JSON_Value *b) { JSON_Object *a_object = NULL, *b_object = NULL; JSON_Array *a_array = NULL, *b_array = NULL; const JSON_String *a_string = NULL, *b_string = NULL; const char *key = NULL; size_t a_count = 0, b_count = 0, i = 0; JSON_Value_Type a_type, b_type; a_type = json_value_get_type(a); b_type = json_value_get_type(b); if (a_type != b_type) { return PARSON_FALSE; } switch (a_type) { case JSONArray: a_array = json_value_get_array(a); b_array = json_value_get_array(b); a_count = json_array_get_count(a_array); b_count = json_array_get_count(b_array); if (a_count != b_count) { return PARSON_FALSE; } for (i = 0; i < a_count; i++) { if (!json_value_equals(json_array_get_value(a_array, i), json_array_get_value(b_array, i))) { return PARSON_FALSE; } } return PARSON_TRUE; case JSONObject: a_object = json_value_get_object(a); b_object = json_value_get_object(b); a_count = json_object_get_count(a_object); b_count = json_object_get_count(b_object); if (a_count != b_count) { return PARSON_FALSE; } for (i = 0; i < a_count; i++) { key = json_object_get_name(a_object, i); if (!json_value_equals(json_object_get_value(a_object, key), json_object_get_value(b_object, key))) { return PARSON_FALSE; } } return PARSON_TRUE; case JSONString: a_string = json_value_get_string_desc(a); b_string = json_value_get_string_desc(b); if (a_string == NULL || b_string == NULL) { return PARSON_FALSE; /* shouldn't happen */ } return a_string->length == b_string->length && memcmp(a_string->chars, b_string->chars, a_string->length) == 0; case JSONBoolean: return json_value_get_boolean(a) == json_value_get_boolean(b); case JSONNumber: return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */ case JSONError: return PARSON_TRUE; case JSONNull: return PARSON_TRUE; default: return PARSON_TRUE; } } JSON_Value_Type json_type(const JSON_Value *value) { return json_value_get_type(value); } JSON_Object * json_object (const JSON_Value *value) { return json_value_get_object(value); } JSON_Array * json_array(const JSON_Value *value) { return json_value_get_array(value); } const char * json_string(const JSON_Value *value) { return json_value_get_string(value); } size_t json_string_len(const JSON_Value *value) { return json_value_get_string_len(value); } double json_number(const JSON_Value *value) { return json_value_get_number(value); } int json_boolean(const JSON_Value *value) { return json_value_get_boolean(value); } void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) { parson_malloc = malloc_fun; parson_free = free_fun; } void json_set_escape_slashes(int escape_slashes) { parson_escape_slashes = escape_slashes; } void json_set_float_serialization_format(const char *format) { if (parson_float_format) { parson_free(parson_float_format); parson_float_format = NULL; } if (!format) { parson_float_format = NULL; return; } parson_float_format = parson_strdup(format); } void json_set_number_serialization_function(JSON_Number_Serialization_Function func) { parson_number_serialization_function = func; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/parson.h000066400000000000000000000336101523150047600224060ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT Parson 1.5.3 (https://github.com/kgabis/parson) Copyright (c) 2012 - 2023 Krzysztof Gabis 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. */ #ifndef parson_parson_h #define parson_parson_h #ifdef __cplusplus extern "C" { #endif #if 0 } /* unconfuse xcode */ #endif #define PARSON_VERSION_MAJOR 1 #define PARSON_VERSION_MINOR 5 #define PARSON_VERSION_PATCH 3 #define PARSON_VERSION_STRING "1.5.3" #include /* size_t */ /* Types and enums */ typedef struct json_object_t JSON_Object; typedef struct json_array_t JSON_Array; typedef struct json_value_t JSON_Value; enum json_value_type { JSONError = -1, JSONNull = 1, JSONString = 2, JSONNumber = 3, JSONObject = 4, JSONArray = 5, JSONBoolean = 6 }; typedef int JSON_Value_Type; enum json_result_t { JSONSuccess = 0, JSONFailure = -1 }; typedef int JSON_Status; typedef void * (*JSON_Malloc_Function)(size_t); typedef void (*JSON_Free_Function)(void *); /* A function used for serializing numbers (see json_set_number_serialization_function). If 'buf' is null then it should return number of bytes that would've been written (but not more than PARSON_NUM_BUF_SIZE). */ typedef int (*JSON_Number_Serialization_Function)(double num, char *buf); /* Call only once, before calling any other function from parson API. If not called, malloc and free from stdlib will be used for all allocations */ void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); /* Sets if slashes should be escaped or not when serializing JSON. By default slashes are escaped. This function sets a global setting and is not thread safe. */ void json_set_escape_slashes(int escape_slashes); /* Sets float format used for serialization of numbers. Make sure it can't serialize to a string longer than PARSON_NUM_BUF_SIZE. If format is null then the default format is used. */ void json_set_float_serialization_format(const char *format); /* Sets a function that will be used for serialization of numbers. If function is null then the default serialization function is used. */ void json_set_number_serialization_function(JSON_Number_Serialization_Function fun); /* Parses first JSON value in a file, returns NULL in case of error */ JSON_Value * json_parse_file(const char *filename); /* Parses first JSON value in a file and ignores comments (/ * * / and //), returns NULL in case of error */ JSON_Value * json_parse_file_with_comments(const char *filename); /* Parses first JSON value in a string, returns NULL in case of error */ JSON_Value * json_parse_string(const char *string); /* Parses first JSON value in a string and ignores comments (/ * * / and //), returns NULL in case of error */ JSON_Value * json_parse_string_with_comments(const char *string); /* Serialization */ size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); char * json_serialize_to_string(const JSON_Value *value); /* Pretty serialization */ size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename); char * json_serialize_to_string_pretty(const JSON_Value *value); void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */ /* Comparing */ int json_value_equals(const JSON_Value *a, const JSON_Value *b); /* Validation This is *NOT* JSON Schema. It validates json by checking if object have identically named fields with matching types. For example schema {"name":"", "age":0} will validate {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. In case of arrays, only first value in schema is checked against all values in tested array. Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, null validates values of every type. */ JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); /* * JSON Object */ JSON_Value * json_object_get_value (const JSON_Object *object, const char *name); const char * json_object_get_string (const JSON_Object *object, const char *name); size_t json_object_get_string_len(const JSON_Object *object, const char *name); /* doesn't account for last null character */ JSON_Object * json_object_get_object (const JSON_Object *object, const char *name); JSON_Array * json_object_get_array (const JSON_Object *object, const char *name); double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ /* dotget functions enable addressing values with dot notation in nested objects, just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). Because valid names in JSON can contain dots, some values may be inaccessible this way. */ JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name); const char * json_object_dotget_string (const JSON_Object *object, const char *name); size_t json_object_dotget_string_len(const JSON_Object *object, const char *name); /* doesn't account for last null character */ JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name); JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name); double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */ int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ /* Functions to get available names */ size_t json_object_get_count (const JSON_Object *object); const char * json_object_get_name (const JSON_Object *object, size_t index); JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index); JSON_Value * json_object_get_wrapping_value(const JSON_Object *object); /* Functions to check if object has a value with a specific name. Returned value is 1 if object has * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */ int json_object_has_value (const JSON_Object *object, const char *name); int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); int json_object_dothas_value (const JSON_Object *object, const char *name); int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); /* Creates new name-value pair or frees and replaces old value with a new one. * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); JSON_Status json_object_set_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len); /* length shouldn't include last null character */ JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); JSON_Status json_object_set_null(JSON_Object *object, const char *name); /* Works like dotget functions, but creates whole hierarchy if necessary. * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); JSON_Status json_object_dotset_string_with_len(JSON_Object *object, const char *name, const char *string, size_t len); /* length shouldn't include last null character */ JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); /* Frees and removes name-value pair */ JSON_Status json_object_remove(JSON_Object *object, const char *name); /* Works like dotget function, but removes name-value pair only on exact match. */ JSON_Status json_object_dotremove(JSON_Object *object, const char *key); /* Removes all name-value pairs in object */ JSON_Status json_object_clear(JSON_Object *object); /* *JSON Array */ JSON_Value * json_array_get_value (const JSON_Array *array, size_t index); const char * json_array_get_string (const JSON_Array *array, size_t index); size_t json_array_get_string_len(const JSON_Array *array, size_t index); /* doesn't account for last null character */ JSON_Object * json_array_get_object (const JSON_Array *array, size_t index); JSON_Array * json_array_get_array (const JSON_Array *array, size_t index); double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */ int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ size_t json_array_get_count (const JSON_Array *array); JSON_Value * json_array_get_wrapping_value(const JSON_Array *array); /* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist. * Order of values in array may change during execution. */ JSON_Status json_array_remove(JSON_Array *array, size_t i); /* Frees and removes from array value at given index and replaces it with given one. * Does nothing and returns JSONFailure if index doesn't exist. * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string); JSON_Status json_array_replace_string_with_len(JSON_Array *array, size_t i, const char *string, size_t len); /* length shouldn't include last null character */ JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); JSON_Status json_array_replace_null(JSON_Array *array, size_t i); /* Frees and removes all values from array */ JSON_Status json_array_clear(JSON_Array *array); /* Appends new value at the end of array. * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); JSON_Status json_array_append_string(JSON_Array *array, const char *string); JSON_Status json_array_append_string_with_len(JSON_Array *array, const char *string, size_t len); /* length shouldn't include last null character */ JSON_Status json_array_append_number(JSON_Array *array, double number); JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); JSON_Status json_array_append_null(JSON_Array *array); /* *JSON Value */ JSON_Value * json_value_init_object (void); JSON_Value * json_value_init_array (void); JSON_Value * json_value_init_string (const char *string); /* copies passed string */ JSON_Value * json_value_init_string_with_len(const char *string, size_t length); /* copies passed string, length shouldn't include last null character */ JSON_Value * json_value_init_number (double number); JSON_Value * json_value_init_boolean(int boolean); JSON_Value * json_value_init_null (void); JSON_Value * json_value_deep_copy (const JSON_Value *value); void json_value_free (JSON_Value *value); JSON_Value_Type json_value_get_type (const JSON_Value *value); JSON_Object * json_value_get_object (const JSON_Value *value); JSON_Array * json_value_get_array (const JSON_Value *value); const char * json_value_get_string (const JSON_Value *value); size_t json_value_get_string_len(const JSON_Value *value); /* doesn't account for last null character */ double json_value_get_number (const JSON_Value *value); int json_value_get_boolean(const JSON_Value *value); JSON_Value * json_value_get_parent (const JSON_Value *value); /* Same as above, but shorter */ JSON_Value_Type json_type (const JSON_Value *value); JSON_Object * json_object (const JSON_Value *value); JSON_Array * json_array (const JSON_Value *value); const char * json_string (const JSON_Value *value); size_t json_string_len(const JSON_Value *value); /* doesn't account for last null character */ double json_number (const JSON_Value *value); int json_boolean(const JSON_Value *value); #ifdef __cplusplus } #endif #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/tconfig.c000066400000000000000000000222141523150047600225260ustar00rootroot00000000000000/* MIT License Copyright (c) 2019 Justin Kinnaird 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. */ #include #include #include #include #include "tconfig.h" static ini_entry_s* _ini_entry_create(ini_section_s* section, const char* key, const char* value) { if ((section->size % 10) == 0) { section->entry = realloc(section->entry, (10+section->size) * sizeof(ini_entry_s)); } ini_entry_s* entry = §ion->entry[section->size++]; strncpy(entry->key, key, INI_MAXLEN); strncpy(entry->value, value, INI_MAXLEN); return entry; } static ini_section_s* _ini_section_create(ini_table_s* table, const char* section_name) { if ((table->size % 10) == 0) { table->section = realloc(table->section, (10+table->size) * sizeof(ini_section_s)); } ini_section_s* section = &table->section[table->size++]; section->size = 0; strncpy(section->name, section_name, INI_MAXLEN); section->entry = calloc(1, 10 * sizeof(ini_entry_s)); return section; } static ini_section_s* _ini_section_find(ini_table_s* table, const char* name) { for (int i = 0; i < table->size; i++) { if (!strncmp(table->section[i].name, name, INI_MAXLEN)) { return &table->section[i]; } } return NULL; } static ini_entry_s* _ini_entry_find(ini_section_s* section, const char* key) { for (int i = 0; i < section->size; i++) { if (!strncmp(section->entry[i].key, key, INI_MAXLEN)) { return §ion->entry[i]; } } return NULL; } static ini_entry_s* _ini_entry_get(ini_table_s* table, const char* section_name, const char* key) { ini_section_s* section = _ini_section_find(table, section_name); if (section == NULL) { return NULL; } ini_entry_s* entry = _ini_entry_find(section, key); if (entry == NULL) { return NULL; } return entry; } ini_table_s* ini_table_create(void) { ini_table_s* table = calloc(1, sizeof(ini_table_s)); table->size = 0; table->section = calloc(1, 10 * sizeof(ini_section_s)); return table; } void ini_table_destroy(ini_table_s* table) { for (int i = 0; i < table->size; i++) { ini_section_s* section = &table->section[i]; free(section->entry); } free(table->section); free(table); } bool ini_table_read_from_file(ini_table_s* table, const char* file) { FILE* f = fopen(file, "r"); if (f == NULL) return false; enum {Section, Key, Value, Comment} state = Section; int c; unsigned position = 0; int spaces = 0; int line = 0; size_t buffer_size = 128 * sizeof(char); char* buf = calloc(1, buffer_size); char* value = NULL; ini_section_s* current_section = NULL; while((c = getc(f)) != EOF) { if (position > buffer_size-2) { buffer_size += 128 * sizeof(char); size_t value_offset = value == NULL ? 0 : value - buf; char *res = realloc(buf, buffer_size); if (!res) { free(buf); fprintf(stderr, "TConfig failed to reallocate buffer\n"); break; } else { buf = res; } memset(buf+position, '\0', buffer_size-position); if (value != NULL) value = buf + value_offset; } switch(c) { case ' ': switch(state) { case Value: if (value[0] != '\0') spaces++; break; default: if (buf[0] != '\0') spaces++; break; } break; case ';': if (state == Value) { buf[position++] = c; break; } else { state = Comment; buf[position++] = c; while (c != EOF && c != '\n') { c = getc(f); if (c != EOF && c != '\n') buf[position++] = c; } } // fallthrough case '\n': // fallthrough case EOF: line++; if (state == Value) { if (current_section == NULL) { current_section = _ini_section_create(table, ""); } _ini_entry_create(current_section, buf, value); value = NULL; } else if (state == Comment) { if (current_section == NULL) { current_section = _ini_section_create(table, ""); } _ini_entry_create(current_section, buf, ""); } else if (state == Section) { fprintf(stderr, "TConfig [Line %d]: Section `%s'" " missing `]' operator.", line, buf); } else if(state == Key && position) { fprintf(stderr, "TConfig [Line %d]: Key `%s'" " missing `=' operator.", line, buf); } memset(buf, '\0', buffer_size); state = Key; position = 0; spaces = 0; break; case '[': state = Section; break; case ']': current_section = _ini_section_create(table, buf); memset(buf, '\0', buffer_size); position = 0; spaces = 0; state = Key; break; case '=': if (state == Key) { state = Value; buf[position++] = '\0'; value = buf + position; spaces = 0; continue; } default: for(;spaces > 0; spaces--) buf[position++] = ' '; buf[position++] = c; break; } } free(buf); fclose(f); return true; } bool ini_table_write_to_file(ini_table_s* table, const char* file) { FILE* f = fopen(file, "w+"); if (f == NULL) return false; for (int i = 0; i < table->size; i++) { ini_section_s* section = &table->section[i]; fprintf(f, i > 0 ? "\n[%s]\n" : "[%s]\n", section->name); for (int q = 0; q < section->size; q++) { ini_entry_s* entry = §ion->entry[q]; if (entry->key[0] == ';') { fprintf(f, "%s\n", entry->key); } else { fprintf(f, "%s = %s\n", entry->key, entry->value); } } } fclose(f); return true; } void ini_table_create_entry(ini_table_s* table, const char* section_name, const char* key, const char* value) { ini_section_s* section = _ini_section_find(table, section_name); if (section == NULL) { section = _ini_section_create(table, section_name); } ini_entry_s* entry = _ini_entry_find(section, key); if (entry == NULL) { entry = _ini_entry_create(section, key, value); }else { strncpy(entry->value, value, INI_MAXLEN); } } bool ini_table_check_entry(ini_table_s* table, const char* section_name, const char* key) { return (_ini_entry_get(table, section_name, key) != NULL); } const char* ini_table_get_entry(ini_table_s* table, const char* section_name, const char* key) { ini_entry_s* entry = _ini_entry_get(table, section_name, key); if (entry == NULL) { return NULL; } return entry->value; } bool ini_table_get_entry_as_int(ini_table_s* table, const char* section_name, const char* key, int* value) { const char* val = ini_table_get_entry(table, section_name, key); if (val == NULL) { return false; } *value = atoi(val); return true; } bool ini_table_get_entry_as_bool(ini_table_s* table, const char* section_name, const char* key, bool* value) { const char* val = ini_table_get_entry(table, section_name, key); if (val == NULL) { return false; } if (!strncmp(val, "on", INI_MAXLEN) || !strncmp(val, "true", INI_MAXLEN)) { *value = true; }else { *value = false; } return true; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/tconfig.h000066400000000000000000000057561523150047600225470ustar00rootroot00000000000000#ifndef _TCONFIG_H_ #define _TCONFIG_H_ #include #define INI_MAXLEN 255 typedef struct ini_entry_s { char key[INI_MAXLEN+1]; char value[INI_MAXLEN+1]; } ini_entry_s; typedef struct ini_section_s { char name[INI_MAXLEN+1]; ini_entry_s* entry; int size; } ini_section_s; typedef struct ini_table_s { ini_section_s* section; int size; } ini_table_s; /** * @brief Creates an empty ini_table_s struct for writing new entries to. * @return ini_table_s* */ ini_table_s* ini_table_create(void); /** * @brief Free up all the allocated resources in the ini_table_s struct. * @param table */ void ini_table_destroy(ini_table_s* table); /** * @brief Creates an ini_table_s struct filled with data from the specified * `file'. Returns NULL if the file can not be read. * @param table * @param file * @return ini_table_s* */ bool ini_table_read_from_file(ini_table_s* table, const char* file); /** * @brief Writes the specified ini_table_s struct to the specified `file'. * Returns false if the file could not be opened for writing, otherwise * true. * @param table * @param file * @return bool */ bool ini_table_write_to_file(ini_table_s* table, const char* file); /** * @brief Creates a new entry in the `table' containing the `key' and `value' * provided if it does not exist. Otherwise, modifies an exsiting `key' * with the new `value' * @param table * @param section_name * @param key * @param value */ void ini_table_create_entry(ini_table_s* table, const char* section_name, const char* key, const char* value); /** * @brief Checks for the existance of an entry in the specified `table'. Returns * false if the entry does not exist, otherwise true. * @param table * @param section_name * @param key * @return bool */ bool ini_table_check_entry(ini_table_s* table, const char* section_name, const char* key); /** * @brief Retrieves the unmodified value of the specified `key' in `section_name'. * Returns NULL if the entry does not exist, otherwise a pointer to the * entry value data. * @param table * @param section_name * @param key * @return const char* */ const char* ini_table_get_entry(ini_table_s* table, const char* section_name, const char* key); /** * @brief Retrieves the value of the specified `key' in `section_name', converted * to int. Returns false on failure, otherwise true. * @param table * @param section_name * @param key * @param [out]value * @return int */ bool ini_table_get_entry_as_int(ini_table_s* table, const char* section_name, const char* key, int* value); /** * @brief Retrieves the value of the specified `key' in `section_name', converted * to bool. Returns false on failure, true otherwise. * @param table * @param section_name * @param key * @param [out]value * @return bool */ bool ini_table_get_entry_as_bool(ini_table_s* table, const char* section_name, const char* key, bool* value); #endif//_TCONFIG_H_ jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/wave_writer.c000066400000000000000000000143151523150047600234360ustar00rootroot00000000000000/* * Base documents: * https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ * http://en.wikipedia.org/wiki/WAV * * License: CC0-1.0 * https://github.com/tai2/wave_reader_and_writer */ #include #include #include #include #include "wave_writer.h" #define FOUR_CC(a,b,c,d) (((a)<<24) | ((b)<<16) | ((c)<<8) | ((d)<<0)) #define FORMAT 1 #define FMT_LEN 16 #define DEFAULT_RIFF_LEN (4 + 8 + FMT_LEN + 8) struct wave_writer { int format; int num_channels; int sample_rate; int sample_bits; int num_samples; FILE *fp; }; static int get_data_len(const wave_writer *ww) { int l = ww->num_samples * ww->num_channels * ww->sample_bits / 8; assert(0 <= l); if (l % 2 != 0) { l++; } return l; } static int write_byte(FILE *fp, unsigned char in) { if (1 > fwrite(&in, 1, 1, fp)) { return 0; } return 1; } static int write_int32_b(FILE *fp, int in) { unsigned char a, b, c, d; a = (in>>24) & 0xFF; b = (in>>16) & 0xFF; c = (in>> 8) & 0xFF; d = (in>> 0) & 0xFF; if (!write_byte(fp, a)) return 0; if (!write_byte(fp, b)) return 0; if (!write_byte(fp, c)) return 0; if (!write_byte(fp, d)) return 0; return 1; } static int write_int32_l(FILE *fp, int in) { unsigned char a, b, c, d; a = (in>> 0) & 0xFF; b = (in>> 8) & 0xFF; c = (in>>16) & 0xFF; d = (in>>24) & 0xFF; if (!write_byte(fp, a)) return 0; if (!write_byte(fp, b)) return 0; if (!write_byte(fp, c)) return 0; if (!write_byte(fp, d)) return 0; return 1; } static int write_int16_l(FILE *fp, int in) { unsigned char a, b; a = (in>> 0) & 0xFF; b = (in>> 8) & 0xFF; if (!write_byte(fp, a)) return 0; if (!write_byte(fp, b)) return 0; return 1; } static int write_riff_chunk(struct wave_writer *ww) { if (!write_int32_b(ww->fp, FOUR_CC('R','I','F','F'))) { return 0; } if (!write_int32_l(ww->fp, DEFAULT_RIFF_LEN + get_data_len(ww))) { return 0; } if (!write_int32_b(ww->fp, FOUR_CC('W','A','V','E'))) { return 0; } if (!write_int32_b(ww->fp, FOUR_CC('f','m','t',' '))) { return 0; } if (!write_int32_l(ww->fp, FMT_LEN)) { return 0; } if (!write_int16_l(ww->fp, ww->format)) { return 0; } if (!write_int16_l(ww->fp, ww->num_channels)) { return 0; } if (!write_int32_l(ww->fp, ww->sample_rate)) { return 0; } if (!write_int32_l(ww->fp, ww->sample_rate * ww->num_channels * ww->sample_bits / 8)) { return 0; } if (!write_int16_l(ww->fp, ww->num_channels * ww->sample_bits / 8)) { return 0; } if (!write_int16_l(ww->fp, ww->sample_bits)) { return 0; } if (!write_int32_b(ww->fp, FOUR_CC('d','a','t','a'))) { return 0; } if (!write_int32_l(ww->fp, get_data_len(ww))) { return 0; } return 1; } static int is_acceptable_num_channels(const wave_writer_format *format) { return 1 <= format->num_channels && format->num_channels <= 8; } static int is_acceptable_sample_bits(const wave_writer_format *format) { return format->sample_bits == 8 || format->sample_bits == 16 || format->sample_bits == 24; } static int is_acceptable_sample_rate(const wave_writer_format *format) { return format->sample_rate == 8000 || format->sample_rate == 11025 || format->sample_rate == 16000 || format->sample_rate == 22050 || format->sample_rate == 24000 || format->sample_rate == 32000 || format->sample_rate == 44100 || format->sample_rate == 48000; } struct wave_writer * wave_writer_open(const char *filename, const wave_writer_format *format, wave_writer_error *error) { struct wave_writer *ww = NULL; assert(filename != NULL); assert(format != NULL); assert(error != NULL); if (!is_acceptable_num_channels(format) || !is_acceptable_sample_bits(format) || !is_acceptable_sample_rate(format)) { *error = WW_BAD_FORMAT; goto format_error; } ww = (struct wave_writer *)calloc(1, sizeof(struct wave_writer)); if (!ww) { *error = WW_ALLOC_ERROR; goto alloc_error; } ww->fp = fopen(filename, "wb"); if (!ww->fp) { *error = WW_OPEN_ERROR; goto open_error; } ww->format = FORMAT; ww->num_channels = format->num_channels; ww->sample_rate = format->sample_rate; ww->sample_bits = format->sample_bits; ww->num_samples = 0; if (!write_riff_chunk(ww)) { *error = WW_IO_ERROR; goto writing_error; } return ww; writing_error: fclose(ww->fp); open_error: free(ww); alloc_error: format_error: return NULL; } int wave_writer_close(struct wave_writer *ww, wave_writer_error *error) { int result = 1; if (ww) { int l = ww->num_samples * ww->num_channels * ww->sample_bits / 8; assert(0 <= l); if (l % 2 != 0) { write_byte(ww->fp, 0); } if (fseek(ww->fp, 0, SEEK_SET) != 0) { *error = WW_IO_ERROR; result = 0; goto release; } if (!write_riff_chunk(ww)) { *error = WW_IO_ERROR; result = 0; goto release; } release: fclose(ww->fp); free(ww); } return result; } int wave_writer_get_format(struct wave_writer *ww) { assert(ww != NULL); return ww->format; } int wave_writer_get_num_channels(struct wave_writer *ww) { assert(ww != NULL); return ww->num_channels; } int wave_writer_get_sample_rate(struct wave_writer *ww) { assert(ww != NULL); return ww->sample_rate; } int wave_writer_get_sample_bits(struct wave_writer *ww) { assert(ww != NULL); return ww->sample_bits; } int wave_writer_get_num_samples(struct wave_writer *ww) { assert(ww != NULL); return ww->num_samples; } int wave_writer_put_samples(struct wave_writer *ww, int n, void *buf) { int ret; assert(ww != NULL); assert(buf != NULL); ret = fwrite(buf, ww->num_channels * ww->sample_bits / 8, n, ww->fp); if (ret < n) { return -1; } ww->num_samples += ret; return ret; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/deps/wave_writer.h000066400000000000000000000015221523150047600234370ustar00rootroot00000000000000#ifndef WAVE_WRITER_H #define WAVE_WRITER_H typedef enum { WW_NO_ERROR = 0, WW_OPEN_ERROR, WW_IO_ERROR, WW_ALLOC_ERROR, WW_BAD_FORMAT, } wave_writer_error; typedef struct { int num_channels; int sample_rate; int sample_bits; } wave_writer_format; typedef struct wave_writer wave_writer; struct wave_writer * wave_writer_open(const char *filename, const wave_writer_format *format, wave_writer_error *error); int wave_writer_close(struct wave_writer *ww, wave_writer_error *error); int wave_writer_get_format(wave_writer *ww); int wave_writer_get_num_channels(wave_writer *ww); int wave_writer_get_sample_rate(wave_writer *ww); int wave_writer_get_sample_bits(wave_writer *ww); int wave_writer_get_num_samples(wave_writer *ww); int wave_writer_put_samples(wave_writer *ww, int n, void *buf); #endif//WAVE_WRITER_H jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/000077500000000000000000000000001523150047600211105ustar00rootroot00000000000000jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/jollygood.svg000066400000000000000000000074721523150047600236450ustar00rootroot00000000000000 image/svg+xml Openclipart 2013-08-10T13:05:14 A Jolly Good Cuppa Rupert Carmichael Cup crockery set dish dishes service set silhouette jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/jollygood1024.png000066400000000000000000000747641523150047600241510ustar00rootroot00000000000000‰PNG  IHDR+ƒsBIT|dˆ pHYs5^5^ "¢wtEXtSoftwarewww.inkscape.org›î<tEXtAuthorrdevries"W,ö3tEXtDescriptionsilhouette of an English porcelain cup.¢úÏd!tEXtCreation Time2013-08-10T13:05:14Z†ÐÈUtEXtSourcehttps://openclipart.org/detail/181742/english-porcelain-cup-by-rdevries-181742åÃÕÀXtEXtCopyrightCC0 Public Domain Dedication http://creativecommons.org/publicdomain/zero/1.0/Æã½ù IDATxœìÝ{˜]e}èñï»öd’@åŽ\"$3I¸Ä`!¬ b•d'F=±µZ=G+híilkµ=§=hk+ôØ*(—ì"VT¨"‚ �dö$È%ˆ\HB.3{½ç� —\IfïõîË÷ó<)ììÉÐ'è#Òþ)¤¤zÜae`h`¬‰�U!ò,‘•y`Uúze¬Œ¬ «J¬=Ž•‡~sëG1$IÒð8�Ô¶î;›qƒ5öŠ‘½j9{{=ÿOxñ×›ýþ¦\¼Ké­V«¬Ê‡† «Ø44ȇiXEÆÊ�³ŠÈÓù–oXËŠé×ò\êxI’š‘I-áÎ2{ìžñêZdBÈÙ'f¼ŠœW“mZÀÇ­ñ{1ô<»¤Îó°X<˳œ1c99+<•ÃS1°¢«Æòçöa¹g!H’:�IIÜZfôî°oÙ/ ì“Ã`ߨ'„™¼:0ô5Ð�8YR{{x2Âò VÄÈò˜±<‹,~OÆ�—FðÄã¿ãÉ73˜:X’¤�å@R]ÝZfô˜œýJûÙ/ì"û…ÀþÆØ/ÂþÀ¾ø¿A’Z×zà1àq�…œÇóŒ•Yä±x<�<Ö]âñE5ž˜]z%¤$I©ùÿ|KÚ!÷�͸�90‡B`rÚ´¨?0lZÐGx5n»—¤—žŠð» ÏOf‘ÇòÀ“äàþ¸/˹�0ôuO…§·I’p í’j™nr&’1)‡#LŽpT€#ñuy’$mip?�{Cd ‘%ªUX6»B-uœ$u ÒvÜw6ã6 r$‘ÉDŽÚ´ÈŸÌжý®Ô}’$µ¸ ÀRK€þ‡þsÝxî›~‰Û$©í8�€›N¦kï½98+Ñḓ‰ôGá³ù’$mxè'Rúó@Õ·HÒð8PÇé?ƒCj%z3˜#SC 7-ô=i_’¤& aÐôG¸'æ,\šqŸ�HÒ+s ¶µìƒŒZ»Ž)!ç"ÇÄÀ”½öHÝ&I’êê9 ¸‡ÈB2v­gÑQßcuê0Ij&Ôî;›q725~û>ôÓ�‘‰Ó$IR:�¿Š�_e9U"ý“¯¤?@L&I)8P˹§Ì¥È1Ž&pL€c"šºK’$µ„§yÉN÷<õÕ73˜:L’Í€šÚK?ÙÏà8àDû’$©Î,"òó¿ ‘_¹S@R;r ¦q繌½‚×’qC ýãð~I’”ƳÀâùpK>‚ŸN¹Œ'RGIÒp8PqÙ’%L®å¼!Àë�×S€®Äi’$I/ç7~á—1rG ~ÕSaMê(IÚQTˆj™1Àëcà"oÞì™8K’$i—¨å°$‹ü2ÏøyWà–IWðëÔ]’ôr¨!–žÉþƒ9Ç‘s�ú„¿;u—$IRƒ=ÜAà–ùùºñÜ>ý"RGI8PÄydý‹™–—xs9> À�©»$I’R ðL?Ïàg1ð³¹£§ÂÆÔ]’:“í’E39¬”qJ œBä-ÀÞ©›$I’ZÀ@€E1r?Ï»¸eꥬL%©38Ð+ŠóÈú«L�‘“ ÌÞ ŒOÝ%I’Ô�{Ü”Ãõcvçg‡~“õ©£$µ'ÚJ„P�ÃTjÌ p2ð{¸à—$Ij¸ë"ü,D~œG®ï½’Åbê.IíÁ€Xø^ÕUâ¤8x;>Ã/I’Ô ž$ð_!rÃ`�L»ŠGRIj]:Ô­eFï8!Ï9%N‰p,þ}�$Ijv7„È ¥�üð¨ï±:u�¤ÖႯCDý39:Þo p"0*u—$I’vM€uÀ-1p]-òÝi–¥n’ÔÜ´±j™1yä÷3xG ¼xuê&I’$5ÌBàš,ãšÉWpWêIÍÇ@›é?ƒCb‰·E8x+02u“$I’ ÷0ðCß‘õTؘ:HRzZ\œG¶¸Ê1!pzy§ÏòK’$i + Ü@Î÷GŽä»¿Ã³©ƒ$¥áB±ÝZfôØÈiYàÝNöIÝ$I’¤–°¸>Â5Ý]\säe,O$©8ZÄ­eF� œ"åïƦn’$IRë P‹p[€‹»»¹Ü�RûsÐÄî~7{vwñV§G8Ø=u“$I’ÚÒàz"•Úh®œv kSIª?MfÉ{Ø{p"³€ÿt§n’$IRGy6@…È·&/à–1u�¤úpЪeÆä�w‡ÈÙÀ)@Wê&I’$ ¸ŸÀÅÙ O¾Šß¤Ž‘4<¹édº^µoÍ3Αwãö~I’$5¯¸™Àÿ¹š«&^džÔA’vž€‚UgÒs |Ø7u�$I’´“Vó|¥§BêI;Î@Ïáð,çìg¯MÝ#I’$ÕA pC \øÔ“\3ãfSIÚ> rßiŒÜ¸;ïÊ熡ÃüüïZ’$Iíêñ¸(‹|µ§ÂÓ©c$m›‹Ò:[<‡I¡Æ|Ø'u�$I’T 5À¥1ã‚)W°$uŒ¤Í9¨ƒj™11r&�s€7¤î‘$I’Ë#|/ƒ/õTøeêIC CµÌëcàÃDæcS÷H’$IÍ& !ð·½ó¹)u‹Ôéì¤j™n"åøðºÔ=’$IR+ˆp‘/NYÀ÷S·H�ÊÀZø^UÁ'ò1àÀÔ=’$IR‹º=Dþ¼g?I"u¯ Zæè`n„Ñ©{$I’¤vá†�ñ¹Þ+¸#u‹Ô)lÃü2¥I‘?Èá¤Ô=’$IR›Š”2>3érJ#µ;/qßiŒÜ8†D88,u�$I’Ô!Öø‡ÁQ|iÚ%¬M#µ+À¹ì^ÚÀ¹!ò©¤î‘$I’:Q€G‰œ7y—ˆ©{¤vÓÑ€¥ïbìÀHþ0ÀùÀ¾©{$I’$ð³|xZ…{S‡Hí¤#÷¾—}6ÖøXˆü)0>u�$I’¤ÍX—þ~Þ|qúE ¤î‘ÚAG î}/û ð9vKÝ#I’$iûüŠœô\I5u‹Ôê:b°éÿ�…Èg#쑺G’$IÒNYáüÞ _õli×µõàÎs1z%ÿ=Â<`¿Ô=’$I’†åšÒ št5+R‡H­¨-qYµŸ9Dþ8œºCJ­ð7�Lׄ | ã�$I’Tœßöá¾@�¬Ð ç²û„ \�‹I’$IÅ:¨'gzê)¥ÂKÞÃÞ¥õüxgQ÷”$I’¤çå%NKÝ ¥TÈ ZæˆZ·Çq?I’$IÚRˆœšºAJ©á€j™×·G4ú^’$I’´S¢¯Wkè`ñ,Ž�p}„ �¼�$I’$í€Ý–œá[ÈÔ¹6¨–91~ŒkÔ=$I’$igä]–ºAJ¥!€¾Ùœá:`l#®/I’$I»"ä~@©ÎU÷À¢2Sˆ\ Œ©÷µ%I’$i8r¨¥n�R©ë`ñÏàGÀøz^W’$I’ê!s V·À]e&„œûÕëš’$I’TOµÀªÔ R*]õ¸HµL7P‰x †$I’¤¦Kpoê)•a|#ÂIõ’$I’¤y¤§ÂšÔR*ÃÞP�ÅŸx_=b$I’$©Q,JÝ ¥4¬À¢2'_¬S‹$I’$5LŒü uƒ”Ò.ªeö�pÙp®!I’$IEÉrþ3uƒ”Ò.�!Dø<ñ_’$IRkX‘g|¨Zæü»ÊLH#¥vå‡úfó'Dþ¹Þ1’$I’T€¼®§ÂÓ©C¤"íô€…eŽ$ò÷�ˆ‘$I’¤á ©#¤¢íÔ Bè‚ �ÝÔ#I’$IEø@µLwê©H;5è/ó�'5*F’$I’ 26Þ˜:B*Òªeö·þK’$Ij9¼5uƒT¤ÄÈç#ž–)I’$©m™:@*Ò –œÉk|´Ñ1’$I’T˜È!©¤"íÐ`0ço€‘ n‘$I’¤"œ:@*Ò+Ïáð,rf1’$I’T ½SHEzÅ@ˆœ¡TDŒ$I’$hDœ·soF“ZÙvÿ²ßSæ"s‹Š‘$I’¤"ý¢ê£ÎêÛ”Ægÿ%I’$µ©nèNÝ åe7�LW9§ÈI’$I*ÒˆQ ¦n�Šò²€}&pz„ŠŒ‘$I’¤Å©‡³.u„T”—døì¿$I’¤ö`}˜GžºC*Ê6Õ2c€S n‘$I’¤ÂDx.uƒT¤mbàô£‹Ž‘$I’¤=“:@*Ò¶È9½àI’$I*ÚïRHEÚj!˜‘"F’$I’Šà‰Ô R‘¶ô—™ì› E’$I’Ѝ³l5È¿—"D’$I’ŠO¦n�Š´Õ Ëy]ŠI’$I*R€e©¤"m}@à¸!’$I’T¨È©¤"m6¸µÌè¯M#I’$IE<˜ºA*Òf€=r‹PJ#I’$IE°nZ…ÇRwHEʶøêðD’$I’T˜SwHEÚl#‡¥ ‘$I’¤¢„@_ê©h›ï¼:Q‡$I’$&ä,JÝ m˷앤B’$I’ ”ÃâÔ RѶì�¤B’$I’ ”w¨ól~ŒN"I’$IY5¹ÂoSGHEÛl| $I’¤öw»oP'Úr€I’$Im-nKÝ ¥�mñ…S0I’$Im-‹Ô™¶Ü°:Uˆ$I’$ ÖFp{ê)…-ßðl’ I’$I*Æ’©—²2u„”Âæ€ÀªD’$I’Ôp1ð“Ô R*› j‘¯kµH’$IRCe¹u®ÍÓ*,‹ðùT1’$I’Ô(jù¯ÔR*[ž@oÿü,A‹$I’$5Lwó]{VçÚjæ‘g5æ‚cH’$Ij#�¥N�RÚj0ù*~#ç#I’$I SãªÔ RJa{ÿ²¯Ì�ÀŒ‚Z$I’$©Qè­pDê)¥mîXXæHàÄ[$I’$©!~ú/½ì  Œ(°E’$I’"�|7uƒ”Ú6X4“£²Œ*ÛH’$IR+°lr…ÃÄÔ-RJÛ\à—2>ýrÿN’$I’ZIŒü»‹i‹üed0+A‹$I’$Õ[§Ž�šÁV€5kùƒ{¤ˆ‘$I’¤:»¡§Âé#¤f°Õ ÀY)B$I’$©Þ"|5uƒÔ,6;pÑYŒÏx™¨G’$I’ê"Àâɦùü¿4d³ÙFÞ†‹I’$Ií òw.þ¥m6ˆǧ ‘$I’¤z °lÝ^ÌOÝ!5“Í!rbªI’$Iª—>5ý"RwHÍä…3ªeÆDX t%ì‘$I’¤a‰pÔ oMÝ!5›—îx=.þ%I’$µ¶ 1çã©#¤fô FŽJ"I’$IÃøÔÔ+Yš:CjF/ š2D’$I’†%Pé�Ï×RgHÍêÅÁ€$I’¤–uÿúȇSGHÍìÅ€;$I’$µ¦õYÆœéžI"5³—>pHÊI’$IÚ!òÉÉWpWê©ÙeB€=SÇH’$IÒN Tzpaê ©d‹æ²[„RêI’$IÚ �•øãÔR«È²AƤ‘$I’¤�CäœIW³"uˆÔ*žŒM"I’$I;*þ¹g×¥î�ZÉó‡º@’$IR«Xºaç§Ž�ZMöÊß"I’$IMc0æÌ�~-Ï¥‘ZM2S‡H’$IÒ+‰�ÿ=åJîLÝ!µ¢ ` g uˆ$I’$½‚_¯Žüuê©U íp I’$©¹Å||…u©C¤V54á@’$IRóŠ�‹z*ܘºCjeC¯¬±6uˆ$I’$mK€G7DÎKÝ!µº `r«ÔRÇH’$IÒ–røøô Ϥî�ZÝÐ#óÈ#¬L#I’$I/àú)®NÝ!µƒìù_X‘2D’$I’¶°q>ž:Bj/ ",O"I’$I[øÇiîM!µ‹®—üÚ€$I’¤¦àQà‹©;¤v’½ä׿MV!I’$I/#Ÿî©°&u‡ÔN^< òPÂI’$IzÞ-= ¸"u„Ôn^ºàád’$I’4$ÆÈybê©Ý¼x`É’$I’ÒŠ‘ʔܚºCjG/ jùMÊI’$Io ù|ê©]½0˜z5OÏ&l‘$I’ÔÁbà듯ä¾ÔR»zñÀ¡gl|Ǧ$I’¤VçüMê©�e›}éOÔ!I’$©ƒø?Ó®æÉÔR;ÛlKS…H’$IêX«6ðÕÔR»ÛrÀ’D’$I’:UäŽù.«RgHín³@Vr I’$©PO�é§ÿR65‰û�5‰Z$I’$u˜ÿ8ñ;¾�L*ÂægÌ#%j‘$I’ÔYVŒêæŸRGH�¢k¿wp|Ñ!’$I’:Là+~úßÙÎe÷l#—"äp@ˆìMdd„=bFWÈ$ ¬ 9Ïç‘G»r–Mºš©û[ÍV€¸;Ä)’$I’:ÈÚùZê5Þü2¥)‡ç9Scdj L 01À�q={䛾7„¡_‡øâ¯‰Ÿÿýµ ªe–¿Œ�Ÿ F¾{t…G‹ûSµ¦°åoTgrL̸+EŒ$I’¤Îà+=>™ºCõW-sD œ@Î Žz€Ý|Û¸-Â×—?Åe3nf°Á÷kI[ÊtGx• G’$IRûÈjLœ|¿I¢á™_¦Ô“35/ñæ'9Ø/eS€e1ð…Þù|+eG3ÚjÐWægÀ‰·H’$Iê!òížÌMÝ¡]³ôLöÈ95DNNöLÝ´-~Pƒs}4àEÛ:"?'8�$I’Ô�H� 7¿Lé(8:NÏ"ï¬qlx™“›I€·wÁ�}s8µ÷ ¦îiÛø…çJ’$Ij€÷T¸'u„¶ïÎÓÙmÔhN#gf€·G؃¸é�¾Ö²/97õÍám½WpGê˜Ô¶9èÁÏy陋’$I’4l1òO©´m›ýÿ-DÊÞMd,¡%ý[OÎ÷úÏàØÉWñxꘔ¶98ò2–÷•¹˜Xp�$I’¤6`YàºÔzÑÝïfÏÝü‘™ÀïÙ þmÙ7/ñí§´ÇLc×lû €!?Ç€$I’¤:Éá_gW¨¥îètóË”z3bäýÎ ²{ꦂ¼¥o6eæ3?uH*ÙËþ›À­vH’$IjcÖu òï©;:U„°xÇ÷Íæk“á‰á$<Ü}3ÞO‡¶û¡¯Ì€Ó j‘$I’ÔžöV8:uD»«–9:‡€³ñƒÜíàUÇ|—U©Cж½C‰�[Bt I’$i×…È7R7´«ûÎfÜúÎÚôiÿôÔ=-bDwo‚Î{#Åö‘›Ü+"I’$i6øNêˆv³x&Ó)ñÑ ™`·Ô=­&â`++žâŽ X Œ-¨G’$IR; \Ý3Ÿ§Sg´ƒûNcäú1Ìð'À<Ðo×…À±©RØî`ÆÍ VËü4Â;Š ’$I’Ô>2Üþ?\KÏdÿ�œs7F>`Bêžvá Ô )lw°É�8�$I’´ó~3i27¦ŽhEBu6¿O䣵ïPòÿºÚ?u@ ;:�$I’¤�—†yä©;ZIµ¬aî¹ IDATÌàƒUøS"wú7ÆÞ7�L׌›LR¤WLîaQµÊr`Ÿz$I’$µ‰RàÒÔ ­¢ÿ ö«uñ‘ù8°Wêž^óº ³Ù+}æ‰ÝÍ�O‘$I’Ô.,ž|}©;šÝ¢2ÇUË\œ—x8Dþÿ…y|-÷Ò»y"7˜ÕàI’$Ií"rYê„f5¿LiràíDÎŽw‹»�zåÄÛÍ j�Ÿ”]"I’$©]ÄÁÀå©#šÍÝïfÏÝ|„ÈLj˜º§Ãå÷®g}ꈢíð–‡j™G"ÐÈI’$ImáÖÞ '¤ŽhÕ2ûæ�? ‘O{¦îË{+�÷JÅ{`È�ÀÜF…H’$Ij‘Jê„fÐ?‡Þ<òg1òÞ‘ºG/ °"uC ;>:À€$I’¤í*uñÝÔ )-*sR€Ïä9§±»®UœO¥nHaÇ�Ÿ4°C’$IR{¸{Òå<”:¢hqYµŸw„Èg#¼)u�¶/À²Ô )ìð©‡=h`‹$I’¤è¬Oÿ«eºûfqNµÊR"ßsñßbäשRØ™3 r#�ÃÔ"I’$©Å…Œ«R7aá\vÏÖsN„O8(u�vN€{S7¤°s€¡ƒ?܈I’$I-ï�ÉWЗ:¢‘î~7{v�àO²õ|"Òy§È·�À}©RØ©@ÁMa�ˆYH’$IÚÚ5©eá{xU6‚O†ÈŸãbê ×[€{RGm§ò‹Ë,ÐÛˆI’$I­+DNéYÐ^‡‡WËœ>�EΉ0:u�ê#ÀS¥ ~Ô÷X�º¥H;ûYäÆH’$IÚÌÚîµÜ’:¢^Ïáð¬Æç"¼/DºýÄ¿½DX7°{Bg vø-/qcÝ+$I’$µº›&^džÔÃÕ?“‰}³øfÈYt§nRCLΗb‡=Þ¾Ó€u¸ž›’H’$Iz‘ëR' Ç¢™ÖWæÂ<£ŸÀØ…ÝÒj-!rv™óRwi§Ó¯å9W7"F’$IRkŠ%~”ºaW,žÃ¤j™‹³Œ{�sqáßQ"|qñ,Þ™º£(»´Ý¡6§ä‘ëë#I’$©%ýº·Â‘©#vFu&=dœœ¡”ºGI­9oê¹’jê�FÛ•3˜4™ŒLÝ£–‘G8uJ¥=ϼÖô+D.©Wˆ$I’¤¸)uÂKÝUfBß,¾jü:þÿÚ9Y€K–žÉþ©Cax€œKÔê#I’$©Å4ÉóÿKÞÃÞ}³ør7#·ë"I’$©åÜ{äe,Oqãj™1}³9/à�ŒIÑ¡öá³ÕYœ–º£ž†=ÈGq°¦-’$I’ZH ü¢è{Þy:»-.ó‰÷ù20¾èuŒ,¾½äL^“:¤^†=˜v k\]�I’$I­#˹½¨{-û £—ùĨQ<ààÕEÝ[m¯Z�ïÜt2]©Cê¡.ïÀÌ£�H’$I�¦¸µÑ÷¸ó\FôÍâýÏ­¥ÓÂßFßSÚÂñû¼ŠÏ§Ž¨‡º z{¹!À£õ¸–$I’¤–°z)ô7êâqYuåQ+é'ð­‡6ê^Ò+ ‘?ïŸÃSw W]a9‘Ëêq-I’$I-áŽÙ•ú¿<Î#ë›Å™ýUúc`>pD½ï!í‚®<çâ…sÙ=uÈpÔeK> I’$uŠêÿüßlÞV­r'�Ë"YïëKÃ4±´žL1uô^ÁBàžz]O’$IRóÊrî®×µúæðº¾2?!òCà˜z]Wj€sûfszêˆ]U·@ üG=¯'I’$©9Õ"¿î5–œÉkªe.fèmo©C–Ôp!ò�j¹5£¬ë zÀ†z^S’$IRs ðÌ”+Y¶«?W™ }³ør­Æ½æ]Rj &Dø�Ø‚oë:è©ðtŒ\SÏkJ’$Ij.îwöçªeÆôÍæ¼nx€Ày@wò¤"œZ�Í9©#vV]™�H’$Iíî®�ùæj™îj™s#ÜOäËÀØuIʼnüß%sxmêŒ�Q÷@®~[ïëJ’$IjqÿÞôJ¿÷Gøu„ �W78M*Òîƒ9ߺédºR‡ì¨ºfW¨øV½¯+I’$©9d9}¯ô=‹Ë¼£¿Ê=›Ö‡�%.À÷™Ày©;vTݵȿy#®-I’$)©ü™Œ¥/÷/«3yC_™›|?”"äüEu&=©;vDÃN-ì+s#0£Q×—$I’”Äý½&nù›‹frTV⯈̢OG—†éö~8av…Zê�íiÈ€ˆ‡J’$Im¨ÿ¥_ÜS怾2f‹‰”qñ¯Îô†É‘�§Žx% ¬†ÀªF]_’$IRý‹Îb|ß,¾<îÎ…Ö9MjˆÀ«eŽH�±= _a] \ѨëK’$I*^ˆ<Ô7‹Ïd<@༣S7IMb·_�M¼ ¦¡aÕ2¯�p{#ï!I’$©P�¦Ž�šU„s¦TøFêŽmiød¢¯ÌB`j£ï#I’$IRxº6ȤiWódê�-5ì€çøV£ï!I’$IR“Ø«ÔÅߥި–†²A¾lhô}$I’$Ij¨–yKêˆ-5|0éjVÿÙèûH’$I’Ô$B„½ï4F¦y©†²Ðœ H’$I’Ô ¯Ý¸;ÿ3uÄKòz‚ùeJ“áAàà"î'I’$IzÁêÏÆÍŒÙôë‘Àni²:Âs5è�VaYê(ðý„}³ù "UÔý$I’$©<<@àÁy <#tÕxlpÏN¹”Uâö.°è,ÆwÕ8 æ˜öÎ"S#‡âÖõ½Þ•: ÜSæ€.xè*êž’$I’Ô.<áöøE¹mpwO»„µ�¼ç½ïeŸ�Ç‘ó:§xc„R#ïÙ–§öÎçGé3 ÔWæhŽÉ‡$I’$5¹%D~ày·M¹œß¦º«Ì„îÀÛ‰œü>06uS‹X²~<Ó¦_Ä@ʈB‹Ë¼#À÷‹¼§$I’$µˆAàv׿5®™z%KSmϲ2jífø$pLꞦù½ ¸ eB¡”$I’¤Í”êþ…žÁ�]%ò/ƒ$I’¤³4®èÊøæ¤Ëy(uL#UgÒ3¾ ¼3uK“ÉsxýÔ ¿JqóÂàa€’$I’:ÆÆ¨d5þ©çJnOS´ê,N‹�¯ãcà/uso…)nœ¥¸iŒü[ŠûJ’$IR<"_�%Ž˜2Ÿ÷uââ g×�ìf pQê–&ròâYivF$Ùç‘U«<’âþ’$I’Ô ¿Žð/ÖóoÓ¯å¹Ô1Ídñ,Î� �}R·4�%O=ÅÔ73XäM“ ÀÃ%I’$µ�·eð…É~ ¦îiVÕ2ûæpE€ßKÝ’ZˆüaÏþ£Ð{y³—ò0@I’$Im`aˆ|qò¸ðß1Õ2Ý1ðoDÞŸº%¥�®[Ïk‹Ü)’ä €iWñH„¤º¿$I’$ â™ÝSᘞT\ü︞ {æóAçÓÁÿ½E8`ÔH>Vä=“íX<‹w†Àµ)$I’$i',"ðùžù|ßEÿðUgqv |™º%‘•ùŸz)+‹¸Y²K׿IÙ I’$I;`E„Oöñ½ó¹ÖÅ}ô,à;f©[_à³EÝ,éð0@I’$IMm=pÁÈn¾4ñ;<›:¦]õÍæ½!rI‡ž·~Ž8ºÂ£�¾QÒ!ç":wÚ#I’$©y}?Ïéé­ðYÿ�Õ;ŸËbàCtæÎŠQ#"Ÿ)âFÉwô•YÌLÝ!I’$Ií©pKê–N³¸Ì'\�º#�Bv$ß"ÿšºA’$IRÇ[_¦»øOcJ…¯øJêŽ ÙÐ;"„þ2K"™ºE’$IRç‰ðÓ˜ó‘©W²4uK§»édºö™ÀOü^ê–‚5|@s쀦î�$I’ÔqVøHo…“]ü7‡73H³�ÇR·¬á»šb�wñÍëRwH’$Iê�Ë7‘=.òµ~ÍeÊe<"gÒaÆÇÀ¹KÏdÿF]¿iS/ee¸Tz_°ªe.ˆð‰Ô’$I’ZÖ³!rNÏ*©CTÕ2GG¸èJÝR�‡žzЉ3nf°^lºyÆ…øI’$I» Â]1ãXÿí¥§Â=1ð�ÔzÍ„}˜UÏ 6å`Ê,nNÝ!I’$©µ¸dÃzÞ<å HÝ¢ú+ òà¹Ô… œë¸s¿)!ò¯©$I’$µŒçBä}=Þ?ýÚZ v˜ÉWñ8�NÝQ iýefÔëbM;xr9W|=‡$I’¤Wô'÷,à;©CÔxù°2uGQ"|º^×jÚÀŒ›Œ‘OÝ!I’$©yE¸-«1½÷ îHÝ¢bó]VEø‡Ô:ua™#ëq¡¦tuñu` u‡$I’¤&¸|5¼eòU<ž:EÅÊGqð»Ô ¥ÈGêq¡¦u9�פî�$I’ÔTr"çõÎç½ÇWX—:FÅ›v k \�º£0�ÞZfôp/ÓÔ€ùZêI’$IMã¹ïé]Àß§QZ!òotÎÆï9s¸iúÀ”Ü`qêI’$IÉ­ ð¶ž ßK¢ôz*< |;uGQbà�†{�¦äøJ@I’$©Ã=–ÃI=nI¢æà+@LÝQ�×/*sÜp.Ѐ . ðLêI’$IITc‰7N­¸3X›ë©Ðܘº£(Ù0w´Ä §ÂšÇ2´.o±Çd¾¦’€æWß�î�$I’4u®hÀ½í_3¥èåL GwtÈž˜yÍd¾ ’€_Œn�$I’4e«ò^Ýl³!:DÝcàn~ÝÑ)9ñÖɼ¾²€=÷âTàŽèI’$I“vCîå�~“»£Cԕ΋è Wýð P’$I*½Ì±Ëørt†j¢FÇ2<±€—M䵕4—ñ]àºèI’$I;uf³ŸOFG¨>ŠÌw¢:)e^?‘×U~� “ùRt‡$I’¤º~Þ––PD‡¨>qpStG§¤Ä2¤Ý½®ò€ÑÄ×��Ñ’$I’e´Ñà°ƒÚÜ¢Zº*: S2‡Ã^ÎXtˆô×GtXêíÙù1€®�½ P’$Iê�[Æá• ¿ÉÝÑ!ÒãÜ�Ði¹ÁËwö¹®4¹$ÁêèI’$©ËÝ™ þhQ›£C¤é�ÃÑ —k6(_Œn�$I’ºØ­4øýæ#Ñ!ÒÎl™Ãú膬~3¿º£Otí ‘9 ¸/ºC’$Iê6†sƒ—ö/åšèiW�ÈÆ›¢;:,5¶ò{;úDךm6߈î�$I’ºE‚ñ”ùòæQ^8°”ŸD÷H‘áÞè†/ØÑ{;]ÑI¹ÁSÁQ@Šn‘$I’*ê~àZàÊq8ná wm©rê÷˜øÄówüá.7ÜâBààèI’$©¢†úÛ!MÕH‹2ìÝÑašMž˜–PlÿÁ®=ð°ä#%I’¤iøet€4Æ¢ì=¼–ç<öƒ]?X;g&¸9ºC’$Iª¤\¿ç¨«ëÔï�Æ  ë/g 8.ºC’$Iª¢Üà–èišæFDH0ðØ�uýàAÇQÓ©�$I’4=pktƒ4M{GDȉ…�ýX-Í6·æÌéÑ’$IRÕäqïPåí¤¶;ȉ/F7H’$I•Óp êÊKh{FwyúÕ-öÝþµ,ló½ ÃÑ’$IRU$�õÑÒT]¿Ž½�ÝeN¦¹ýïk3x�»$I’¤ ʰ~q›ñèiªFÇxftC¤Ô`ÿí_«@Nî‹î�$I’*Âíÿª¶‚_‹Nˆ”r�Í6ÈœÝ!I’$UAv ŠK‰gD7D*¨ñ ÷ð GwH’$Ie×Èü<ºAš–‚ÿ�ìÙÛÿ¦v€�¥¬¾Ý!I’$•]†›£¤éH°_tC¤üfÞîÄÚ Rö2@I’$iÜ Jˉþè†HöXõÆGXËÀíwpzrš)I’$íZƒ›¢¤©zð�ï¢;¢Í™ûÈEˆµ¼œ12ÇGwH’$Ie–²U×êCØØ'º#Z.j>èéåx`kt‡$I’TRù^wͪÂzõÞþÿ�œ°àTn!qFt‡$I’TRw¾¸Í¦èiÊ2/ŒN(ƒÜp@.¼ P’$Iڑ쀪¸"ñÒè†2HÔüÀ‡ ²<ÁêèI’$©lžÿWu­|7sÝQ)óÔ‡~]ë@Î|9ºA’$I*�Ì/¢¤©š/ÏöŒî(ƒë݉ÀýÑ’$IR™¤Ä Ñ Ò”�sptBY$�Xp÷“81ºC’$I*ª²”xutC‰8Ø^Nü�£;$I’¤²Ëü$ºAšŠ•-ž˜áÅÑ%²ÏC¿p ,em‚‹£;$I’¤²è�£¤©˜—x0'º£DöÈ�ÀÀÊä#%I’¤ÝÖl³!:BšŠÝP2�-惀‡Ýq;§%¸9ºC’$I*·ÿ«’Ö½Šyd^ÝQ6OÛöD:x9cEâ¿£;$I’¤h9¹ý_Õ´u^<)º£lÆæ9xœñÌñÀXt‡$I’)y *j¼àÍÑ e”·n»ÁÀvžÛæfà[Ñ’$IR(¨ ºæöJÉóÿ»âà1Ræ+Ñ ’$IR¤T8Põ4F9Ø+º£Ì�·ÏŽî¨wLT‚œÇGwH’$I³-e~Ý íκW1�Ì_EwTEÊ¹=œlŽî�$I’fS‘¸6ºAÚ�Ñ}øðëÑU‘Ý09Ï9…;ÈœÝ!I’$ͦîP¹]Ýb߆ïþOÖÏú…€ *¼ P’$I]nÌ€JnNâï2<1º£J²€Éhs1°.ºC’$Iš%£?Þn¡ •ÍH‹¾”ygtGÕôôðÓ‡~í`‚ä_‹î�$I’fÉu‹ÛŒGGH;’!eøÐÝR5szÝ0%==|‹î�$I’f\rû¿ÊkM‹·/�»÷?™ûú�€IXp*·çFwH’$I3®ð *§µoä©À?GwTQ~̽&)e¾Ý I’$Í´ä€*©ñ>—aßèŽ*jdÖ<ê÷Q!Uuû| ¸5ºC’$IšIEv òY³˜? qdtG…9˜Žƒ—3Fæ¢;$I’¤™4>×E7HÛ[÷žPlÛ��¢[*ÌÀtõôð5 GwH’$I3ä–í/ “Ê`óV>üztG•¥ÂÀ´°”ë€K£;$I’¤â€*•Õ-þ�Ì;£;*î®N{ä€à`ê2ÿ� I’$Í/T™Œ´xJ¾Ž[ÿ§kezÌÎuSt_¢ ÜÝ!I’$MWá@%RÀW3<3º£ò2W>öC¦èÅm6åÄÒèI’$iºRò€Êax1G%xCtGWhðýÇHS–_�n�$I’¦«§áÅ[sý)ó/ÑÝ¢1ÆÊÇ},"¤[ô/åûÀªèI’$iªlZ°àÑ…I�6Òbï¢`Y†=¢[ºA‚ûNã—�ý¸€iÊî�$IR…eX—–PDw¨Þrâxà€èŽ.ráŽ>è`𙓀ÑèI’$i*¬�nP½ /æ/ÈÝÑMrâ‚}ÜÀ45ÛÜ•àÌèI’$iJ²ÅiñR2ÇFwt™œ2íèfBæ¢$I’¤©(ãÚÃyF†eÀœè–n’a¤ÙæÖ}ÎÀ ¸ý·ÿ,I’$•Yv€\s{mçlàéÑ-Ý&%þwgŸs0^ÎðÍèI’$i2Œï±�ë¢;T/y �Æ('%x^tK7ÊÅÎ�¨;˜!�_�n�$I’&#à ûŸËæèÕËÈ>—à Ñ]ê®;îàÒ�}ÒÀ é[Ê0pMt‡$I’4 k¢T/ÇògdމîèZ‰sÜ¡¾CfPÊœÝ I’$M‚çÿÕ1Ëy‰/Ewt³]mÿ3+q2ì|Ú"I’$•JâÚèÕÃêCXH¦ ôF·t«›‰ïîê5fЃ�Z8?ºC’$Iš�ä;5oâétqe IDAT©ÁÙÀ>Ñ-]î¬f› »z�€–á¢$I’¤ Ƚ›Ü Ùµî-0˜áeÑ-µ�9å ãغ»—9˜){ @’$Iå–²Ûÿ5;V¾›9–%xutK]äžÝoÿ³¢¯ÍeÀO¢;$I’¤]ð@͸¼„Æü»9x}tK�ü¨)ßŸÈ Ì‚9§‰M`$I’¤©áͬ id„/ÝR'þk¢¯u0KŠmÇrt‡$I’´#£Ù€fÖšCùgàÝÑ5sO1ŸS'úb³dQ›�Ë¢;$I’¤¸óymÖGG¨{ŒÊ?äÄG¢;jèk‹NdãD_ì`6%¾� I’$í€çÿ5cFZü}N|2º£†r£àË“ù³¨èeØÝ!I’$=†Ûÿ5mÒp‹ÏeøTtKe8³oˆu“ù³há7¹8?ºC’$Iz”ä#5=y �‘Cù ð¡è–ºj|vÒ_3!zD‚S¢$I’¤í%/Ô4,kÑ32Ì×H¼+º¥Æ.hqåd¿¨w6Jôˆ±ùœÙ3ÊF`¯èI’$  ÑãMÍH‹¹¾Iâ�è–:K™šÊ×¹`–-:‘�9ó­èI’$ Á¦ øyt‡ªgÝ«˜—¡ .þƒ]Õä‚©|¡€NH.£$I’4›2\—–PDw¨Z®}ûlޛ﯋n©»;Õ¯uÐ{ïŹ î�î�$I’nÿ×äüøÍüÊØ<Î^Ý"–7Û|gª_ì ö;�Ñ §GwH’$I…OÐ$¬:„g��q)ðÂèAŸœÎ×;è�H’$©Rá@3Òâ�+2<'ºE -l³b:ßÀ@‡¬¿� €Û£;$I’Ts=>P»·f1oÈpð´èA‚ñÜ௧û}tÈÁËK0Ý!I’¤Z+F`]t„ÊmäP>�3ƒÀžÑ-Ú&'¾2°túÃ;”< I’¤@ ~vÐÙ<Ý¡rÊ�V/fIN|>COt�vgÏVþf&¾‘€:`—?‹î�$IR=eŸ �X÷*æ�,æ›)Oýsš>vÀéÜ9ßË@%È91Ý!I’¤šJü8:Aåsu‹}7ïÍd�nÑ£e¸¢¿É×gêû9è´q�H’$)†OÐc­>„…óàJà%Ñ-zœ¢G§%3õ tØÀ+Á‹W$I’ÔyãÉ€±z1‹Ó¶ÇüíÝ¢Çˉ/4Û\5“ßÓ@„ì.I’$u^êu m—ý /æciۺěþK(Á��Ì'gúû:ðà#5$I’¤Nº{àn‹ŽP¬uoá #-Î"óY E÷h‡Šqx{³Í†™þÆ ± ¸.ºC’$Iµ2ígˆ«ÚFZ<{ó.^Ý¢�ˉ/,ló½ÙøÞ‚$83ºA’$Iõ‘²Ûÿëlx1¯ÈpÐŒnÑÎÍÖÖÿ‡82§G7H’$©>2>°Ž¶;ïÿ-àÉÑ=Ú¹ãdÞ6[ÿâ È@›+€_DwH’$©’O¨�•-ž8Ò¢Mæ³z¢{´k9³¤9È%³ù3I�œÝ!I’¤z/¼ƒªN†ãùóáGÀ!Ñ-š� ›ýüãlÿ� �H’$©3Š=àÆèuÆH‹wSp)ðÑ-š�Û{{8"-¡˜íÔ;Û?@;·ùÉ,Ÿ7wO‰n‘$IR÷Jð³ýÏest‡f×µ¯cŸ±ù—3‡G·hŠœ9bÁ©ÜÒ‰æ€@ÇVçDwH’$©»ð“èͮՇ°pl+qñ_-™ä»�úq‚5< I’¤YÖ€uÑ š=Çrdj°ø­èMÊYÍ~–tòz Ø=™óž�½¢[$I’Ô¥²;ºÑº·ð„-›ùBN¼5ºE“ví(Ù‰sÿÛs@°·ÙœÝ!I’¤®v}t€fÖÈ!¼pó®vñ_Iw7 ^wP›{;ýƒ”@N�$IÒ,ʺÅE/§wx1Ë .~3ºG“V4oíŠ9–ã€ÛÂ9sæ°˜Ý"I’¤®SÜÛà†èMߪCxVOƒ“ræEÑ-šš”9¦¯Í·£~¾;JàÀ3¸¸(ºC’$I]éæ��ªÂF¥ÕhðƒŒ‹ÿÊÊÛä?#ÜP)szN¼"ºC’$I]ÇíÿæE]"sJs�ODg¸ ,gBgo€”$IR xþ¿²†sðæ-¬rñ_m Î} oK�£[”D³Í­ÀÑ’$Iê:*æš#Øk¸Å’¹ø£{4u®îÙÌ!ÇÖèpP*9ñ­èI’$u—ì RFåw{F¹x?�¢{4-kéåÕ Îâþè�‡8(‘Æ8çF7H’$©Ëx .o±Çð¡|–ÄEøx¿np}o0p ·E‡lÏ@‰ô ñ#à–èI’$u�\ìÉO¢#´kkãwžW“øX†žèMÛÏ{zøÃ§–omç Dä”ùNt‡$I’ºÆ/�ÈÆèíØ�ÊüáCùl.¸XÝ£qÓ8¼ü€SùitÈŽ8(›ä1I’$Íßý/©áżdãFßõï2¿È ^ÔæÆè��é�УÍ�Ëw7oa+0'ºE’$IÕ–2ë¢ôh×Á^=›øë”ùˆ ÿ®òÓØ¿´ÜC7w”Ìþ's_‚Ë£;$I’T}E£Ü‹‘ºYÝâ�{FYã»þ]gíø8¿Ûl—ÿÂMw”PNœKæeÑ’$Iª¼Ò/Hê`õ›ùÕÆÿ’áˆèͬ?èíå•ýmîˆn™w”Pöq€’$Iš�q�D9”VcÄÅ÷ÉpñܹüÏ9¥‹pPJC¬nŠî�$IRµm/ïedÝn¤Å³‡[\˜Ë€§F÷hÆ�u?¼rÿ“¹/:d2”•�”$IÒôÜ}àÜQ7+ßÍœÕ-ŽÎð#ààèÍ‚Ìñë×sÈ‹ÛlŠN™,ï(©œ87Á»¢;$I’TY?�¨›áÅÌÝ|8 ºE³"çÄgÚ,‰™*%Õ€ó3læE·H’$©z’€Ž¹öpž1>ÎgsöœÛ˜2oéosftÈtx ¤šm6—EwH’$©šrv0Û.z9½«[=6ε^ò×Õn¥ÁÁÍÁj/þÁ@©¥Ì·£$I’TM9ñ³è†n¶f1¿÷+ûòÃÿìÝ£Yó£Æ8¿Ó¿”ïG‡Ì%Vô8�$IÒÔô8˜kÞÄÓGZ|£È,OÐÝ£Ù“2'�Žò’¾ÓºçÏ’w”ØÀRÖ·¸xVt‹$I’ª¥H>p&­|7sæÝÍû ø ð„èͪ1ÕlsltÈLs@ÉåÄùÑ ’$Iªž­›»ç]ËhËyíü»YóàvÿÝí¶Fâ÷û—ußâÜPz�‚ râ=Ñ’$IªŽ÷x÷DwTÝÚÃø­¢àßræ�£[4û2\Ý3ΛºiËÿc9(¹Æ8Ž÷Ràn I’$MPÆíÿÓ1Òbï"ñ‘ñ‚�ãc¹ë §ÌÎÛÈ_î.›£cf“€’;àtînq p`t‹$I’*£kßÁœMËZô4á�þ.ežÝ£Ù—`}oïä[Ñ-�à râ”H’$ibrâçÑ U³f1Pd>—aat‹:fùVxësÛÜÒ)n+¯€FÁÑ ’$IªŽTð‹è†ªX}Œ´8§Èœ�‹ÿZH0žŸ^P§Å?¸ ÆöàâžQ¶s£[$I’T~‰z-j¦âÚÃyÆø8ŸÉoϾ1Z'?ŽXÆ¥Ñ!ü½�ÈFàÊèI’$UCjpStCY­|-{/æccã¬ÍðN\ÕG¢]ÌáyÍv=ÿà€ÊH™ râw£;$I’T~cãx¬‹^NïÓöåþ–Ì3¢{ÔQ·7ïé[ÆÑ!ÑœvUDjpQtƒ$I’*!ï³·DG”E†4r(­}÷e$ÃWÀÅ�$8} ô»øßÆ1ç~®Ü¼7£ÀüèI’$•ÚúýN`4:¢ †ó’‘̱ÀK¢[Ôq÷%øh³ÍqÑ!e Øÿ\6g¸*ºC’$Iå–ðüÿšÃè^Ì22—â⿎.Ë žçâÿñÜP!)ó=¿Ý!I’¤òÊ5~À5-žÓ“XR,Æ7;k'Á¦>Ñßæó rtO9¨�Fƒ‹ ÿ1–$IÒ®äúíX{8¿1>Î'€w�]ãÔÔª¢àˆ�!VE‡”™8*ä�M\>>[€¹Ñ-’$I*§T£#-~=çÆÇy®mêjŒÌçRâoú‡ØSvþ!©�ƒÎæ�á+�G·H’$©œêp`õáü >•3oÇ7ÇêìZÙ¿”ïG‡T…€ê¹’$IÚ‰F£{xÍyZObœ£ñéXu–�ãÇçó¡E'²1:¦JTLÊ\œ�î�$IR9åq~Ý0Ó®n±ïÜ̇SâƒöˆîQ¨_6Ö·ŒoG‡T‘€ŠÙ”¸|>x«©$I’v w.·E7Ì”«[ì;>šá({zv½%8qËV>xàÜÝRU):@“7ÒbU†�èI’$•Ζf›ùUÚH‹§�ùpN|Ø;ºGáî ñÞþe E‡T�;*¨H\ž²I’$=έU^ü_óFžÖ˜ÃûræhOŠîQ)œ×ÛÃ;œÚ½w[t’€ J°xOt‡$I’JçÖ耩X{8¿QŒñ$Þ•³güÀ>ÙßæóUj•�€ êI¬÷�€$I’¯RŽB“çÍ$×&ÚæÁ7<�ìos}tK·ñ"¹ Z°”u ÖGwH’$©t*qàH‹¬nqzn°*ÃøÆ¤¶͉�ö5yiÓÅÿ¬ðZ%ÈÃpðÚèI’$•GNåÞ0Òâ¥>–á5ÞF®íe¸‚ïXÊÚè–n怊J™Ñ ’$I*—FQ¾@†4¼˜×Ž´¸<Ã%Àk¢›T*£$>¾^êâö¹ ¢Æ—;½‘$IÒc”æÀ‘ssæð‘Ä_’iz…•+ÁŠñ‚w,âÚè–ºpPQy>+Ó(ãz¢[$I’T9~ðã7ó+[Çùóœ9ŠÄ¯F÷¨|lþª¯É¿§%Ñ=uâÑ› i±*Ã@t‡$I’Êažµ¨Í�?{õaüfcœæÄŸ{F4¨ü|×?–;ª,ó}’I’$mÓ𤨑/͉¦‚7åäîTíX‚M9ñé‘Ì¿.b<º§®TXÑàû)óŽèI’$•Âæf› �øAë^żÑ}h¥Ì3<Ÿ žñ×.\¼£™�ö‹æ Âræûžá�$IÒƒfýÝÿ5oâéE/GnÉ| ež9Û?OÕöлþÍ>þųþåà Âz`u†ÍÀ¼èI’$…»c¶¾ñª¿ÝGp8™9¾Û¯ ¸¬Ñà,åºè=Â@…5Ûlnq ð‚èI’$ÅJ3¼àÆ?eþÆ ,&ña`¡‹~M„ïú—›€ªÛv I’¤šË‰;'û5#-æ/ s`†�œxN‚gOÙ¸‘=Iî4Õ¤,/xçÂ!nˆÑŽ9¨ºß÷ÆI’$‘'vàšûõ$“ùà /ö|èáàÞ/¥)Ú@æcÍA¾”¼²ÔT\N\•ü#&I’T{i€U“cü)™ÃÙöŽ¿4S.‡?[4È�Ñ!Ú=׿”kGZÜ<%ºE’$Iqòîiñl2È[y'°W@–º×$>ãYÿjqPq ò0\ ¼*ºE’$Iq¶¿`íaüVQðÙ o ¹³_3+Ãÿööð®Nå§Ñ-š] ÁÙ€$IR­5`ýH‹§äÌ_Žƒ�ŠÖÌ»'ÁÇšmŽ÷¬59è¬p¬+I’Ts™VN ‘xRtŠºO‚¥E/G÷ŸÂmÑ-š:×�]àÚ×±Ïø<îÎÐÝ"I’$©«ü2gÞ?0ÈiÑ!š¾Ft€¦oÁYܬ‰î�$I’Ô52pܼ¹,pñß=<Ð%2¬¢;$I’$UÞõ$ÞÝ¿Œ‹¢C4³ÜÐ%Ò¶€$I’$MÕV2ÇÎÛ@¿‹ÿîä€.1+¼@’$IÒT$XAÁ»šCŒD·hö¸ K,lspGt‡$I’¤Jy€ÄÇGàw]üw?wt‰y5\•àÕÑ-’$I’*áÜÆ8Þw?‹Qg8è")³‚ä@’$IÒ.Ý•àÍ6ÇE‡¨³t‘+rt„$I’¤òJ´·dŽz^›õÑ)ê<]dl®èe ÿw•$I’ôh?%ñÞþeœ¢8^ØE�ÈFðâI’$I+€ã ¸ø—ïw™”Y‘‹¢;$I’$ÅÊp5ïbet‹ÊÁ]&ÊèI’$I¡ ññµðÿÚž;ºL#³¢HÑ’$I’‚œ“à¨æ2~¢òq©Øe2¤5-n˰ot‹$I’¤ÎHpsNÝ¿Œ¡è•—GºL‚œáÊèI’$I1–2Ÿ¸ø×îx  åÄŠ”yMt‡$I’¤Ù“àãðž…ƒü ºEÕà€.”¼P’$Iêf÷d8¦¯É ¶]ükâÜÐ…ÆçqUÏ(cø¿¯$I’Ô]íÜÃNá6ÚÑ1ª/ìRÃ-®Œî�$I’4#~’3ïä»Ñ!ª.�t«ä1I’$© l%sì^{Ñïâ_Óåñ.•aE‚÷EwH’$Išš Óà½KYÝ¢îà [%V�£#$I’$MÁ] >Ñls|¿Õkæx@nqð´èI’$IR¤Ìq$>ÕlsWtŒº�;ºÛ àõÑ’$I’v-ÃÕ= Žê[ÊÑ-ê^^ØÍ¼P’$I*»»3³^àâ_³Í],\ž=ä!I’$•QNpÒføðóÚ¬�ŽQ=8èb÷&V>¶s£[$I’$=쇵°íŽ]u–GºØ‹Ûl®‰î�$I’À=ŽYÏwñ¯îèr .ÏðüèI’$©Ær‚“ÆÆøÈ¢Ó¹=:Fõå Ë°"ÁÑÑ’$IRM]Câ¨æ2.‹‘<Ð帵H’$I ðÐvÿßîwñ¯’ðŽønqðkÑ’$IR ä'½|tàn‹Ž‘¶ç€:H¬ ÓŠÎ�$I’ºÜªG5Û\"íˆGêÁc’$IÒì¹'Ã1ë×óÛ.þUfî¨�4ÎåÙQ�$I’4ÓŠ'{»¿ªÂ@ lz*Wïq7›2ìÝ"I’$u‰•�è[ÊÑ!ÒDù¾p t[3\Ý!I’$u�[¼§Ùä….þU5j"ÁåÑ ’$IR…mM™ÏÏ›ËsšmŽKK(¢ƒ¤Éò@M°Âg>J’$ISra*ø`sˆ‘èi:ÔE/—3!I’$UÊ/È|ª�oD‡H3Á#51p ·7DwH’$Ie—`SN|ú>ø-ÿê&œX‘2ÏŠî�$I’JìœFhžÊO£C¤™æ F¬ÞÝ!I’$•M†áÝlsat‹4[<P#iÜ'H’$I�qw†cîXÏ�.þÕíÜP## VõÁ`ïèI’$)X‘àä±1>²ètn�Ž‘:Á'ÃÕÌp‹ �ƒ£;$I’¤( ¾—Ý¿”k¢[¤Nr@Í$¸<;�$IR %¸9g>Ù7ȉ rt�Ôij¦€nû�$IR�$Ø”3Ÿ'ñ÷ýƒlˆî‘¢8¨™<‡ËÓV2ÿ�$IR=œ3\4È�Ñ!R4�54Üb-° ºC’$IšE×’8¦çE‡Heáck(e(I’¤®u‰÷¯_Ï€‹éÑ<PO—�$I’fP‘àäÍðáç-c}tŒTFê(qIt‚$I’4Sz¬_ÓÇúI»ä55ÜâfàÑ’$IÒT=ôX¿¦�õ“&Ä;j*g.�n�$I’¦"Á&2Ç úù†‹ib<PS�Ä%GwH’$I“äcý¤)rPSEÁÅÉý’$Iªë'M“KÀšê`¸;ºC’$IÚ�»Èµú]üKÓã%€56Üâlà5Ñ’$IÒä'��ñ‘E§s{tŒÔ <Pg™KH$I’T.®Îðþ…mVD·HÝÄ#5V$.‰n�$I’¶sW†cÖ \üK3Ï5¶åɬœ7{F·H’$©ÖŠ'÷öò¡çœÂÑ1R·ò€šnq!ppt‡$I’jke‚£šm®Š‘º�Gä1I’$E¸3Ã1Í&/tñ/u†Gj.e.Îî‘$IRçd¾4šøÔAmî¥�#Õ‡€šÛso.Û¸‘Q`~t‹$I’º[‚Õ©Á»û–rEt‹TG¨¹ýN`ü?`I’$Íž›râÓÀA.þ¥8î .ÊðòèI’$u¥åcðÞEËøqtˆTwîÀ…Ñ’$Iê:·‘y[›ƒµ]üKeà@W£#$I’Ôr‚çôÒß?È7¢c$=ÂûßÀp‹ï¯ˆî�$IR¥ý4gÞ>0ÈòèI�çm“¸(:A’$I–hsxž‹©¼¼PÛ$.$GGH’$©‚no$ÞÓ·Œ3¢C$íš;Àš‚«�»£;$I’T¾Ýç¹.þ¥jð=l¸ÅÀë£;$I’Tz÷%øh³ÍqÑ!’&ÎzXN>P’$I»uQî¡ßÅ¿T=ô°œ½P’$I;•É»þpàTnŠŽ‘4yÐÃ2¤‘¿~5ºE’$I¥rgÊÑäÜèISç=,ANøØI’$="Árºø—ªÏ€¥€ó£$I’TC›Fù=·üKÝ¡7:@å2ßé…ŒÇC$I’ê,“ùûæ ›¶ýÝPRp‘§Çi±*Ã@t‡$I’:/Á8ð>où—º�G´#߉�$IRˆ-EâO\üKÝÉ€vÄ€$IRýl&ñº�e,‹‘4;hG.6DGH’$©3Œ§ÌýË8/ºEÒìq Çi¶Ù\Ý!I’¤Ž(ȼ­9H;:DÒìr KN%I’j!±¤9ÈÉÑ’fŸíÐxæÛÑ ’$Iš]Îh.ãï£;$u†íТ67ë¢;$I’4k®oÀ rtˆ¤Îp �ÊɧH’$u©¢€?k¶½øYªÚ¹ì=’$I])ó¥…m¾�!©³h§6�r0Ý!I’¤™“àÞ”ø›èI�ç@;uÐÙ<�à’èI’$Íœœù×f›»¢;$užíZæìèI’$͘ûRâߣ#$Åp ]Úš8 o†•$Iê§zñŸT_´KÏmss†FwH’$iú 8!ºARÚ­œÝ I’¤éI°~ ÍÑ’â8ÐngD7H’$izrbyòh§Tk´[C¬Jpct‡$I’¦.Ã¥Ñ ’b9Єäì.I’¤*ë�ë£$År  )’÷H’$UYçgÑ ’b9Є\»mËØÑ’$Išš¹ó¹)ºAR,š�ÅmÆ�s¢;$I’4%÷ì2÷EGHŠå@Öð€$IR%%|÷_’M›ø.ð@t‡$I’&'ÃuÑ ’â9Єt6çGwH’$i’2WE'HŠç@“’ðq€’$IUS$®Œn�Ï€&eËVÎ6GwH’$iÂîé�Ñ’â9Фx÷߉î�$IÒÄ$8»ÙfKt‡¤x4iN‰n�$IÒeÿî&iš´Í£œ lˆî�$IÒn­ëëç¼èIåà@“öàÓΊî�$IÒ®åÄ¿¥%Ñ’ÊÁ€¦$»•L’$©ì®Ýü$¾!©<hJ‰ïwEwH’$iÇüÅADZ5ºCRy8Д4Ûl!3Ý!I’¤Çˉ¯4Û>¹IÒ£9Д¥Ä©Ñ ’$Iz´ ÃÅ<>Ý!©|hÊúš,Opst‡$I’vKÏ8¯Yt"£C$•�MÙƒ7ÊFwH’$ Üœ þ¨ï4~Ý"©œhz Ÿ I’TkÒ8/i1"©¼hZšC\ \Ý!I’TW NLðBßù—´;4}^(I’áÊFâeÍ6G6ÛlˆŽ‘T~):@Õ7ÒâٮÞ$I’¦êlûûÔï½»xÝç¤Ìñ}m.J�;“'©¸`ÓŒXÝâ{iÛ¿°$I’4IŽhókßÈS‹^ž_$úSÁ¯�˜“`™› X}Ç\uðrÆ¢{%U“͈‘šáëÑ’$Itÿ¼¹üÚþ's_tˆ¤îæšcói÷GwH’$UÐñ.þ%u‚͈E'²1'£;$I’*¦( ¾!©hÆ4 �H’$MF‚3qCt‡¤zp Ó7È¥ÀµÑ’$IU1ÿÝ ©>hÆ$È$Ž‹î�$Iªˆ.ló½èIõá@3jë¾<Ý!I’Tv þ1ºAR½8ÐŒ:ð îI0Ý!I’Tr#}MN‹Ž�T/4ãrâ+Ñ ’$Ie–`IZBÝ!©^hÆõ/ã2àGÑ’$I%å»ÿ’B8ÐìHüwt‚$IR)%þÖMu³6IDATwÿ%Ep YÑ;Ê7ÜÝ!I’T2#Í>N�Ž�TO4+œÅýÀ Ñ’$I¥â»ÿ’9Ðlú/ð_p’$I V4—yö_Rš5Í6×'87ºC’$©2pLÚöŸ’€fUNügtƒ$IR¸Ì7šm®ŠÎ�To4«šËø.°6ºC’$)ІÞ^>!I4«䌻$IR}åÄ?-8•[¢;$É€fÝÞ{ñuà¶èI’¤NKpãÞ{òoÑ’Ôû�Àh‚/FwH’$uZN|t¿�î�$p Îù/`Ct„$IR�Ó¿Œ¡èIzˆuD³Í]À Ñ’$I��àÞ1xot‡$mÏ€:fþ ‹î�$Išu™¿xn››£3$i{Ô1‹Úܘpœ$Iêzçõ ºóQRù8PgüPDgH’$Í’�EÁûäèIz,ê¨æ#ÀÙÑ’$I³!ÃGqCt‡$íˆu\‡SqI’ÔeœßßæËÑ’´3Ôq Ûü ùÑ’$I3è¶4ÎÛÜú/©Ì(D£à3Ñ ’$I3¤H™·ô�Æ/£C$iW(Dsˆ+œÝ!I’4m™¿mrAt†$펅ɉOG7H’$MÓw›ýüct„$MDŠP½�´8'ÃGwH’$MÁm žÛlsktˆ$M„;*7øPDwH’$MÒÖFb±‹IUâ@¡ú—rM‚vt‡$IÒdäÄû–qqt‡$M†…K ŒEwH’$MDÊüãÀ2¾Ý!I“å@áú†XGâ„èI’¤ ìë篣#$i*¨rƒÏ£Ñ’$I;“`Å}pdZâýE’ªÉ€JaàTnʉÿÝ!I’´# n,zyã‹ÛlŠn‘¤©r Ò˜?‡Ï·GwH’$=Æ�Eƒ?8…Û¢C$i:¨4ö?™ûRæo¢;$I’¶sO.xåÀRÖF‡HÒt9P©Œ$þ;ÁêèI’¤÷Òà�†XÝ"I3!EH�5Òâ•Î�î�$Iµv_*ø£æWF‡HÒLq€J§Ùæ;ÀyÑ’$©¶î/à•.þ%u*¥ž6GwH’¤Úy g^·°ÍŠèIšiTJ,å:ÿÝ!I’jåî¯dytˆ$Í*­ÑM|øYt‡$Iª…[hpp³Í¥Ñ!’4[¨´:›Ræ£Ñ’$©»eÎ=üNÿR®‰n‘¤Ùä@¥Ö¤�O�$I³ç¢ÍðÒ�S¹):D’f›•^£àh¼P’$ͼÁ½öâÕµ¹7:D’:Á€J¯oˆuÀ±Ñ’$©‹$þ¹Ùfñ~'0�"I�â@•�à€‘èI’Tm ÆI¼¿K�£{$©“Rt€4Q#‡ðB\–¡'ºE’$UÒFoî_ÆÙÑ!’ÁªŒæWføRt‡$Iª¤[sÁË]üKª3ª”ñù|<Á�Ñ’$©R®oüÞÀ+£C$)’UÊ¢ÙHæ¨èI’T VÌéåE^*,Iµæ@•Óä\à„èI’Tr™SænààçœÂÑ)’T½ÑÒTÌ›ËÑ›·ðrà7‚S$IRùd2ÿÜäÞô/I�ð)ª¬‘/–ûTI’´� Žhsztˆ$•�GTYÍ6—‰�î�$I¥ñ“Fƒ¹ø—¤s J›?ŸVEwH’¤p—l�õ-e8:D’ÊÊ€*mÿsÙLƒ#�-Ñ-’$)Ìq£Oæ÷Ÿ×f}tˆ$•™w¨+Œ´øP†ÏEwH’¤ŽÍðþ�6_�‘¤*p ®�!�´8x}t‹$Iꈟ§‚ÅÍ!®Œ‘¤ªð€ºB‚\ÌáíÀÏ¢[$IÒ¬;»˜Ãs]üKÒä8P×XøMîN‡[£[$IÒ¬ˉO7›¼aá7¹;:F’ªÆ#ê:#‡ò‰œøÇèI’4£nO™?irAtˆ$U•;Ôuúú98/ºC’$͘ ÆÇpñ/IÓã@]'-¡èã-ÀO£[$IÒ´Œ‘ùë5ðŠE§s{tŒ$U�GÔµVÂÂÔàr`¯èI’4i?KðÖf›K£C$©[¸@]k`ˆU9s$�£[$IÒ$$Ú[·ò\ÿ’4³Ü ®·z1ÿš2Žî�$I»u_N5°Œ“¢C$©¹@]omæcÀw¢;$IÒ.]™<ÏÅ¿$Íw¨Ö¾‘§Ž÷r%ð›Ñ-’$éQ¶�øûõ·óO/g,:F’º™ÕƪCXÐhpð”èI’ÀªFƒ·÷-åêèIª�¨6qmʼØÝ"IRÍm%sl‚ç»ø—¤Îq€jgøP'ñMüç_’¤¾ë/IAÜ ÚéäTàï¢;$Iªßõ—¤`¾ªZÊ�ÖÊ7râ­Ñ-’$ÕÀU¹à]C¬Š‘¤:s€j)AÞôÞœÝ"IR·Jpo†cÖÀ‹]üKRÕdi‚#Iš:ÒndH#‹ùK2ÿ„f$Iõq'‰™w?ÿ¾ÿ¹lŽŽ‘$MŸ‹i‚V/fqÊ|Ø3ºE’¤Yô™ÿMüÓAmî�Ž‘$ÍÒ$Œ sƒ3ð1�’¤îS�jŒñQ/ø“¤îä@š¤kÞįõö°,Ë¢[$IšA§õ·9$:B’4{ÑRÕ,:�_lz2/#óùÿÛ»÷X½ëÂŽãŸïsz¡–¢à :d ×rN[."C‰†aSÂ,êŒ&:§É6·y‹Q«Îý¡›f›Ó‰‰s íHpÂŒ¨M@· ØžÒQ�;m¤\ÄÞÎóÝíÔMz9çû<ç¼^ÿ´M›æ}þü}òý}.C`Ѝ%_iÝÀÄröÀØhήɗ’ìߺöÀ�6dþâUÙÞ:€‰ãì�‘n®.ÉÉInnÝ»«Ô|ÜÃ?ÀÔg€=4ÒÍ6ääZò‘’Œ·î€]tOJ.iÀÄó ìEkÏÏ)½^.NrdëxVJÞ¶`e¾Ð:€‰çìEÃ+òŸ3¶äÄ$µn€gá¾Ró¯­#˜NÀY}nN+%ŸOrtëxZ5ï\py>Û:€ÉáL�…—gÕø>9±–ü]âb%úνs÷Í[G0yœ€I°æü—^.ÊŽ/@{5¯[py–·Î`ò`’Ôe錭ÉSòÉ$µî`ú*É wsjIjë&�&ÙÍçäy3gä})yW’Y­{˜vjINéæ{­C˜\hdõù9¶ôòé$g¶n`ú(ÉÅ#Ý\غ€Ég€ÆÖœ—SkÍߔ䕭[˜òžªC™¿pyîkÀä3@ŸX{^ÎèÕ|2Éñ­[˜¢J>´`e>Ö:€6|úÄðÊ\72’—ÔäÂ$ëZ÷0åÜóxÍß¶Ž ' Õe錭ͦæƒI^Úº€ÁW’³Fºù÷Ö´c€>V“rÛy9«Wó�$/oÝÀ`rñ‰W ¯•¤¯Ì5kw\øÍÖ= ¤�[’¿l@{NÀ€X·4¿½}<7'9¨u ƒ£Ö,Yxy®lÝ@{NÀ€˜¿<¦diIÆ[·0jÉ¿xøà`€,X™o÷Jþºuý¯$wí33Þº€þa€³`8­Éu­;èkÛ“\xÔ%y¼uýæ,K¯·=oHrëúSI>8ÒÍ ­;è/.„56šãkr}’}[·Ð?jrÍ‚n^]’Úº€þâ ¨‘nnIÉë] À/¹§“\àဧc€¶`e¾šä=­;h¯$?K'£#Ýü¤u ýÉn¤›O¥ä³­;hªÖš·,X‘[‡Ð¿ 0¬­ùÓ’|­uÍ|lÁåYÞ:€þæ@˜"Ö��yÛgçº$'·n`ò”dÅp7¯óÞ?ÏÄ ˜"æ_�'¶mË™InnÝÀ¤Y5ëɼÉÃ?φ¦�®Ê¦­É™InkÝÀÄ*ÉêmÛòGG]›-­[ ^€)hõÒRÆs}’C[·0!îìŒçÃWæ¡Ö! '` Z¸<÷ÕNNOò`ëöº{Ç“Wyø`W`ŠZ¸"wÖNÎ(É­[ØkîON;®›»Z‡0x¼SÜ­£yñPr]’Ã[·°ûJò@éeñðYߺ€ÁäLqÇusWIN-ÉêÖ-ì¶»“œæá€=a€i`¤›'9-É5N`×Ý6>žWŒtsG뛦‰‘n~²yÇ'ohÝÀ³vÓÌyåqWæþÖ! >L#'uóØÜ¹yUj.kÝÀ3úúìY9ý˜Ë²±uSƒKaªIYs^>\j>ܺ€§Qó… óŽÅ«²½u S‡¦±±sóæZòÏIfµn IÒ+É»GºùTë¦LskÎË©¥æ+59°u À4÷x§äMÃ+sUë¦&�ÛÎÏѽ^®®É1­[¦©ÖN^»pEîlÀÔå@ Ç®ÈíC[òÒ”t[·LC—nÞœ—yø`¢9üc£y[Mþ1î˜h›kò¾…Ýü}ë¦ð+V/ÉI¥“n’ÃZ·LQk{ÉÒEݬnÀôáàW,¼"7Íœ‘—&ùzë€)¦¦æŸæÎÍK<ü0Ùœ~­š”µ£ykM>�ä9­{܃)yû‚•ùjë¦'ðŒÆF3ÜK..ɉ­[RI·7#¼èÒ<Ú:€éË<+c£™•š§ä½5jÝ0 î®ÉÛvó�Ö!`vÉØ’üníäËIŽnÝÐǶ•šÏ¥ä#Ý<Ù:°n½ s;[ò‘RógIf´îè3«j'ïX¸"·µ€_fvÛØhޝÉ瓜ܺ Ü_KÞ¿`e.)ImÿŸØ#5)cçæ‚”|:É­{øij>3ck>>ÿê<Ñ:~°WŒ�æI>Q“ Z·L’^I.Iòž‘n~Ü:ž‰Ø«ÆFó{5ù»$Ç·n˜ ½Zsy†²Ì{þ °×Õeé¬]“%µä“Imݰ·Ô亡NÞ;¼"?hÝ»ÊL˜�_ xw©ù«$s[÷즚äk½dÙ¢n¾ß:v—˜p·Œæà™5­%Æg�Á±½Ô,/ùÄ¢nV·Ž€=e&ÍmKsØøxÞŸä-1ýkKIV–^>6|EÖ·Ž€½ÅLº[GsÌPò¡$K“tZ÷ìt-ùܬ¡\tÌeÙØ:ö6ÐÌêósl§—÷'y}M†Z÷ÓSI¾_kþaó¹ì¤‹²­uLÐÜΑäÂ$û´î¦…'S²<%ŸY°"·¶Ž€É`úÆFsàÌ’w–šw&ù­Ö=ÀÔS’ï'¹hhK.›užhÝ“Éô�›^“çì3'oNÍ»’Ѻx—äËã½|qÑY×:Z1}kåh†Fj^]KÞžä÷ãÂ@àÙ{¢$W%¹ô‘ ¹nñªlo­€�pËh*yk©ù“$µîúOIÆ{É·KÍÅ¥äÊ‘nžlÝýÄ ”õgeöÖ}³¤&oOòŠÖ=@[%ùYM¾‘’+{3rõ¢Kóhë&èW``ýpIæ—¡,-5oŒ»`:y*É·RÓ�=;WuIoƒÀ ¼º,�5kòÊNrA-97É~­›€½îG©¹¶”üÛ¬'óí£®Í–ÖA0h À”òÝÑÌÙ¯äœR󆚼*ɬÖMÀnÙšäú’\3ÞË5nï€=g¦¬›ÏÉófÌÊ«S³¤“œY“9­›€ßhm-ùæPò­Îæ|sþÕy¢uL%`ZØy2àŒR3Z“s’ÌkÝä¡”ÜPj®ë åÚ…Ës_ë ˜Ê À´óÝÑÌ™Wsz§äjrV’ÃZ7Á4±®$ß©%×÷Æsý¢+ò£ÖA0�€iï‡KrøP'gÔä5ÙqoÀìÖM0l/É­©ùN’¶”¬:±› ­£`:3ü’uggÞ¶Ù9£Ôœž’ÅI†[7Á¨%¹=57ö:¹±ÖÜ8onn~ñ—²¹uð €ß`l4/¨%‹ÓËâ�ƒÀ‘­› µ’Ü•ä–^ÉMCÉ÷¶lÍM'\•M­»€ß̰ V/Í!e{×N^^j^V’‘š µî‚ ²¥&c�š[z�Ü:”ÜòTÍ­'uóXë0`×öÀº³3o|VN®ÉËJÉ)59%Éó[wÁ.Ú^’;k²¦$ëjÍš^ÉØÖý³î¤‹²­u°wö²ÛÎÏѽ^NJÍ µä„$'$9 u$ùi’;Rr{MÖ&Y;T²¶örûH7[[ÇË0 Ö¾6‡fFNèÕŸ‹’Ú8‹©isMîè$ë“ÜQKÖ×^Ö�—¬?¾›ZÇí;µ“cS3œdøç¿&‡%é4�£ŸmMr_’»Krw­¹»vrw§æîmÉ]Ç�䡲,½Ö‘@ÿ1ô™ïŽfÎs{™Ÿ’#SrD-9"5G&9"ÉÁ1LY%¯ÉÃ5¹·$–šû{�Üשy0ɽ½¡Ü³`~ð€ìÀYVfoÞ/‡gaËšzTXtRaw profile type exifxÚ­šivÜ6”…ÿc½ÌÃr0žÓ;èå÷wÁ’d+’c'q%ª‹�7Ü”Ùÿ÷¿ÇüÿRlÑÄTjn9[þñ{ó�Õ>ÿžwgãýùáûûOÇÍûžC�÷ðüš÷ëüÎñôqA‰¯ããçã¦Ì×8õ5Ðë‹·ƒîìù°^3z üsܽ~7Í?zþa9¯ÿûqýuÑsΧßc!+1^ðÆïà‚½?ýsR`¡…ÎOÇOœ×‘t�~Æ{æ§ø™÷Ð}À÷OŸâgçëxøÇ3ÐÛ²ò§8½Ž»ôuün”~œ‘óïwö?Î(­·Pþ5~ç¬zÎ~V×c6„+¿õ¶”û‰á ÷²Ì«ðâs¹¯Æ«Ún'·Z,u;ø¥9OÄ�‹n¹îŽÛ÷}ºÉ£ß¾ðîý$î:VCñÍÏ›”¨—;¾ò³B%7“Ìeä}.îÞ·é~ܬrçå8Ó;#Ç?¿ÌçÿôõÓ@ç¨Ì�³õ=VÌË«j˜†2§ŸœE ÜyÅ4Ýø:ó¼ÙÏÿ”Ø@Ó se�ÝŽgˆ‘ÜGm…›ç`“áÔhŸ$»²^"î�˜ Õ�ͶËÎï‹sı’ŸÎÌ}ˆ~�—Lò‹YúB&9ÕëÞ\SÜ=×'ÿ^HD 9RC‘¬SÌô[¥„ºI!Å”RN%ÕÔRÏ!ÇœrÎ% §z %–Tr)¥–Vz 5ÖTs-µÖV{ó-cÉ´ÜJ«­µÞ¹i��±:çw ?ˆ#�<ʨ£�>)Ÿgšy–Yg›}ù`V^eÕÕVßnSJ;î´ó.»î¶Á�ãO8ñ¤“O9õ´Óß³öÊêÏYûœ¹_gͽ²æo¢t^ùȇKy N’rFÆ|td¼(´WÎlu1zeN9³ÍBòÌ2)9Ë)cd0nçÓqï¹ûÈÜ·y3D÷Oóæ¿ÊœQêþ‹Ì¥î‡Ìý5o_dmõ ·á&H]HLAÈ@ûq®Ý×.^úGïæŸ^ø{õ“YgXg÷µ3ËÎö„2ûÌç´•Šks‡ÞÜ®¹çäët dl«A5ö|Zö}OÚ3£ÞO-g“çC¶ïUï5†“¶>µÀOCØySöOêàõÜcŸ˜×IqÆr|8£µZÐ%è2Û¹zwO™7��#«%_Ž™~ ýJ²9!ÞÏ'§Ä$("WNÇjBÇ7·p\:,Ù�d2º¹Ó½7`’þÃ-ž/¿ûnuJ—‘Îä¼fï ÌwwøÓ˜ïîðׄ_Ü ³»Û6/»–MÍ�‘=ÉÝáøYnm÷3{ï¡Õìz$«®Ÿ9kÛiUzoŒÊ@7©>Ð�~p×í¸Eg—žoHÉ‘�ˆßÄHÇt„W¥S�B§÷L™ךsU¨FoóïËÛ¼h{òb.£GZ=W&ßÚ #Áüĉ™µ0 ·ê®è�tN Åõvãeʘs¤YV´ªþ±™tì»ôS{OÑê“P–4ín3€y„Ñ-0f…0ìq\¨f7¡qòn~uìŸËÜ+MÐqÚÙˆ¢#èÅ´¹Ì©e®£€"‘òLwxÃñÑv]3‡ªþ`iŒ\Ñ0 îÌ+“¾íâ¹á˜FRÛ/ÆÜ@á6àäðg’‰qBX³»ŠWÒ܇FmEX‹Ï|¸Áx›Ï%k>Ëíi^óÙ…ù�Æ<Ï …aÚ)p"êT‹Â¾„($@·ÇF`Ä‘q²Z=©Þf,5ÄEÈJYk€1)L‚ÒKÞ©‚Æ3ßÙ§¨Õö|”ÿáÝ|>ðÓ{õÔ÷†©ð¿«$h&Ç‚}dM…Bvó$—W—m=yšf¸ªR®€?¥M¶oW¹v±‡’PÁWZïèz›„ñlC`†æ ¨Õ=cdÐq m�ú‚(à¶}¨>yÐBĽ´í&%ïD!(>y£ÔfrÞ/Áq,D™hÜ4G©ýêTf[ƾ޽hqôå{W§²p²YgeB>CÎkR4©Cy…9ñ#—æ*¥³¦ç‹˜Ö^+t-ˆ)JèöÚ¡ŸŒýKCž”ŽU¯]<DŽÞ±¥¥Ò<5 NQÅÎ5Jwž* Vܯ�9ÀŸòì“‚JCî z](‡KŽŽ³¢ä@}>ɦ}z7ß}ñ›ïšh%‘ÛÄÜÇ"¡bŠŠˆª…ynüÄMzŒm{K [zÏ¡.¹ ‘`A­�Àý¥¢ ¥öIíòV’ô^~ÂY3^�oæßñ~¯{8Q/6k·ɳVŠ©¥Qv¤ã€×lCßöИh Æ­ XÑ)ù9¨�qr¤ÅÉ¡Mc[ÅÒ‚‚  ]¨#`•]$ÜVSÉDñÁ)p5†óC)�ñk2TWÊÇSŒc§D[B@x’ñ9{ïè6®eÀLPk-–Öóžh. w�†úÇ‹”L&wsÜ íË´öò‘¢ç#^/Œ. àâꦣÄ(sÈ·Â/CÙ›µ7’RCQ¶Yâ}«K2hŽäÜ>ª{ŽEÛ·D?õ«¥ˆw1ð³P5$æ:Þâ]ÖP¼“-$Þ®#u‚OÚ¢ÔiAp} Šg¦Â*Á¬ðÄÝm aÛV6ˆTÀ5Ïí*ý 2t+d=—~�Ä…IPì# Èà fwòÛm#âvç‰nÚŸ� Œ¨’ÁüUªB¦:,X·…ƒGF)3gªÝˆ=æ$Û=¾GÞ_½›ïO@NöP–2ž5¥M73€…añ’ ^i”(rf ±N£å�ù` Åíó8j‰¬DxÔÕ | Û<€SÅuˆtâFhf«8Å]nþëà´4Óv\B⨣¼ÂD�ROxRCØæÐÐ!×)¹…W1uØå �= rnj ®2'Ë}±NkEYz œ2¨J��ÙåN1³P Ý‚P[ä“ÈL'£¥5;v�fm…×è] ÁǬçê4.þÇ S7á«ö8¸вP³—íD yW&Í„}¥ZßÀäP”³QeÜÇ”È8 õ^y_ùÂE^žÐßt“F*¨ VÇÌè|·üHhY©�‰9b•Râ\¬ö‚ºÈŠË� QESËðäl–�Y<–HÒJïRa-ø@¢òÜhX :ÔFûH�#šÔ†›æš¶ìHב‰ …z˜Bõ°ôQÙ'g¦ä&Î%@—Ð.0’�b‹&¿�€Á5Ü/mé1:íd+ïýÁoúIòßû5|<Ðk‘#"à‘‡˜îŠ<ò®ðƒI,Ïâ`®P‰<[€—óOkЯÈxOÛ”D%›ðp¬ãžKsu?+¢R ™‘ýÕE­ï5]jqÔxñ¾› »õE»Lô`xpE¦°®¬t A0…ÑðÈ5ÝO©æ¬ð¾,ÕÐ=`é‹PÍ1#ý¨a¹oÒ”úªUÙ>ôc)ä?‘Š ¤:Ô‰Š@¸ÍÊøîW#JOƒhð1`‰¦Gˆú –¢‰(i0�|A7k4‘΂‰ #¦é—q11*p>ZYÝ(Jœ�¦P(µ½)·ä I§ `9×CÊàII`/®*él r >m-{"=]áŽ!·–ÃR`;É Ãd ÿÙnaW�¿»ýê‘øØNÊší€ é³ ìËP÷�šR»¨#º©ŽU¸ÚàëÏ”»žŸÐÒXÌ‘S?3Šñr9PUÝR¡a�„ÒB•@¥ ¯DÌìrèµ+ä#¤¦ÜEðv´«_Õ‚¤9iþg{l è‘'œ{¶™ž,+‹4 }Ú"Ý�ì´T ì=bÅ3p�_è¥ßÙ¤0Ï.EW,?àñ;jy+Œ¨ýmzeã G”!ž�:ÈÐé�+d¯žŸØ•@ƒTƒ|þ g½HnO)©Dyaè–æTío ©mLî…ÉPw"”¬§ôú^Í_¯?–]ZJr} ÙHþøž rIýz+MÓSã×?’ç?È?>›z§…ë Y58 ‰Ñæ7¸ï'êƒA 206o/ŸÞ¨&‡;ràG[¹Àäq¼Bq °$ر±ðºÊ@ò—èø9‡”wŠ4nÚ>1‡�ü¯¾@7h?]&n teiµADÂÍyÙ0ºËñ÷š¯r¾¥!q3ò2�^{£˜(Aè{ PÚðþò½.UvY%bN�KÚ>®çnëO=ˆôë­wc|>D8ìëwg"$1€†¼ÛAš·ÏwóÀ½öå¥!3m¿e´ï6ù›ÑZ_£M¾uü«·×^µ~…ë"稉�eïY}xõ¸KŒ u9[ …¬¡*°Û²5L† .°RȰ\Ln\ÁcÑÓ”¥GؽöŸ›�'>ûG&ûc¦—:%¿õÑ’9 O¿ÁiRÿm\díÀx.ƒö™,U&ëÔï¼JŬMG¼Œv jBÐĸåbhy[1ÙrÕäÔÈK™ ªvEÍWñI mƒ‚ž•uŠ€PBÕúBŽñŽœa@êK-Ûpˆ˜ÅWÉwªá�÷Ø0í×o,�$,Ü "Ý=»¤G4}pa`H$ïè�†ñ>=ÓŸB¢7´ •~± ÅØ é#ض™ÒuÚNÓŸ&`E+j/yvÒ±(§9dj—ë]yúÐs!A°�vâ%„Ôu—ÒÚHCs·bvƒŸ�tõÚPH9ËíLð~HôFò˜Ë5¥‹�©êækìT+Ò¯Ï ‡Wébª+倱Åßã’G|šïÕ'b ¬ÆdèQ ih’úáè`-”øB^#kõ`̃/êÃâ{«¾ ãÁm°‹ ³,lQ['aä¥áªÎKQ‘Š1åwÄÂữXxi÷Y+˜±ðkÀù»wãn%QûëYá÷ˆDÍák ˆ 5#TìÖ8 ÔàËq¨§„%šÚ6Å”M¡èBaÔ%FÓ¦Cé¡6yúS‹Ïò€XŒŽè0“E³Ê¬‚<9Ô€¶ÛáDÆöNÈS:¸µí3pt² Öß§€æm™A µB.ƒÉ�vñ>éÄG'[´¿û<9XÚ—ß@"höÅû4h挤è9eΠͮB”ÍÔ_ Þg°¸ð³Ãów Ú„J·Ëlî6³þ² Æ©?mCÎ]»J½Ôç�'f\Ð6©…˜}!áhO±µ{c‡>¦ZÀF?yüÅâólñÿåm‹/ç}·ø.NÜ->T½\Ö3'=ͦh‹ÉXIX¢e&ÌËž…òCu@ÕZ�šo cfð»v% ªp®ºJw9ɨŽž¼@»�çAûk§ô>hÏ/_eïN©´ßnF^€ngªÍ ¼^n ¸SÝæß´ÅD.‰¢µæÿP�”RC^º}…iCCPICC profile(‘}‘=HÃ@†ß¦Š¢+ˆ8d¨�,ˆŠ8jŠP!Ô ­:˜\úM’GÁµààÏbÕÁÅYWWAüqquRt‘¿K -b<¸»‡÷¾÷åî;@¨—™fuŒšn›©D\ÌdWÅ®WôbaZ£2³Œ9IJÂw|Ý#À÷»Ïò¯ûsô©9‹‘x–¦M¼A<½iœ÷‰Ã¬(«ÄçÄc&]�ø‘ëŠÇoœ . <3l¦SóÄab±ÐÆJ³¢©OGTM§|!ã±Êy‹³V®²æ=ù C9}e™ë4G�À"– A„‚*J(ÃFŒv� ):�ûø‡]¿D.…\%0r,  ²ëÿƒß½µò“^R(t¾8ÎÇ(е 4jŽó}ì8� ø \é-¥Ì|’^ki‘# ¸¸niÊp¹ =²)»R�¦�ÏïgôMY`àèYóúÖ<Çé�¦^%o€ƒC Z ìuŸww·÷íßšfÿ~d%r¡™ÉèjbKGDùC» pHYs5^5^ "¢wtIMEã  7Z/…Þ •IDATxÚí�{p凟݄ �RËÅ,5² Ei)mÕuŠK U©V …Z 8Îtz™G ­Ö)J+PiËmÔ ´°€Z¥€ä„‹á*"áZ!�äœíߢsn{9gOò>3gHrv¿=ìïw¾Û~ïû� ‚ ‚ ‚ ‚ ‚ ‚ Í-ìpû=`竟m�BlÚŸZ­€| Àù½Àù[Þ%EØ@ƒó:Ô9?×;¯óÀ)ÍæQ[ƒ�GáÁ5b€@‰ M£­ _:E@gççŽ@à2 �óïe�„ ‚óÀqà4p8 ¼œN5À^ç˜@­a‰âReB Úhp-P|è\ tuÏeއ€j`·cŽJ Ú†S%V 2@UØ1>\|èôvÄn‰"À�7�M@MØj רY J𛀛�Wi.ô+²Èn`-°X•—DZž rÈUCÁÖé 78�0!}bÀ` 0¿¢5?-©"&m�aÀÀ�ò-÷�(° ø5°6“Í„–DøÀ€qN/\žÕÀÄIÛWgË“B` ð#gÌ-d–óÀ8ÃbNF °Õ„|è̺‹YņK2b€È0@ã»Às¨Ù4!ûì³5ŠJw`Ë@cð‚ˆ*®Ò¢Á޲t€VQ¿’û:΢Ó3P¼U†̸h!T´CÍ·g€¼€kä^·LtÔlž^m€ÏÊ=5›ƒ6À?凖S1Ø´dŠ7¼,.µˆm€�rŸCÉÀ/3Ñ ,’{JÆû2a€¨Üëб¢•Æ35 Ü/÷;TÔ¯Y”™‹åoË=³£ ìqsâŽû ZGêþ‘Ô”ZÉ °IîyhˆOö^šÚÁ•CAÓé‚Z¢wg´Žþ\²~C‡¹ÛÊ]º;‘þ-÷=²é7µKÚ—'€ÇD‡¬q*”ìq`PèsùwËã5³Eƒ¬2\úhâ¼1i·¨2ÁVqk7Š¡ç ê1ñ.Ôòñ«�/§`œ™­óp�õMÀiî…ÌÌA iEÅβᵋúK;ŒÐø¡ÓŒÇë3Ä€ë ‹­ñ Pète�@¸XG€±6,O'ÙDÄä!`f‚C¾fXllÜ ¼ÈóN»"dy^èmXé‰PœÇï€ÿ$8dòŠ&FØ6»�Wåþg•À­†Å7'k*éÄ“ ùæçMº4i€Õ6Ì ²Æ<[ã^Ãâ‚—Bb*ß@¼ià<`T¼&b,Cå¸2ËZ Æ”,ò¾J«T�V%8dXÄŒcc11©2Î!`D±Ço~#Å\\ž(p*©¢<[€[ ‹ŸË=–äýoÄ5@ùFN‹D›ŒðŠa±+€r“…–÷Žk€?¼ ÀoE›Œ�÷ƒ K’÷‹†„k6o k3�ÿ s¿£ªø$G]™ÐÅjHø¼è8ý.0ršÎ$°×:•¤ QÉ“…`°ÁÝ2ð$LL¡P—Ô†ÅY`žè»c*}½ß~5Ë7)…C÷¥šæEÑ)0ö”úŸt&*½LÒ¾Gj¨46c³ø’èå;ß R©þ&w¦xÊë)Õ†š xI´ „j? U h žHñ”ãhlH'3Ø<$’8ÞòZÀÏûÀ”^Üáh”ê®*sŒEDS6Àé.%ñÃÁeÀËÉ›¿Cº3XJüU@�9‡“2e|ý@=§üãjÿ!WlõLæ£6ÌJ•iŸ;¤›†ô¯¨!‹lá{Ýî 1ù6³3ÍS7jðÔÅ_ÒÊ:½�“$4Ò¦*í†AĤ°Õ…ø³ø’Åi`ŽŠ[_.ºùÆþ4¿õ…¶Æ4`=j¶t8ŒZfö±@7UùÅ­Mš€Tª4ASÙÜf ¶ÝK—]À†õÉë¹I½‚Ï^%s0«?DLŠ4X ¬t)þà+M‰ïÊN§E†ƒþp,Auß±_WžvC\”}x@o`¨añ~¼ƒÜöæÿ<(úy#?Ê;M �Ú«i©Íç7Å`´a%_ØëÖD>ϼ×�§pv††ŠP‘Ácp—ÔÏãvÏ•, –’ Ý\%O£:js‰!ô  WĤ¬ú·ãmÓŽµÀXÃJofÑõ�“¿·‰Ž®©CÍÞyÝ©å 05Ïæ…žé‡ôy™ÑÛ ð„I ^&4Û§ƒ÷­ ³ÆAànàn/â{­6¡Ö³É>™£xø™³TÏ3®kÞÇÇÕ,BRV½£ LñK|OpbÖ¥žw3ÚÀ@ÃbGªy„3Ñ€JB0N4 „ZTŒÿS†EmPñj€×E§@¨5¬OÎú�§1¨c/ð®èåÕÀ@[ÃÌ„øž P¼�mçü <­A‰a±º$ƒ1Ù~ì,O½·õƒÛÙL)¶8Ÿé‹û±¶OJyc´a±,[÷^ØÔoŠŽ®Økg9 ‡g8'ÿ"Zº¢²ÄÊîÐ}*g¡h銬Ýê>²xCôL‰]Î8¸Í^ÛÿáPÞ¯‚RÈO+(†KÃòat�4É$’ŒÍZ~¸úK¾ Øâà÷¢q\bÀ÷Šç‡+·îsyåðÑÎWÂǘv¾•JÑÞl àd·þ�hý *t›Ÿ\ÂLKzNGÅ¡  ‘¿Ý«"µeÚ9oÃâ *¨A€r[g¤áoèÐ× ±˜Û‚…¯îGcrÉÂpï¾؂ΈI[ÔŠ!£…‰FVú±ÿͧPMÁ9à.ZΖ´€_}sEü@ à˜àp Í÷‘e@ÉÞÓüØÈÂ3ýP6�šƒN¨ )5Cñgc‚±87?|Æ‚:¶ÞùùŒq†‰—7ñÏ= +w‡½�êq:‡Ó�‡›�&å¹üÐ3}A§s8•ò$—Y®;És™¬ÅõELÚnÈÁû¶¸Í13R¸« Î:�Â\Ë>ºÔÄϪÔ]¨g$ðÄOdŽ÷5Ô32ÈP­Ó4Ñ$tFM¤Ü´ Ñ=:ŽzÂùl¢l[b¨,-Æ•À÷#tÊÒG©u:©^εÉ�œ5@£!¸5�<¸6ÀËý؆J·X·r'µ�TÒìɉì•ÃA³é€Ú²¦p•cˆŽ@çßx%œsªñSÎëêùÄ!`;°7ÏfÏ Z$9�Þeå¸âs*O�®‘oÛ´±!�˜fsAϧ6Úšýሂ ‚ ‚ ‚ ‚ ‚ -Šÿœ¾®µ)a‘IEND®B`‚jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/jollygood256.png000066400000000000000000000154171523150047600240650ustar00rootroot00000000000000‰PNG  IHDR\r¨fsBIT|dˆ pHYs W WgëtEXtSoftwarewww.inkscape.org›î<tEXtAuthorrdevries"W,ö3tEXtDescriptionsilhouette of an English porcelain cup.¢úÏd!tEXtCreation Time2013-08-10T13:05:14Z†ÐÈUtEXtSourcehttps://openclipart.org/detail/181742/english-porcelain-cup-by-rdevries-181742åÃÕÀXtEXtCopyrightCC0 Public Domain Dedication http://creativecommons.org/publicdomain/zero/1.0/Æã½ù@IDATxœíÝyœ\e�ïñÏïô’ B@\T–Á tu'ˆ8( „‘ÍQP •àø‘¹rïŒ ˆ—Ñ™;‘eð^Çñwt†e�!/�t%ÁË""K: bCwu@H؃dë­ÎïþÑIIºS]ÛsNÕ÷ýz…P]užçÛ¼8¿zÎö< """""""""""""""""""""""""""""""""""""""""""""""""""""""""%±ÐêÕâ,ãöibïþƱwEÎ^14YÌD�h6gO Õ� æŒqgü¶Œ @ë°c�qæô:lÞöcçm‡€GÄضMLŸ›ÜXïNo“±cƒÇôbÞ¶V6ö Ò{tn»m¤®©ìƲs™õ³?ö‹`²Ãd`�˜9“Ü™„±·Ã$ƒIÀÞ ý=&lò²m2Øèðð&ð&¶åïþx�7š[x}]�WŽÍ½SŒ$ù¶ä³´FMòà@�x?Îû�mh'ßßa?Øî[YŠñ°ÆaMkÝYãk"g�k Κ‰X{È-ô†*u^–�ɾÖÌ¡Ä84Š9ãP‡ƒ�ý©óß?ÁxÉa•Á*œgÍXe«zcž=*Çk¡6ŠÔï>›¨«‹ƒ£&2ædp2±Ñf0Ø3t>)Éz`ðŸ8=]qݯ¿ÂÊ1:\=IUXz:ãÇŒáÈ>âÆQG az£è–›ÓíÝt¹Ó�ÉñBè`i•ذp:ÍûNæ(œc|h‡ÿHG84…Î&‰ó ðÎbƒ%0–êddqSg·§sLd|8ÞáãÀ¡sI* ¿3g‰Ã’æf=üÖ„•DÁ €ƒ-ŸÅ‡ ÎiæœÃH×½EJçÀ2s~ýË�_ÏÌ Ý+ÑèjZžü<“š[8Õà4†þì_ËþE¶x¸ßàÞh�û�¸“7B ¥ê`q–q�“€sq>‡¾å%A 1,4gNaó§ÍacèLµT•�ÏÒ;Ÿ5ã ŸqW�~D*Éà±qkìüpZާCç©…Š€|–� þ ï%½ø…ÁÕ™¿ ¦š*RºfpŠ_Ýòm¯ËtR7 °ˆ+ÚæòÛÐYª¡¬Ð=“Sq¾ÃÐ|‘ºdPpø—^ø›z{R²¤�Ïr¼ÃUÀôÊÆI´ ²™�‡R)£*ËÎâýMMü3ð¹*åIº^ƒY™w…R EŸMÔ“ç/®&V9“HÒõ9œÞ‘ã�ÐAʵÛ°ì,ÞßÜDç–[sEdÈË™LŽ7C)G4Ò›ù³9¦©‰Çµó‹ìdw¾:D¹†-ù|Ñ#ÿ¥vqDRÄ8't„rí²tÍä7æckœG$MZ8�æÐ!ʱSèÊ2ËœIУÂ" 5°ïä‘£“î]á»g1Íàæ.";3X1~Bº÷•máóYZ‰¹M¯%R‡Ž ø÷Ð9ÊñNõr.¦†‹"’>fœ:C9"šlÓ-ý—4DØÇS|¾,7†3�}gI#·¡Ç‡S)ˆ�¡ƒˆ¤Ô[¡”#r0ƒãCI©—C(GôÌxðÞÐADÒÈ`yè åˆuì/R†žÐÊbâÐ!DÒʌš3”#òñüx.t‘tç±Ð!ÊM›ÃF‡KCI¡G396„QŽhË?ND$uœÛCG(W´òÓŒþ,t‘”Y×käB‡(WsÿxÚö D$MÌùÞÑóÒ?ExDÄ´Ð!DÒÄ küü tŽJˆÜø@è")²q²‡ÜBoè •Ó:„HZ8\RO ‡F¦µüDŠõp{Ž›C‡¨¤(ŽX:„H ô{ÄEi~ôwW¢(fmè"Ig𽎹é~ðgW¢‚ñbè" ÷Fk+ÿ;tˆjˆÆ4“B$É̹vÊOx;tŽj0€î,k�ýgI¢µoÃ¡ÇæØ\ëŽN§yßÉŒ3ØÃaœ/Zxrêm•9w·uU“ß§U¢A‘zâðÃZíüËÎeBóf>G|ÊœO‡–í§u  ¯;ËícZ¹¤Ü‘I3€ÁWyƒÍ-Í\_Í>VœÁž…VÎpc½œêF«íþ:ÃàüÞ~ïÌrüÌ…RûoˆaIjç5©7~ò¡Ûy½âíÎ&êéæT7.(ÀgƕҎÁÇÚœ,pG©Yš¢¡Àè®@‘­b¯ìM?ù,ûÇÆù<_Á8*rSÁi”[296tgy8®ü<"uaeG®2³ýäÏæ˜Ø¸ÔáLó Éû•³ù;K;c*"sʹëÏguçùœÁeÇUñ»¬¦·‡‡ þ¶ü<"ég”²Ýêó»a#_Îç¹Ôàƒ•Îµ#£¼ù¶€±YÒ¿›K=!!R/ Vgæ�î¹eç2¡©� 7näT+Û.´–³ñ¶Õ�§ÜGŸÃƒåçI·Ø¸§ØÏ®8ƒ=óY®hîe5Î?RÛ�‡YÝ3K?t�ÞõÊÒ½Ö¹H%<°»Ï,=�ñù,W Žá9‡k&×"Û.´â,XqNi…ç]Àœ»¡ò×=ERÄ›†_ìcé…´ä³\8v,+®ö©a¶áì[(p[gvôs{¼«drôctV.—Hº8ä�¸“7vúùl¢ü ²c×Ñãp=5êïŽÃ ¸|´ÛE;ýĸ¥�DÒÈœ%Û¿v°ž™|>Ÿç)úr¬ú™ýR9\™?›cF³ÍN }.¿qè®\,‘ô0ã©­ÿÞ=“ãòY‰�;�LÀXÅjñˆÛV~‘‰Ån°ó0gNå2‰¤Gìtç³üqW–p~|"t¦Qú£¾>þ¡Øïò.¢çp@¡À ® C¥ñ<ü eÞa˜GÆô¶NÝ݇ý%»³Ü‡I«c6pä”ûèéCÍýáÎ�f*"3tn­›¡ó =C�bÆFÆa@›Ãg¡¼|¶8¼o—W�ô¡aG §Ó<5G×®ÞßåeÀ­29^p¸¯”ŽẼ#29þ¦R;?ÀÔù¬ºù|)Û;4EpÕpï�X¶4ð¯¥t,Ò VcœÖžãìLŽªÑÇ´«cø0XbgôÌ¢}Woì¶´g¸J;Ž©só -|¤½“û«ÝÑÔKܸ¦ÄÍ­à\±«7v[l6±ÃM%v,R�6|¹=G¶R tãõW¹J[ŸÐœY]³8tÇŸï¶4¸™¡YƒEÝ‹ùD&Wû‡æN\Ä ÎwKܼç’XTh[ÀZ£´9ÒDê…C·ÁÇ3óy2T†Þ}¸Ý`u)Ûšsîâì»§ü+ªàü°”NEêÄ/[ú8¶Z'úŠuô ¸s]‰›Ošh|~û]2óø%¼ó¨¤H£px¬¹�Ï~ëCg4r”8e¹;lÿºø`Î�JéT$Å~Ó§%eç82ÇK¿-e[ƒ?Y~ÎЪD0ʰ¹�9P»³ž"�­mnâóGçøCè ;rç§%n Æœ±íÅh¶<ún6i²i½sÆáw°&t�]1ã%o윺õ_GU�Q‘5 E’ËáÂŽù, �c8›{é6J[Ü`úêó %€i9žö¡YSDê“ñ:rÉé}7›¼ÄË�ÀøÍ›8J(M¦K‚R·þ9ÓÆe¡Ci§éË‹åÎ)0ÂŒ@#évînƒ€K ’4îœØ1�EäB')Ú†R7tø(”8˜™£àÆõ¥v.’D-ý<:Ã(•³0è‘VRˆ¸ FžpP$EÞNÒµþ"•>SLZ–倒 À´;yÕetIPê�±*t„Q:c÷YlL,»¬wnÞ*·‘�,NOX}>cþ´Üv"Wv86ÇfœŸ”ÛŽHHÖij¡3kÃ&þ سÜvbc°üC€&n¬H;"� ô§g`ÎEi'®PhŸË2àñJ´%ÀúiwòjèÅÈg9øp%Úrã�ÊŒL3Kj­ �Yšþ¾RíEΚŠ:˜¶;©D°”€6ãk@¦RíŒ+V296`Ì­T{"µ[ò Àòs8çÊ 69¸çò•;Ìu2PR(NvX8�æB�9Àlö™Cn¡·¢ “ãq`Y%Û©¶(á#€Éûr MàQ1Î@‰��Èø÷Š·)R=ƒÀ†1œ®™ÌĹ¼ÒíF?ƒ*€h�;J�«L$€U™ý¡CìJÏ,>9·Vá¦{£Þ¡iý*^Ú°x¸ÒíŠTI"‡ÿ]g35޹Ûy÷R^aütëÜ•?r[•Ú©,/mµÝjê:›©ñðÞj´oñ;÷ìT¥´¶²À`s5Ú©$KØ À§²tDR¥�èikgáÖU)S~ÂÛÀ]Õh[¤’bç™Ð¶z*KG<ä0¹Z}8\i³‰·¾®Ö!nzBP’¯µ% k§Dðh5w~ƒ®öÌ»§ñ«Zè�ÄÏ�׫վH¬ûÐíáÿížÉW̸˜TÅnøúößþPÅpô ¸1¿Zí‹T@ÐoÿÎ,MùüÎ @KU;3þ-“Ûùê\Õ @óÿªÙ¾H9,`øm–Émp¯=àSmÏÇÍ|sWoTµ´näaô„ $”:Ø5ƒécàIxg•Þ*êó˜Socݮެj˜r}ÀÕìC¤TÕ¶tfiêšÉìÈxИӿhÎE#­r\Õ`pwµû)ETÃ@ÏY”�‡Ìù;‡¦štê\Õ>oä‰zª^úà^= äÔà! Ëg¹0n¢Ëá„j÷·�ñ£öyüÏÝ}¬êਯ9üºÚýˆŒÒK™\é«ë£çl¦tgYäp=˜Å·hÆ�2m\\ÌGKZ|ÔŒ»pŽ­I_"E0ª7 øÒ iû—ºówV�‡y†çÛÑÉ5ÅnPõ€¹ÎH²¸ñ|5Úí™ÉIc×ñÎw«ò$ßðúÌùóŽ\ñ;?Ô¨drôÏÕ¢/‘"½PÉÆòYìÎ’‹�€Ã+Ùvžó˜ã3óøñh7¬Í!àÆBs¾\«þDv£"`器ßÏ7þ _‰6Gé/ w�wjV,f¦ ‰QVXz!-ãÖñå¾~®ö«P¦ÑØäð­ö×ÙÐ�’ÔnÐÌBÓÅ@I·Ò €Ï&êÉsޝc¶Ã”Jç*Ò¢\0-Çêrªô\c#êβø`-ûÙ•1­ìµeÞŠ¢8XO–³fSÁÅ9Fi“ÝÞÉÿ-ç[{5`,ÆU$¸Þbwþ…Óiž¼/Ù¼s0µÊ¹F²8Š9¿m>++Ùhm @̯1ΫiŸ";{ewXùiÆôM`Æ·q«E¨aôbÌîqþaæüÊßQ[Ó¿®ÉuG‘‘ »ðò3yOÜÌWûá"ª8;O‘–|)ÓIOµ:¨ièß›§Æ®ca.—ˆ`;€ßey_“3%‚scã 5¾�gWúq®zíu¾{â"«ÙQMOtgyøD­ûÙÊ�Gηp.scppèLÛYFÄ—ÚçÖf‰½Úž0–à*Ž9'8<‹%g$j°96®î›Ä÷޾��Zõ[óÁ’x÷©¦? `{�Æ1ÿmêüÚ¯QPó00Àâ¦Ú�;D’h�Á™7VêºþhÕü¤ü´;yXUë~EÅÈõÇ29nµóCˆs€KÌ“5 ©‘UîüEGŽ_„FKBô+Р9ׯ2µc^2v~4ˆŒ%q°A�HÍý2†¯N�GWè ; Rºc–µÁ`�ý‹ÔÈZœ+2ó˜ò8$Afæ(Àðs•‹¤Ü 9×�iåðöyÜšÔ��ÌYìÆôPý‹TƒÁ#qQÛ\ºCg)F°Ã’šß‡,R=kpþº-ÁÃý] öp^k �‘¢ÿP"ÃèsãÚÂXKúpW‚~ wgy‚>k-RŽ{<âës«·Æ@µ…¾)w1*’>ËÝùz’®ç—*èüæ<²‘QÚàÆwÆlàÃõ°óCà@ÔÌ‚f –äsŒ9….ßò,KÝ~"¾;ËsÀA¡sˆ ã™Èøj['†R á§è3 Ad6¹ñƒŽzÝù!üI@14S°$Ê/ ¾ÒÞYÙõ“(|(°ÈÂ�CDzq.ËÌã_Òv=¿TÁÏ€ÎH"üÞcÎê˜ßXϨ$ã»×¸7tih+ Žm´�R"T$˜×½‰OM[À‹¡ƒ„�ˆ0n<›Bç�ÆãpQÇü>tŽPQ¹…^à¡Ð9¤á<Ûž!:DH‰(÷„Î �ÅœGl6 ½LEb ÀÀÐy€†¸ô" aäCG-1àÈ/O„Î!�Ã�'Cg-1À�9¡3Hc0X�iÓmè‰*ÎíP»…¥1Üø‹F?þ‡„€£r¼ü,t©koç´wrè IüY€¹s«Ÿ �Cêˆq!1ëÝXÁ#™BGJŠÄ€È¸Çáuཡ³H]x*ÓÉM�òpÏh%ê “££3t©ÿK;ÿðW̹1t© Ͼúš¾LF’È�Éñ;`aè’n—ž¸ˆÁÐ9’,‘ã¡#Hz9ü4“ã®Ð9’.± ÓÆ½ÀŠÐ9$•6FpIèi�Ø`³‰1® �CÒÇœ‹3¹úŸÏ¯[2mÌž �CÒÜë2óøqèi‘è`³‰ ® �CRcÑæ}øFèi’ˆIAGâ`ù,�Ÿ�E’Ëà‰h�S�¸“7BgI“D� ÜàkZDL†óàíü£—øC÷ÄÆM¡sHò8mCGI«ÄŸØ^W–“ î'e¹¥âÜ�+;:™:HÚ¥nGÊÏàßܸ t f£;çuÌcAè õ 5‡[ Žãk ÉÔò>®�¿rRW¦Íacs&ðvè,R;s cùèÔ]¡³Ô“Ôl•Ï2á“ÿR”7þ²#ÇÜÐAêQêF[erÌ3çòÐ9¤ªî„©Úù«'õßžÝ3øGŒ¿ �C*j-ÆÅí�̤ޥv°U¦�o „êE¯×6÷ñ!íüµ‘úÀV]Y.1øuô;5˜{â˜K¦ÎgUè �¤®v–®,³ n&†Î"Å3ç¿gæq}è�(õ‡ÛëÈ17*0ø�ÐY¤h+7ïÃÍ¡C4ªº*m xþµ×˜îðut¯@â™óí£oÐrp¡ÔÕ!ÀŽVœÃ1W›s. \¥Ñ<і㣚·?œº.[å³|¸ÚaVè,²MŒñÉöN®…Ô†fêÉ2ßáÌÐYÜø~G§¦ï ­îÎ gË0ó<‡îÐY�Ãcc×óíÐ9¤� @&dž¦˜³€WCgi`k 0cÊ}ô…" VÚæ³2Ž9X:KzÝàä#s¼:ˆ i¸0u>+,ædà•ÐYÈZƒ“39zB‘w4dÈÌ'Çkèùòj3xºÇmYôU¤a À–ûÎ�æ…ÎR¯ îìàcÓr¬�EvÖ0—Gâ`ÝY¾fð÷ÀøÐyêÄ:s.Ó2]ɦ°�®Yj17'†Î’bÜnpY&ÇË¡ÃÈÈTvà`ù™œ‡óà ÐyRæçó­Ì|ž DŠ£0Œ|–Và|‡+�ýBçI*ƒ‚Ã}QÄ5msy,t€ÝXv.¢>Î5çb -tžYnpëÌÑuýôR(Ò–…'áüW3>Kcž,Ì»qtÌgiè0R>€ä³ìœ±å铨ßbð²;�ZÄ£nü¼c.φ$•¥P¦•ŸfLÿçÎ)fœä0�tÎ=°XfÎOÄÎãSç³"t(©.€ [z:ãÇŒáHŒ�FÎÑnd€Ã€ ¡³ÀKÏ»óŒÁÓ±±‚ˆËcž›™£: Ô– @�t�âA‹�ƒ#x°¿ý½°70Ñ`/‡q£hvX¬chú³õo`¼b1¯zÄë8¯˜ñba�çŸnb­vrÙž @Â,½�–Ö ì±õuKÌ^C·l7E¼Uhš>«i€�™ý¡rŠˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆÈ0þ?¸ô�£èâ�IEND®B`‚jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/jollygood32.png000066400000000000000000000052601523150047600237700ustar00rootroot00000000000000‰PNG  IHDR szzôvzTXtRaw profile type exifxÚµWkšë¨üÏ*f ¼Ëá¥ï›ÜåOI�ÄÉI÷œôœkwc�TªB6ë³ù ‡/Κ˜¨äš³Åk¬¾á¦Ø}ìÖÙ¨W=üy„ßOýæþÀ£+  ûg^g|Cz¼@ñô÷ç~CãØ)ÇÐyp3df™mžECÁï~w~›z–ÔòÅ�óߨµóÒóò;‚1ìoü .X½ú=(`¡††«ÃÕç¥'é½Åǯñ3÷н àýî%~vœþðÇ6ts+¿Äéô»ô>~¥ëŠœ¿Ïì¯+ZÞV{=.ñcž…ymïZÌáÊÇ©›+z‡�áÜÑÈ8 ÿ ÷¤gÅYl³¨M¸Ú�íøQ�GÄÙE7]sì–¶Ã ,1úå ­÷q—¾ÈW?”(§cOøÌP€ÇrÝþ¾§óV™“Ì<FzcÀøù4¯?=Ÿ 1Kš;gË=VX——üÂ29¹bq|bš4¾ÎìÆ¾l‚IÃ\à`³}›èÉ=r+(ÎÁ&ƒ¡Ñn¾8šÇB„¹ƒìŽÎf$¶ËÎ’÷äâX€OÃÊ}ˆ¾—Lò«ô1„ pŠdNÃ;ät¬O~wC^D 9 �VŒ)fð­ …šI!Å”RN”Jª©å�cN9gÊ¢S�EJ”‰¨P¥VB‰%•\¨”RK«¾ÈX25Wª¥ÖÚ&m±ÁVÃø†Žî{è±§ž;õÒkoé3âH#eÔѦŸaBÌÌ“f™u¶åRiÅ•V^´Êª ºÁžGNœ™¸påvGí úŒÚ+rߣæj^�’qô@ ÝD7Nä$ f@ÌGÄI@B{ÁÌ£ä3[}0!$�U&g:A Æå|bwÇî�Ü—¸D÷SÜü;äŒ@÷'�3ݹ_q{ƒÚl*·A"¦PÈúaÀ*Í—&ûÒ�ZóÓ?2ÄÄú¼fãg«xÀI´ÝÊîÓ‚´ˆøG¶ /Ì‘K Œ¨r¯‡ˆ¯¡/7Bñpa„vf(Oö B$¦o†¿´*Ñ=vßZ5¯ËÞvK~Zq÷¹µP; Ìƒd;"Î6M,A°uAU’C•Ó#AjõÞþV›Ö ÉŒP0Ï„¤�³ÏÅ32¶�P·‰¬î3#[bê\+6*yO_w`‹ëœÚÂC“1ŽS˜í²´Z°•çU³8�÷ÜØçúnYæwׯmº/ˆ(Û.Ë ˜ðg:%�ÙÍí‰gõ„ÕROö‚fªì± öM�à4Ù&}bÿKk¾áH2ín{õØÎ¹"”¡ZÈ¥Þ•¦Û� ½ ~YïË5?öÇíøiq"ó&‹ªÛ©Ð“¦B›HÂÝÊâ¡;°iþCȘoÒ¢vR6g%Ä|¤ºÌxÐ î¦pÚ Ú h›>‘»€[¤pkÒâý�z+�ˆbÜGùu¦š“ª«­5„<›$k Izžeº,£Ñ5`$ÓxåKf3‰AÚ²¢9HGDÀ ø b+i“Û¹Åå_@4?Ïf…PÙ³ »&Þ5!WÄ^D@bÞuåÝTÞÙ«#“ìÅ�‰Œ1*¢}hÞÅ(‘úº’ÃIÆ!áPÒå.)ÕSãiwÂ…®a5�¸B|⃾¦ý}Íù–½æNß {•?GÃÐ^Qå¯P5íU©:—OBUÄ.%Es Tl�QMÜæDµÒ~.µ/­‡§yî0’¤ÀÜÇ÷ÚÜ—Ôî% O±4¿Ìÿ#E4w�Rá[äHU©Š[ª)ÔzW¥šªTîx±ÅõM`Í™@šCW;²\d¢ƒsÅ“Z×Ë3ÏP“J­¨ (¹|y™74ûQ6˜ïÜÔ^qõrˆ¥-äO*sÄãÍþÛžö¼¿}³?¿ÙïŒýCÊf>JkYÏTß„Ñü²üäðŒ¶ÉTYj)™ 5ØêŒ2LKÔ‰P'ëÍ`6¾M6@—òCOÍ«ˆÿÔÓÏ6È-V7Éc_·�Kå` äR:X|¬l%×ÒARøÚÑðí%.åVÃ{8^D½¨Ã\áwp.ŠM–åñØb ¨ÄÈŒL;„¦(ÖQ¨@tÙÖ…v×q57"þ�Ñ/atÆ�Å@'£ØP¡j8�¨ <-"ù"WàºÕRDò�Qìç\ƒf Hþò}K$ŸÀ‘(ön¾�³Ô´ƒca’¿Á[ì�b�—t-O§T†ãسÈÒ[=×-x!> Ô‡!@ûçêCÝbÈô0 »±‡±ÕÁü ƒø€'Q¬wå�÷_â7Ã�I‰Ö…IEND®B`‚jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/jollygood48.png000066400000000000000000000113711523150047600237770ustar00rootroot00000000000000‰PNG  IHDR00Wù‡ ‘zTXtRaw profile type exifxÚµšY–#» Dÿ¹ /�ó°Žçx^¾/H–TsU·ŸKj¥”Ê$AP­æþ½Ô¿øóÑjåCʱĨùóÅ[y“õù;G£ý~Ýî~Åç7çÕã Ë)÷¼2Î{}å|xÞ�ü=ßÞžW©ßqòè~ñ2 “™-oÆ5òäì9oîgUìySã«åÜu™zo:×¼ûìÎ�ñœUv:ãô~µç"‡®¸Ê«áÕ:cåLØï5¯Ú¥�þS×}âÀÇ»wþÓýžwOwœ�^–ßùéž7ásÿm/½¶ÈØÇÌöµEË=M{￵F^kžÕUîŠwQ/KÙ︰áN·o‹<ÿïÓ~YWÝAm°Ô¦tãC1�/ãÍ0Õ,3÷±›Ž‰ÞN›8ZÛñ»œË.ÙbûÅËÃ,›ø —Á£ƒœã´}Øbö¼Eæc²ÌÌÃp¥5 Æoêý‰¿}¼h- sct~ø »¬ÄfròÊUbÖõiØþ5êôû?Ö�`ØnÎ,°êv†hÁùbÒ¸à"æCt{£#�m¢ÑÉÚd ~ÌàS±Ü:o˜ ‚Xi½sp²•¹¹'™}­ öœ†^"¸èÐ�@€å}ð‘|Ë„PUÁBˆ!…J¨ÑECŒ1Eᩚ\ò)¤˜RÊ©¤š]ö9ä˜SιäZlqÐXP%–Tr)¥V&­¾2Våúʉf›k¾…[j¹•V;áÓ}=öÔs/½;Ü€Ôˆ#�<ʨÓLBiúfœiæY&¼±ìr˯°âJ+¯²êµ‹ê[ÔÞ#÷=jæ¢f7Pr]z¢Æé”^†0B'A01ë ˆ'A€€¶‚™ÎÆ{+È fºX§œ +ƒ€3Œ ‚~–y`÷DîKÜÞýSÜìgÈ)�îŸ@N t¯�ûˆÛ'¨�ºéÖm€$ ñ) éH?.˜¹Ú\¥.ýÕQýí�¿G,ݩ嶷PÞÇ9¥ÄïÑ�¥ilãÄ;b3ª®0��¦/‡›jºŒT1�cÛÈé5qưmá8‹«æjaá�ic–#Õ׿¼-+·±V›#­•K�nͺ[)­dF[£˜\fos¹>1zùô2ÄA½Â}>ýžü9¶N‹`³©ÙÕX ÞP¿5ýsËW&½ÆXjtó¹Ý/wß{IÖ}7Ã~j¶z5÷w–oùîËe«f~±üKÃ_®>[¹wS›ÇÄî­Ï^Ù½§¥üºQ’3•>Œ&Ìb& ï!¨Ÿ�¡‚y˜9V¨$mõd6éñEQ[Î&2“õ„:[ú2$ZžmºÊhJ‚x%×àžÀ]­»2¡½,kA†TøÕ-÷ÒÒhÛ+h‡´¦ �¯Jþ·‹»©ºÁ-�Á'®ÕÕòJmá=FYcîU…T «xÞ‹ÝG¥ß�øùúX9»íñÊ{rÝXå-,Õ[+ú}&w>C”–Ô†2Áct(ÓO8ÞuŒìó†eHâ:Qˆïâ…·˜ ¦ó¡µC.cÔ£m ÓIÍhÙÌfw!C9Â7JGîxò ¼ùà_æÔ«â¾’S1õ² /™éwuÉɦ9UHmÌØ×á5Ûf†w›ÞD³Ÿ£=î VŽn®ü‘ Õ?³Ï�f ÒDŒe$ãNϰ|“#•¼{ 6("Û“R¤Ó©E3ÛfBQj ~oDMÀu·S…†9éð6uMÝ™•úKT¨?£/Žo (Hâ¹ÚYÉ6J¬qÎ< ]Š ¹tÙÍH´€ZpÔ™©|Ž3º0BÜ“g©™™‚Œ Ø@Ä")–f‰ÊÞ"g6*R×ÔfOÁÕ•ZîÔ$È…d ~Z¦yFåÔ*Ö4,�Üì½¹„�È©�D®õÌd\.™<å4©Ü^RIb$1Ü3QäQX‰iÅë¡�芊Š!ÐÑÉE„ëož¾C²�¼¢™0³ëÄD6Vc1¾ùÃ6=`ªç¹hES+R‰t“Ä9Y/ùY)¬ÒÏD¯q%ùluÍszÉÔÔ²w(%~`(L=Sb»”KÓ¿¤^¥ÍÑb1¤‡+®Å×Çbu|±Mnâ‹c2±3Ä™± K†P˜‹¡"�à‹6>;)òÝm`aü—,³o³¬éÙ 4Ü0$fñœ]VÌÈVâ=k…�›!<@Ý—!vÇ!äÒhA0_Ó¥œ�ÖÒ Ø0É2FÍ+ ¯Vép°&ÈS m²²Amõ }-4c$�‡.‘²M�¦æ™Ø ¦½P¨ªÎIK«7‡êtRˆ0–Ò'—?×?ô]-u€ïŠT>ÈSQx�1ÙLRÞáNT2±�Ø—IÊñmV¿«Î�7vøŠSÕö*IÙžnÝí„«îI ‰É`2Xâ`ÁT.â€þ!—Ôi,”«}’Ý£�ÒšJoY£ª­f3Ô{i ¤‹ñC >hVš0R \mjÒ¦†DÔvAwJ[\›Ö6Q'…ÕŠðO×bj%5ÅTXPj;¾ÕÝEdX”0×;Àôb�_”eÔZ×;X ¾²9½Í@Ô"YêBG£˜I ¨³QïêÆ¬,W³D-áò<�¤‡0Ó&ŸC—{.EtµÉçpÄ&›¯á61�ÎαqrŒÊ1H8OOx:ÍG£-£rý¯ðwT¯OJÞwEPÞíïo†ŸüÞÙ­$½I´Ö„fF@Á•²³¿¡S¼¿ÚªzZÃ3”Ö/CmÖ`0”ZU¬?ÙW¢ògM‰*FŸ‡(wÕQfPäå>{žÆÉ^â%zšEôŸÐÄš –Ô+â{„¸š ë�„€ùÜæl=¯ �Ñ3¸â_–×É ªJí1úóÈt3²dþ@�º¼àìñȆeÜâA>Ó7CªÑÏ5BZ’¡ÃŽ‚?“I1=„@ŸœTÌÿö?˜éž1–>v•pìÙ=k“ _Æ"ìšCxÓn ›Ÿ¦’Ï4Ô-v6™5uÌ^t¼Ä1¼—brž ì"WÉ;Gݯ#TÉs¬ÐsS]‡bÑÉÔ#�ûÛ~­¿ëšFû Ñ÷EÔ?òwkVlš9¥(b·ì~©¤}è ¹"`ªÛŸX" þlc ¶É^fâšÄLÙ{™©vÏ¢#ÈçåëíÉãîÉp:v{:v!‘3Gï+ÃU¨³Ùk—»’ö¶èæåè4|±Ë]Mª•[à»Í�/N„ ñáq¡lѳfwbpA]´§øé¸=§ûa:ìƒúDfI}Â+-ä=jwRO!§ÀóÄ í(®~¡úߢ½¹)Xguz;Ù³ÙÝÝéíŒCŠJ·à7&?ŠuJ/—"u S#<­2Ƀtpô–QI€ ŸMqQަ­¦$Ùˆ>„z)É”Q·˜ MWtf  #ÓXµ§ðxõž"S=dŠâN+¾…N8½¸ìЋ–�«Ì� 8ó“ýb@Gi –-s»µXxD4ê+"éöI$NJ±è2)FÁ>KjòVÙÔdÕŠêÞ*ûI´Ëù46xSƒatiÀ3�æLJºwù¾ÌÅÀhx–ÖlÝÕ;ÙI·í%süÉÚs´@ ³¥"Å$ éi%³W„çeN ù?íØÔwˆÇÍÃZÊ‚{e®1OsImu0Š‹˜¨©MçÑ&ÑÇVj»¨·±déG¼• +ßÍ/�/kþ›liP3Qß¡iúè–òDƇLPÙ÷?½¼H‹*=ôéÆ@ßâæ®zØ';ëÐfØüF¸n%b° 2µ‚‡†`QWŒok±“,„ù•~aJãýu2‰|•·²î�v5ÅJ„M3j{ö¢Vé÷�ùŸd¨þ† ß“a§È¨�îÝÿ¼èžýσnÝûŸ9ù&;œDZ•ìµ{�óÊhF^<·„]ßi—�Yí>©SäIXF$£æ†Ö|ï»_Æ7P÷ aÊv÷Ÿ¬R}ë¡DÙF€zÏþBf\ûG›@¿¨ªø›"6ÿ訸áU?•ãí§q£o€�1%à@·’•=:\dQ”Ö;d¿°—]¯Ã°Õ/Ñý\õ t᩟ ”úX¡dèm�2í«õE/òképöŸ�ïV¡4u‰Ü¸‘éÂÅùgÿÙ­³ÿ ð²ÿœïùŽW(ÉMÝvAÕC!‚4DézñRÿƒ:¤þ÷:¤vɾHD@Á´Ãƒz4ç”J—­4 Ý›âm¹B��´ØztWYóÊ®¹¶ìêNdWû7*Üù ‚^ ÍÍwû!éž첉,¤M =»ÎÂÙ�mçZE4ùgŸË,w¶; ¹gS²eNpV¡àd¤¬×Óîs·„Šðþå¦á¤Ô¤y2Iw¯fD fA�Ȇ´l~z~긗_“ÒþMEM“¶�;?ì`r³L¹*He,ê+‡Å ˜Ã%®¢xµáìÛâ¥>«^vÑþ¦xaÍú®týŸƒüGZd‚t„ÿ«m7‡§¯…iCCPICC profile(‘}‘=HÃ@†ß¦Š¢+ˆ8d¨�,ˆŠ8jŠP!Ô ­:˜\úM’GÁµààÏbÕÁÅYWWAüqquRt‘¿K -b<¸»‡÷¾÷åî;@¨—™fuŒšn›©D\ÌdWÅ®WôbaZ£2³Œ9IJÂw|Ý#À÷»Ïò¯ûsô©9‹‘x–¦M¼A<½iœ÷‰Ã¬(«ÄçÄc&]�ø‘ëŠÇoœ . <3l¦SóÄab±ÐÆJ³¢©OGTM§|!ã±Êy‹³V®²æ=ù C9}e™ë4G�À"– A„‚*J(ÃFŒv� ):�ûø‡]¿D.…\%0r,  ²ëÿƒß½µò“^R(t¾8ÎÇ(е 4jŽó}ì8� ø \é-¥Ì|’^ki‘# ¸¸niÊp¹ =²)»R�¦�ÏïgôMY`àèYóúÖ<Çé�¦^%o€ƒC Z ìuŸww·÷íßšfÿ~d%r¡™ÉèjbKGDùC» pHYs5^5^ "¢wtIMEã  ™G`×XIDAThÞíÙ]ˆTeðßÌZ‘{ÓV´E!*/é•!}Aà…ætáÅ!$”ÝÖM”%‘YÝT”¾˜l%Hm[aQ³h­¸i„}ìVìÚ®ºÛ…ïàì4³;ãÎ6'Ø? ÌyŸçþÿó>ï9ÿç=Ìasø_#×Hrßz&r:Љ °$]cy´§ñ“ÂéÏ¡à÷ñÓ–¾1ËŠ—`%–a)®I„óMºyø}ø½÷E‡ßŸ‰€bAîÁÚDü¿Æ÷؉!ú¶nÅ‚‹ñ6â¼ ”öIlÑóÓ (\��S]g ãçÓ¶0N�”Çc$ù1–O›”Qò%®G@GFÉï ÑP=.Ì ù¿°©žÄy8”Á2z6DÇËï‹°7áÖ„èDI@n˘€ññ0VWÄã@©„^ËùcXP,èÁ‡UÈã•/²�pCFœJ•QÂx'ù­.\–Æ7†è¥’€ x5c3ñ6­ê¶whl’?û—ã8:KÆl7~Ëù¬ ñ,yѯx2^Šuù--œ àG¬ѱñÞ²ÿw”[ãgR�µ»Bt´ÎÜEù²éù oe@À¡iâ·—ýï¨lNvd@ÀÁ)š¬Nl)êŸ$ ?n/~i±€þä¤ÇéEeÃÝ“,Ùm"uD­Âð訟«�Ø�…eÃG±½ZÛÊuppÙ›g¾Z/_,xŸáÊŠÖó– WðÆZ]>Å‚5ùœ¯±óËröaEˆgrs5ê­«ZÑà]Ü�ë+b#xðT›ç®{}²�®†OZ$`múUâmÜ[íåVKÀþ YŠº¢=¦hhªáÓÁãx*DÃÓî ÕXý¸¶ä{Ñ¢#um]Lënù¿qk½ä³&`[C4ÒÐæQ­ÀŸƒzê0VÍÄ–=ÒèI5¬xorï9ËØ¢ÏåÄ)·ÊCôJ2P³‰í¹3[&ç„yuät¥÷ÂU³Ðónqfk­®/4Å‚vlÃ]Ír�¸:Ä™[÷†>1 îÇÓMТ—›q'ú\¢m¸sMOnnù†g l&æc]2^Ë+¼zåÃ7i í“Óv5w!åšq‘b�d;ÚÓБ¶œÁÅM&;‡9Ìáßø¼Ø‰te©IEND®B`‚jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/jollygood512.png000066400000000000000000000336471523150047600240650ustar00rootroot00000000000000‰PNG  IHDRôxÔúsBIT|dˆ pHYs¯¯ù¯ðŸtEXtSoftwarewww.inkscape.org›î<tEXtAuthorrdevries"W,ö3tEXtDescriptionsilhouette of an English porcelain cup.¢úÏd!tEXtCreation Time2013-08-10T13:05:14Z†ÐÈUtEXtSourcehttps://openclipart.org/detail/181742/english-porcelain-cup-by-rdevries-181742åÃÕÀXtEXtCopyrightCC0 Public Domain Dedication http://creativecommons.org/publicdomain/zero/1.0/Æã½ù IDATxœíÝyœ]eaÿñÏsf²°/ЍàB!sg�ÐV\е¶b]!7AT¤.¨uAŪu©Ñ߯u믶´n¸eÍÜPj]"‹¶ÌÜ ›"«� ��dfîy~L‚!$a–{ïsï=Ÿ÷ëIfî=Ï7!r¾÷9ÏyH’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$IR« ©H­&B8Ž=fDº6³û–ßϺÙ-ÏéÞâKÓ»»Ôcì<ðPž“oýõ�±&Žbd8Ìd@×ëJ†ë1¶¤b±¨­,?޽ºFØ-dì^«±{WÆÎ1g¯‹ü>fü¾;㶃Îá�±;-¤"°´©¡£yFÌÆNî1ð�#jðª9 ˜CAÿ,$u¬u. ‘ŸvuñãY‹¸9u µ–œônx#Oëîâ/¼6Âk€½Rg’¤&ú]ˆ\D ÒSâ Œ¤Ž-ýeºJ‘#bàÕðا|IÜ"ÄŒóCdi©Âpê@j¾Ž*ýeºJð""å(ÏH�I’ZÜê…Àfõós7$*޶/ýeºfE^‘Á‚x=ð”Ô™$©Mý83Ë9£g ·¤£ÆjÛ0´€Cóo!p,~Ò—¤º p%‘¯M_Çbï&èLmU†Žæ±‹·Ex+Г:�$À}¾=ùæ +Ü�:Œê§å À¦)þ£Bà�ÀßâSó$)…Qàüþmv…+S‡ÑÔµl:šgäï �·GØ/uIÒc~’Ãç,í­å ÀÐÍsÞ ÌL�G’´]—ÇȧûsIê š¸–(q!Yuˆ7ùpDê<’¤ñ‹p~'•*Ü‘:‹Æ/iˆ Ɇ9&â¢>Ijg�ÆÀWf>¼k =$)ËNdÚNòv�p@Š ’¤† ‘÷”sYê Ú±¦€ahóbà_€ç7slIRÓD"_.õòIŸ9кšVçó7D¾Ò¬1%Iéøp|©Âƒ©³è‰^ªežü{»‡_’T,¿Ï2^Û³ˆÁÔAôx +Ë^ËÎ;Íä“> ÌhÔ8’¤–÷Gõ.â7©ƒè�R6M÷Ÿ <»Ç—$µ�Õ!祥%TSјº€_Çn#3ùJˆœXïcK’ÚÞ�ÃpØ¡V¥"Èêu ¡ù¼¬6ƒBäÝxò—$=ѳ¦ÁiÑsDK˜rˆÊœ”G~æ=ý’¤ ðê¡2oJ�CSlaÕ2{?p…¿$iîØ°ÏŸ{*#©ƒÙ¤g0 XæÉ_’4AÏž±š£S‡(ºI€¡üyÈù¥Sþ’¤ÉPN�¡è&\æqtž³xjòH’Šá/S(º €�y‹€™ Ê#I*†½V¼‘§¤Qdã.ÕygÝ Ì#I*ˆ‘Œ=Rg(²q€�yüu œ‡[úJ’ê$Ló.€”ž´¬XÀ 6Mû{ò—$ÕËÆ5îI¢ÈvXny3»×rÎölRIRD¸n~…ZêE¶Ã°q˜ï³š”E’TYdhèhž“:G‘mw'ÀÁù¼‰ÈYÍ #I*”GÌ)U¸5u�"Úæ Àµeö!rJ³ÃH’ eçïM¢¨¶Y¦þ7ú‘$5ÞKS(ª'€ xAˆ¼9EIR±xnê Eõ„�ç|>BWŠ0’¤b‰¸`*�[¸b/¨å¬` O ”$i"6ìÅt Ü|�;Ñç5Þ¶õ×$Ij”µÃNe4uŽ"zìd’ÅÀ[S†‘$K„5bêEôX¨òRàY ³H’ŠgMêEõX�—§ "I*¤Õ©Õc ‹‘2ˆ$©�V¥PTlºþž:Œ$©XÜœ:CQe+–³/°[â,’¤¢‰¬H¡¨2€Z`¿ÔA$IÅ2 @*@–ñÌÔA$IÅ32Â�©3ÕØ€ÈΩƒH’ çÞ9ç±2uˆ¢Ê6ý¯[0J’š*F~™:C‘m¾ p8i IRád�ËRg(²±5�ÛÔR‡‘$GÌ,)e=‹$ò¥Ôa$I…±º4‹�Ô!Šì±�×ïÍÂפ #I*†ÿ’§ÎQd�€¹§2’ÎIF’T‘³SG(ºÇ Àm'03DNJF’Tk¦¯ãR‡(ºÇ À£ë8Ø?aIRXràÅlL�£è+Þ’2ˆ$©jpzê ‚p}™ýºáÎÍ¿–$©A~Ó[áOS‡Ð¦€îÈKñä/Ij°ù|ê 3v ðâÄ9$I.Â�ûsQê³y @OÒ’¤N·:ƒ÷¦¡?Ê�:ˆ$©se�·—*Ü‘:‡þhìqÀ°_ê ’¤Îà=ýœŸ:‡/»ífÓS‘$u¤êCprêz¢lõ:vKB’Ôy¬Ï2Ž=¢ÂúÔYôDÙ´î?n$IR½ä�Oô,b0um[–�2’:„$©³¸fEäk©shû²Ñ™îÇ,IªŸµ¼{~…Zê,Ú¾lÎé¬Ö¦"IꑯͮpMêÚ±Í×ÿïMšB’Ô)î峩CèÉm.HšB’ÔB䃇œÏšÔ9ôä6ïxOê ’¤öàÒÒb*©sh|Æv ÎH’¦&�|2u�ߨ @ÎïR‘$µµ‹úsEê¿ ‡�ÔA$Im+øLꚘ `ú4wj’$MRäœR…ëSÇÐÄd�Íýx+ $i‚Ôj�ϥΡ‰ëÞâçË�§§ "IjKgÍ©pSêÍvC™º¡/ž<;æ<“ÀÞ›¿ p_ Üs~=s—xqkí¼ûX‘å1ð×)ÃH’ÚKÈø÷Ôí–7³ûð0GÄÀ‹Cä¥À #ìâØkBxâû6?Þ•‡ªeN¾Xª´ÆŒûc‘«eæE¼S’4>.-U82uŽz‹ÊàU^àÏ"tÕéðÆÈÛúsQ�Ž7i�ÍtuqŨ�m�$�Wä«©#ÔK„08�e° (gðŒ-¾WO{‡Àyóxcêð¸I‹Á2·ÏME’ÔFn-•8(,$Od*ªÇpHìâX" €ç4qèÕµQžs+›8æãtoõë+°H’žDˆœÒ®'ÿ�Ì 9ÇÇFxA½?â�Ó^]Ý|89Éèl]W9.QIR{X×5Ìi©CLÄm'0sÝZæx79G¤Îà­K�äã/¿„Ñã?®Ä—…l{/•$ œ{ð<’:ÇxÜP栮Ȼ׭ãm[Þ¦× "ì³ï¾Ì®M1þã @ï†Ê¬Š°OŠ0’¤Ö—Ãé©3ìHµÌôÞàÝÀ‘¶q“^kˆ5æ�¨<îó~€˜G–¦"Ij wõ–øyêÛR-óìÁy|1Â�Î^­{òȳ?ÞmÐl[/„Œ¥Dæ'È"Ij}g´Úâ¿j™‰p,�i©óLDÈ™™jì'€®?Ï] IÚ†Ð"Óÿq!Ùà ¯�“#m¼QHt[]èYÂ-À� ²H’ZX„kK†Rf¸ífVËœX­R � iç“?"÷¤û‰—"¿ ð¶&g‘$µ°8?ըזÙgzàíëÖñAà™©rÔ]àöTCo»À/À Iú£,ð£f�9p,Ï 5>à1²S³Ço´�qWª±·Yj9¿èª×c$Im/Àݳq]³Æ[~ ’>A�·Ó“](o°‘œ‘Tcos¹ßœs¹ ¸¹ÉY$I-*Â�BÝŸ‹óD7”9hp>ßÏ2n"ð.`z£ÇL©Þ˜jì­÷ÿߦ¥�$µ¶ÐØ'×-/ÓW-sN 9ží_¢î(>x}™ýRŒ½Ý#g73ˆ$©5X¿a}c6ÿ8†ÙÕ2?Èàú ØñÓNôŒn¸pÙkÙ¹Ùo÷ºw1W·61‹$©åpùÜ y´žÇ\Àáe~2nˆðVŠwâßÒ!3gð�Øä] ·û ÆÀ™Í #Ij=\V¯cU�¡48Ÿ~r~àÕõ:nÛ ¼i°Ì›9äŽWàtš°èC’ÔÒ~5Õ,?†ƒ«e~@Æ DÊ´øý)øÊÀ¼æ=ªøIÿTË\áEÍ#Ij9#µ™ì5çtÖMæÍCG󜼋Oo§ û¦èÎiÝzÐÙÜßè�žô_F§ €$Õµ“9ùWË<;F>�N€öz@ObÏå¿#¼®Ñ·]>颋 B’ÔšBœØõÿkËì³é‘¼›ïã÷ä?q¯*ó¾Fò¤ TáÁ7:ˆ$©õä�+Æóºj™½«óøÊ ¸�ÀÇ!Ýcn;Ä—0«‘Œë¶‹®À÷B’Ôšr¸~Gß_öZv®–ùD„߯ÀG#�·_ v"çŒj¹q;!ޫܷ’‹€{B’Ô’ž]á÷ÛúF\HV�GyæLª¾ìÙÜh�/À¡D>Û¨ã�«¼üFÝ@’ g`[ цæóWCU®��~à¹Í�U1ð‰åeþ¢Ç÷ÎKYä{� IjM!2°å¯——ù‹�2W摟FèK•«`² ¾{E¹þ—VÆ]J†"\Uï’¤Ö”–TËô Χ?ƒKüyê\ô¼ÝáŸê}Ð í½œÁ×;€$©5…ÀšÁ2ß–oÚ½Oé|´z ‡Ôó€ÚŠñ–7³ûÆaîv©gIRKÚÌHB�¹zŽ˜_¡V�ƒMhàÀ3y˜À’z ,Ijyžü[ËŸÎ õÛ hÂ�_Ìà»õ\’$�_ˆüß�cyV]Ž5Ñ7DÕ2UhìE’$ÕÉÊ¿ c?‡cÆÃÀô]vÊržÏJPŸl]Ð[áõS=ȤÇ88�øêT—$©Î.‹�«²ÈUÃ#\uÈù¬™Ènx#OëîâEþ–Àk€g4&êäe�Wöôó³©cR`ùqìÕ5ÂÝnù(Ijw¸ ä\´ó®,=à46ÔëÀq!Ùà ¯�¿þ†I\:o�†à°©,œTœÇiÞ6Ù÷K’4\œ¾j%¿üF=`õJ1ã‹Àk=ÖxÄÀ;úú'¿Iߤ Àò2/ÊßS¢$Iª“[Cä”é3øþ�gòpŠÕ2¯‹p*°oŠñ·ð‡/(UX;™7Oz*cv…+�&û~I’&à2¯+•8¨´˜ÿLuò(U¸`Z7½ÀSeØäþa²ožô Àà<þžÀצr I’¶'À•1ð…Þ~.L�ekÂà|>øÄ¾'`}ÞÅA}çpçDß;¥Å 3fpLnêA’¤í 0@àU¥ G´âÉ @ìëga ¼—m<5±"ìj|~2ï�RØ4sÖTŽ!IÒV†È{ªpHo??If<úúùf |(a„ã«ÇPšè›¦|;C–ñ­©C’Tx9ð_à¥Å|«^ûÝ7K_?§ÄÀç ŸåaâO œÒ€ÍË\ ^�cI’Š%Â`„7-.o[›vÊ] �`ø<‡ή00Þ7ÔeCƒøf=Ž#I*”ÑY¸q/m÷“?Œ­ ØonM0|ÖŸšÈê2°ìµìpäï—½–�Ÿìeu-Ó×±XYÏcJ’ÚV ðϽ%Ž™ìkÚÙË/a4Ä${ì=c'Ž}²Õµx1cà»õ<¦$©-�Dx[©Â§ÃBòÔaRY¿7ýÀ]Í7DÞód¯©û- Yä›�öÚÁI’TWë²Àú*Þ6÷TFBLrGÀáÇ0wG/¨{(U¸#ÂEõ>®$©-ÜOÆË{úùqê ­¢{ß°¾ÙãfÙŽg²IAÄGKR­Œ9¯èÔûû'ë ³¹?†æ0ŽplµÌ®Ûû~C @o…ŸеÚS’ î¾,ã}KXž:H+Š‘³ » ‘×oï› )bßhı%I-gežsdÏ"7ƒÛž™kù1°¦ÙãÆÀ[¶÷½†íS¼NÖ5êø’¤ô<”e5{ 7¦ÎÒʼ˜�D~˜`迪–yú¶¾Ñ°0·ÂCÀ™�:¾$)­ë‰¼¶gצÎÒb–dadw ¶õ�†>©(äœÒÈãK’’‰À;K‹¹,u�v»ùiŠÛä3xãv¾Þ8>@’:S |ºTá¬Ô9ÚÉì³X \�`è—Üô&žºõÿ¬âè-�’ÔQ"g÷õó/©c´£?M0f×ð(Gmýõ†€ {³$ÀÝ�G’ÔxnêæÝ©s´«¹*ɸ𺭿Öð0÷TF">@’:ÀZàèM�ºÕ$d5®flýD³ýͲ™ö¸,Í5«ñM`¤cI’"ÆÀ;J†Rig³ÎãàÞCï6}õ㟠ДÐs.€$÷?J’ê!ð•¾~úSÇènI1nWäÈ-Ý”@Hò4$IÒÔ]4ùdê�"DnK1n ‰ @o?K 4k�ߥÎЩn(s0'uŽØ*`î…<ðù’”Z€UžÉéstª æ¥Î°É®ýeº’€Qø>@’’Šøé¿‘¼!u†MÂ!ÓÙ¥% Àœ 7—¦Î!I…-�2p,Ïþœ:Çö„0v¹§% ÑYIj’snN¢,=’î˜ñ] ;u–¸Z¹ÎIC’ àÎR…µ©Ct‚§=•O‡¤Î±k{Jcû=´l(UX ?uI*糖¡ÿ˜:Ç“X¾ùvÏ–-!sOIj´-SuÝØ3Ïéf¤Î²C‘ë6ÿ´¥ @Ï"®–§Î!I�,€©ˆ¦Mã»ÀóRgy21ãªÍ?oéî (I�,SR�ÇIÀÑ©sŒC>ùÉæ_´|Èjœ §Î!I�*µLVu/%ð¥Ô9ÆiÙ¡•±ç@€Yçñ@„§Î!IjMϹc[Ãjb–ßXLO�e_NL�c––*ܺõÛ¦ÄÀé©3HRÙkl_xíØÀ|þ%DNN�c2|w[_o«�EÎbê’Ô!n�{*#©C´ºÁ2_±å÷øß¦«[°ømUJî.O�C’:Að²êEÕyüð‰ÔY&+|ùˆ ë·õ½V~^ñ¶Î!ò’Ô1$©íEïØžþ2]Cð­ïH�e îÛ¸ž¯oï›m5P¡Œ¦Î!Ií.âÛ²ìD¦õDNoó“?¾4÷BÝÞ·Û®Ì9�•À/Rç�¤¶×å ÀÖªev�¹šxSê,Sô‡ ëùÖŽ^Ðv ¸)�$MUœÑí À–n8šýãØ:³£Rg™²À?ïèÓ?´i˜>ƒ%À†Ô9$©]¸çÀ3y8uŽV1´€Þ®..f§ÎRˆ"ß|²µeØô—ö'OúBIÒ6ù ?(ó·yÎåÀ³Sg©ƒÑ�sâÖÛþnK[Ÿ ISâõ` ÌIüØ=u–zðÕÒ®ÏkÛï6ÀM6lä‚™3yØ9uIj7¡àwÜv3×=Ê·ˆßA»ËÝ|~¼/nÛ€M‹~–:‡$µ£Pàg ÍsÖ®ãr"ǧÎRG‘À»KÖŽ÷ m[Âv¶7”$íØÈh1 À@™¿Í»¸&À¡©³ÔÙW{û'¶6®m/lrc›µûïC’š&ÀúÙçrwêÍÔ_¦kVà3!òÚüÃï6ü&0ñg´õB©ÂƒÀ¥©sHR;Éá·¡@V»ñXžY‚Ÿ‡ÈgióóÞÖ¬Êj”K†'úÞöÿƒ^�¤‰Èà–Ôše`=ZcY„¿H�¥Fb`AϹÜ>™7·}�œO�š¬$MYä·©#4ÚevœÇ…ÀÿÏH�§"œÔÛÏÒɾ¿í À +Ü \�:‡$µ‘[Sh¤j™žÝáJïBê< òž ߘÊÚ¾^�¤‰éȰôHº«e>á`Nê<�àôR…ONõ8QB�‹Rg�¤vòÎh O÷ÌIDAT+Ç0{Ÿ}¸2€™©ó4Ð×ïÅ;걈³# @i U˜Ü"I*˜�ƒ]Ü•:D½,;‘iƒóùxȸ˜›:Oƒý,˜{*#õ8XG€8¶ÐC’´c·�çA1í`àæî´škˆ|˜‘:OƒýÏ.»ðú/fc½Ø1 ˹8uIjm àm'0s`> CÆúRçi‚î² o<à46Ôó Süê׌$©#…ö¾pp>/~t×oÚÔgZê< øÎSï“?tк¥ kÊ\à¯Rg‘¤vGê“Q-³7‘Ïy_„®Ôyš ÆÀç{ûù\oƒöºé˜�ÁÅÑ I;ÒV .$«ò–ÿ�ÀSSçi’áïìëçôFÒ9—€QøQê ’ÔÒBû€Áù¼¸Zå߇œüï ðоJcOþÐa`N…›Ü–:‡$µª˜µ~¨–yúà|¾Oä2à…©ó4K„«Fan©Â¯š1^G€M~‘:€$µ¨ ½g³2uˆíYz$݃óù0p#‘ãéÜm|Ÿ À7f®åÈMÛÛ7EG­ ²”À;RÇ�¤tg«>x`¯ �/éiÉ€�³†À;Ký,iöÀWF—tÜoJ’ê£å¦ÿp89_ŽL�%�+²ÇMöq¾SÕq—6MŸ´ýF’To!¶Î–éÕ2Ï,ó-r®¢x'ÿQ"_Ú°G¦:ùCÎl²80uIj%yÆ�©3TËì#‹p�ýОíÊ2ÞÚ³ˆkSéÌ0¶àÄÔ1$©¥Ät`ÙkÙyæN| F>A`ÏT9ÊCäߦ¯ãÓõÜÏ*:²Ôjü¢«›H�V�JÒ“é ÍŸn®–™œá³DžÙìñ[Äí1rBïb.IdK·`Îy¬nL�C’ZI-4ï³[ŽbÆà|Þü.·  'ÿÀwº7Ò××b'èЀ—D˜•:‡$µŠ,ç¾F�±ìD¦í´š¿ÛŸ"òì‚ÝÒ·¥{cä]}.Jd{:¶ÄÈUÞ›:‡$µˆ‘ž «uðj™é1r|XÍ'#ШqÚÄ’iݼ砳¹?u�éØP üº�‹’¤ñ°²›ÝðVvÉ6ðÎ'xV�?ñà!àc¥ §¦Î2[fW¸¹ZæA`ïÔY$)µH}·^~{e£¼Ÿ |�â<¨gG~’wñ®¾sÒßj9^[ÄA¸xUê,’ÔêR®/³ß48)Žð`·z³�x(‡“{+|¯U·YÞžŽ-1ðë-’D˜ÚÀÁÌ!çï�ãc17ðÙ–¶ûÔ¿¥Ž.Yί£;HVMø= ÉV ñª<òr^ш\mjMˆ|¤´˜ÿNd*:ºø5¸!�$Ç?pË›Ù}x˜c«UNz˜ªýOìâÄÞ6ýÔ¿¥Ž.¥ –¹xAê,’”RÛ^!,/ó‚i�Y1ò'1rðÆaŽvirÄV·šÀ‡{ûù~ê õÒÑ À¯£@RÁ…ü�`Ù‰L›ñ CÆkª‘¿í‚ýóÍËל/Ý–‹º»x÷ÁçpOê õÔñ ‡+¼5uIJ©X¹ìµìÐÛÏ’ÔA­#Ÿ¸¥ùjÀoRç�$µ´œ:c:á䏯.üeê’¤–t+�{ûYš:H3uü Œ-L�A’ÔrFˆ|iÆZz‹vò‡‚ÌlZè†@’$"\Û•ñ®žE\›:K*…˜(Ux0ÀÍ©sH’’{$Nê-qx‘OþP�€ÔSâOgW<ùïHa @ô2€$uªàôØÍÁ}þ#,$O¨Õæ<¶P’ÔYnÌèéçg©ƒ´“BÍŒ ók°JR‡XáC«VÑçÉâ ·3Þ`™A ”:‡$iÒb€3ònþ¡ïlîK¦]ê�+‰IjS×ÅÈû{sEê í®P—¢ë$©­Žð¡!8¼Ï“]n Ö¸"®öHRÛŠÎØ'ZaUê0�¤pk"„j™û�½Sg‘$m_€kBÆû{qUê,�¨pŸ…Ĉ™$©…=áCUø3Oþ�S¸K!r%�W§Î!Izœ<À™ÝÝ|ä ³¹?u˜NWÌWÆÔ!$I[úU€”*\Ÿ:HQ²t sumµ]©³HRÁÝCäK‹9=€ŸÍš¨pk¾€G" ¦Î!I6"§toäàÞÅüÀ“órÆÖÄÀœÔ9$©€.ÊsNš½„ߥRd…-›6zOê’T Õ,ð!÷ío …¼—¤Î I±†À'êÉ¿un# - –ù=ðœÔ9$©CåÎå£sÎceê0z¼Â^ r �·¥Ž!Ièê�óÁÒ~�:ˆ¶­Ð .‰X$©^ÜÿPêçWö·¶Â®¨åü2uIê#!rÊôéôôös¶'ÿÖWè5à:Iªƒ¥!ç¥%TSÑøú�K‰Ÿ:†$µ¡; |¸·Ÿ%©ƒhâ } F/HÒåÀ©Ý)yòo_Îd\Bž:„$µ�å!çDW÷·¿ÂÏô-â·À�©sHR‹Ûác«Vq˜'ÿÎà "—ÆÀ[Rç�¤µœŒãûqCê ªŸÂÏD¸4uIjA£D¾àð^OþÇÛH’ôGw(—suê j g€R…[Ü�:‡$µˆ¥Ã0·TñäßÉ,›Døiê ’”\ä««VñׇVX•:ŠËK›E~Bà„Ô1$)•Ÿî[Ì?§Î¡æp`“|:? PK�C’ˆD>ÜWñä_$€MfŸÅê~“:‡$5[€Oö.æßSçPsY¶øIê’Ôd÷TøRêj> À²§Î IMtÏ0¼ÍG÷“` ÕŒeÀ©sHRSDþÑÕþÅeØÂü µ?N�C’šàŽ¡À™©C( ÀVbà‡©3HRÃEΞ_ñΧ"³l%D~lH�C’)Â/RgPZ€­”*¬~–:‡$5T7+RGPZ€m‰^�ÔÑ6¬¨qOêJ˰ µ¸+ ¤v“×ÿe؆9ç±2ÂU©sHRC§ÿeØ®èO�A’!Â�RgPz€íÈ"ý^�Ô�Þ¸žsS‡Pz€í(U¸7Â%©sHR=øÖÜ y4u¥gØ�g§Î Iut;ðùÔ!Ô,;§q.0œ:‡$ÕÁº9nÓ^'’`GfŸÅjàRç�¤)Z#¯é[Ì©ƒ¨uXžLô2€¤¶vq úú»¦I�×�:@«ÛeWÎ_·Ž�½Sg‘¤í¨ø^„#"ì¬ �ÁYRªp}êpjM!u€v00ŸSBä©sHÒ¶Dxg_…ï¦Î¡öâ%€qˆ‘o§Î IÛ`Õ#pVêj?€q˜]aøuê’´µøÖ֧Ρöc¯àô𤖳®6¦¡öd§0v7À#©sHÒc_›s+SÇP{²ŒÓ¦Í3¼%PR«X7ù×Ô!Ô¾,à?€˜:‡$ÅÀ)‡VX•:‡Ú—`J†pg@IéÝ7s_LBíÍ0A¾š:ƒ¤b ð‰ÏäáÔ9ÔÞ,Ô[ágRç�TXËzJü uµ? ÀˆyàßSç�TH£9¼',$ODíÏ0 3áLà¾Ô9$K |ev…kRçPg°LÂ�³‘È¿¥Î!©8Ü´ëÎ|>uu À$mØÈá,€¤æØ˜ç¼å€ÓØ�:ˆ:‡`’æ^È£¾œ:‡¤Îà£}KX–:‡:‹` vÞ…¯¸;uI킞 _KB�Ç0œÆ"_J�CRÇÚÇw UX¦hú:NîJ�CRǹ?ÀëçVx(uu& Àx1 ®Ì•T?Ög¯-U¸5uu. @ E¾\Ÿ:‡¤Ž0’Û{qUê êl!u€N18Ÿ¹ ÿL%MR€Z ¼µ·ßG�«ñœ¨“Þ~.П:‡¤ö –Nðä¯f±Ô×Ç€GS‡�Ôv6ÇöõsFê * @•*Ü¢›Iš�59¼ªTaqê * @�=ør€ÛRç�Ôî 9/™]áÒÔAT<€:;¢ÂúxnÜ!i Ä.^\ZB5u“ zûYJà©sHjM~6}:/é;‡;SgQqY$D>î¥IOøÎý«8êÀ3y8u›÷¬7Ц½~‰EK*¼M÷øª·ß燨5xbj Þ~.¾ž:‡¤äŽ�7zòW+±4Xm&ŸnN�CR2+²œ¹½ý\˜:ˆ´% @ƒÍ9�uYÆ1¸A�T8ÎÛ/êYÂ-©³H[s @“TËáÌÔ9$5Å(�O;å¯Vfh¢�ù|3DÞ�:‡¤†úÇö.â7©ƒH;â%€&šù'ËRç�Ô �ÊȇyòW;p Éªežáà©©³Hª›‡cà}>ÌGíÄ€&+U¸#ÂqÀHê,’êâ'Y�ÙžüÕnœHdp>o"r–0©]­ ðñž ß>ûCmÈ“O"½ýœMäSç�4 �Jm”ƒJNõä¯vå @bƒóù*‘¥Î!i\þ#ïï[̹©ƒHSå @b¥N&PI�CÒŧçÓ(yòW§p Ür36îÊ��¿L�EÒã¸2d|¤gW¥Î"Õ“ El*ßcìIéÝEäS¥Åœîu~u" @ ‰çóÙùlê,R�­‹�Ýug¾xÀilHFj @ (óŽßºSg‘ $cÏëøX©Â½©ÃH�fhQóxM8#©³Hp_–ñW=‹LDjïhQ}‹¹(ëâ…®L�Eêx‘³<ù«h,-lÖ9ü~å*^Ÿ PK�GêPº»ù×Ô!¤fó@›œÏËCäôû¥Î"u’9¥´˜“Rç�šÍ€6ÑÛÏÒÚ4ú|ÈSç‘:Ćќ¯¤!¥à @ªÃ!d|-‹Rg‘ÚYˆüKi1ŸJ�CJÁ€6TZÂu=%^"ïL�GjS÷v óÅÔ!¤TœhsÕ2»ÆÀûˆ|Ø+u©]„ÈÛK‹ùïÔ9¤T,âº7°g÷t>”E>äÞÒŽ¸²§ÄKÂB×Ó¨¸,æº7°çôiœá­©³H­(ÀúQ8dN…›Rg‘Rr @‡9ä|֬ߋw¸��´m1ð)Oþ’ #Í=•‘¬‹ã€Õ©³H-æ²Rÿ‘:„Ô ,jÖ9ü> ¼Å¥ÇÜ7 oòº¿4ÆÐÁzúùq'§Î!µ€Ñ¼°ÂÝ©ƒH­ÂÐáú*ü‘¯¥Î!¥'Ï®piêR+±ÀPà$`IêR ¾Ñ×Ï)©sH­ÆPó+Ô6ìÅ›ü(u©™bàÌžïO�CjEîP W”Ùiw¸øËÔY¤&¸`Ã^Ì›{*#©ƒH­ÈP0›J@?ðšÔY¤F‰ðã]wá˜NcCê,R«ò@ÁQaýªU¼‘ÀRg‘!Àé÷â žü¥s  "„Áù|6D>›:‹T/!rJÏb> ¦Î"µ: @ÁUËœák@wê,Ò ùpïb¾ž:ˆÔ.,¢:�W8;Â>©³H“pG–± gW¥"µ׈Òb~>‡—§Î"MD„ŸÕF9Ü“¿4qÎè1ËNdÚN«ùç8¶}°åP-+Àz"ŸéYÌ¿y½_š €ž``G„À÷�ç§Î"m-Â/s8ÑGúJScÐ6Ýòfvæß"¼ÿž¨5<àäž ß÷S¿4uþ‡];44Ÿ—å‘o³RgQa�øvm„ÏÍ9�•©ÃH� 'U-3=|4DþØ5uFœ3þ©o¿MFê4�[µÌÓcàÿ„ÈßEèJ�G�+Â�3øT©Âõ©³H�Ê  [^¦/Àç¼ÿ©~†‰œM_í]Ä ©ÃH�ÎÿxkÒ–—éËŸ!2ÿ.i’¼!Â�—¦Î£äþd‘3z*\î=üRk°¨¡VËsk9 ˆœœ:�šfˆÈ…!pQO‰+ÂBòÔ�$=ž@MS-óÂx‘x‰ Ó<áW\j\Øs.·§$iÇ,jºap/ e"¯Â™�v´¸,D.Í#—®È¸n~…ZêP’ÆÏ äªež�Ã+3xe„WOM�I�“7×F¸:ƒK{J,wZ_joµ”¸�lp€C³À+óÀ_fpx„=Rç*е7¸6‡k²Èµ]Ã\ð<’:›¤ú²¨¥E7.àÀ¼Æá�08Ø9u¶6·¸‰ÀM!r3‘yä¦|gVÌ9�u©ÃIj< €ÚÎÒ#é~êSè¡‹¹Yäà8(ŸÓRçk!wwÄÀín ‘µœ›ûúø½SøR±YÔ1–I÷¾Oá€Zà  Š�Dø“ÏdìÇž©3ÖÉ0p?° ¸�À½ÀÀ!Ž�ðGgr»Ÿä%íˆ@…qE™�vËxf–óŒö ‘g„À3óÀSCd·{Ø#ÀîvgìÇ. Š[IÿP€G"<‹Õ Ê$©@,Òô—é:x»Çö ùØÿ_B7!æO˜Mص;0 �cŸ¼»rò<´ùÓrÙ‰¼k„‘R…µÍû]H’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$iþ?q¾"œu7?IEND®B`‚jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/jollygood64.png000066400000000000000000000115051523150047600237740ustar00rootroot00000000000000‰PNG  IHDR@@ªiqÞ �zTXtRaw profile type exifxÚµ™ivú …ÿs]ça9Ïéºü~ eÇvæ¼6N,Y’I¸¸`ÔüÏ¿—ú?Þ«|H9–5?¾øb+'YŸŸs4Úï÷ýã¯[|~º®î7,—Gw>Æy=_¹Þ¾�®/˜ö|]¥~�“¯�®·�Ìl9—‘×@Ξëæú¬Š='5>,çú«ËÔëKç™—Ï>áŒÏYe§3Nïw{rXካ¼Þ­3V®„}E®kßûOÝ]÷�ïg/þÓýºîÞÜqº-+¾øéºnÂÇþÛ^z´ÈØûÌöÑ"ó`Ú«ÿÖy­yVW}T¸+>|Ç܆áÁ†;ÝþZ䕸 œ§ý*¼²®ºƒÚ`©M鯇b,_Æ›aªYfîc7½�6q´¶ãw¹–]²Åö Š——Y6)p.ƒM9Çe{·Åìy‹ÌÇd™™‡áIk ŒŸ_êõÂ__O­%anŒÎw_a—•øÂ ANÞy @̺|¶�:ýú#À: ÛÍ™VÝÎ-˜·Ørg§ƒâQ¥´Iã1wÀ¢Û l�NÖ&cðcŸŠåÖyÛ@ÀìÀJë�‹€“­ÌÍw’ÙÏÚ`Ïeè ‚‹. ‰XÞÉ·LU\ð!„RÈ¡„]ô1ÄSžªÉ%ŸBŠ)¥œJªÙeŸCŽ9åœK®Å�UbI%—RjeÒê+cUž¯\h¶¹æ[h±¥–[iµ>Ý÷ÐcO=÷Òë°Ã (@�8ÒÈ£Œ:Í$”¦ŸaÆ™fžeÂË.·ü +®´ò*«ÞQ»P}Fí¹¯Q3jv%Ï¥7Ô¸œÒm#t3³Þ€xh+˜él¼·‚œ`¦‹uʹ`±28Ãb è§±a™;voÈ}Š›Â»¿ÅÍ~„œèþÈ)�î¹÷¸}€Ú¨›nÝH²ŸÂ�Žôã�™«ÍUêÒŸŽê¯_üÙ@-¹î<¹æX«ùµjHÅLù¨u‰©µg_-/_û¬Dvö1ˆ¼ìk0˜zwŒÉ³+‡”Ö &È0½p>{}¾®Þn4ŽàC÷±²[˺æV³Ë¤ÕËÊxÙ¹œ®±© {­÷8]•ýáLðzóáÞp¿|riêmW~žD}5Ëo&Q_Íò›IÔ_—ò:‰úëR^'Q]Êë=õË¥ä3uù%ŽÕW‘ßFH·ïúá‹„¾G8>ˆð¢V¨N�´‡`Æ Ügϳw3õL0É(1—8W3Ö1Š!ÅLÖã9稢Ü&Lb•Äa 5ÆâÞ�V *Ãæw´Wi"7Wf<ÇEnÄí5Å€nñmƵ}E´dYÉM˜lx\9Sµ9øm¦ÒZCºHB_µ$dôùø¢«>þN -Ç{âªçŒn­‰T—KmÞri@›É�Ôí°šŒYÞƒãÙîç҈¹Ö†Ô (@#+RP2·$C¯YÂôð>Ÿ¡Ì9FkỂ3ÛˆÝÅ8š‡ãy¡äÛ~˜åã´2 Î÷4c9Ì©{y+éu-U=¬y…°ô\{jíæp{Ɤ%L-3ÄLƒév M[aÇBACâ†Ñ¸]Kf a¦²ÒYœ0FVž3i)¾8ªïøàHä. ãN(-œÛεfK•˜íž$(‚)ÍPNˆ¼‘a „XÝÑIÐ� —ŠHŽÖ¶^½ËBÁé¬ßäÕæOÍT¿]×>Nƒ›-YÒ0 ’nª^sC$„a%EçH®,vYjÓ ´V)Ý CÛšPǺÎ�®X½9`Ó�Â!IùïžÐ5º2µ0G+Ã,q"Ó+Lr×t “…xŠ(ìÛÙŽ�_�tÜÕ3áÀ:":06ØS|DñVc†0àÄûÉ382D—>Cd¸‘°È¯m%Uú1w¾K� Ã<ŒÕÍ@Ö¸žIOæ¨X'Á.›$v>¼L+*±²“»S‚‡¬dµ›ÝÖVSj´nªüH¬ÁZ8¬ñ+¿´ióœI7ýõQýôA !Ûöd›i7ÏzЬlÃZ¨ÔŒ4:ójðîØïõ½�‰ô@æ.ì?)B¤¥ÁNÌƒÕ e}åöE3ŠªO&Á�ÞÌŒŠœˆ§U2±wZH\ê­¡V"©ÄÞfò�e ¥Ëävg…€½h¦ ÍŒ€IpýUѬ}Ólv=j¶rÓlÍ–ûPMò\ˆ8¤•PéáÖ%Gð[iË Â5œK'«—&>úûz þ¹=NUán¾xõ2>™Ÿ¶ù³uK_öáßN‰ŽÑYCü»FS†šP^8ë)QÅÙnøXJ%ªÝƒ8aý«Ð�#ú‚SâC‘ø2i=:\ÃEÇë6eºC™²7Ú¦‹Q&¤I»¾|5¬i!¨xÔB¦BKôà´†qŽ:ù£jv³$ΖöBz#O3“èÊÈZò˜ÖKK'/ôSGÿ"аè)Š4 úE†�ìm2�©„ nSÝyÝ⎪ë�_ÿ‰·_Žê©Pí2ëH¡z©Râò(û/á¸üµN©O Õ[�’Ã'$ÇL¸;À·ø(´"íjÂ*R Pˆ5š¾V™mü•«PÛËöýqOfãd"õ)�¢…JŒpÔU.L¢\¸¶+qo^PBßEÑwS— ¹Wƒ-H´E/Ò®ãXKÙMI‚"9¸„4õ‚¤C‰ Æ9Ézî~í©ˆï .¨¼Ôðíâ.‰Þ(‹©�ì¦\$CÐUÙ¥´Íz³õyØD·1?Ü6ê,ÚáQÁ|ïqˆ<" ³œmÊci±3JÁÊ_·“ºNÉ»ÉÞŽ—€ëzKe‰Å›h"y´‰@ŠÙL7Å@B1=öIhÕ`(VôÌÆ–ÀÔ×�5„sDªX§Ò^Ó²þêþžÿêE°JEøù’ûö´ú®¡£.‹Ñ d\ÉÉJH:“Ä3zä‰[g $(Ñ �ž&ÙpLØàu›§� ¹aH„\n_­ ÃmŸ}„jC�%H6Þ3ÄÍ@_¶­3®÷.Û[ºS7ŠÁ6¢Ò裕‡PQ߯ŠÙjöÄ�á÷ »ÔûÕº›êÆ ˆØh¾¬e_בW5ByÅsÔŒìÏÞî/4ÈYצ÷Êû´²sßF´ùØ^] ?ŠÞ>öúÕÛ…´7¤þ§wõ?îº�v»î�Üw¯V�`Ãîje æé! 1×›”–5dïΊȲÙWBEÊ÷•\R¾s{+ß;·”Ôï�\»ÊV{âkf7q@#ÁÐX4&¡f<.I ¿üŽ«)lC#ØÇ§ ÒôtBFºÜŽž£ˆIR7i¼ã´”o@¨³ �¤ž >!áA¤؈Wœ0Ïfü ¨Ï=Vº¤(vØÐ$N…qÚÜâ=’‡5#«‰[•"cF‰Ò!ÿøš‘[=ÏÓãüPB�B#‹òÈùj¯ýŽ#è¢lI2bJñ*ÙC÷þíÎè7BêC5~ST|udLóGÊDÊÕN;±_¥äpÏn‚ .÷RE2%¦K�E$Ê®‘ß›,Ž’cò_²#YÎ\CeCC´Dá#”dJ’q¸]òQ ùEy ƒ´(¾«�Ê`Nk‰yµ‡»F#R߯{�Š·³<„]ËÈ<Ó,Újl•–»êÑÓx¸Ö¤¾•¯˜¯}þ®¤ÈÞˆÛ¢_Ó숦þÊÞb>ûŽh¦\-�©Š¬�6Î�†‘t¾ËY¢ D°º¸­´Õ07ÒOjŒ�³7ò‹âHho‰A:F·6“;´êJážÎìŽÊß yö¿1H8Jf´¶àèt’'~˜<êóìù]ò¨Ï³çۈĈ¢-Í¿¡’mDùw0ػȺޝ¦9Ò C5;m½§­—½Ó«ùUg©äÛn:à—þpöPú¨­ƒ�ÖµSxpVµ·^\-=êâ'«Ê\ãllIŠgÙ¤îôw¡­€µ'ŒˆZ8êÀæ8P»?Ï=L&&eC×è%jdI íÐìz¯âÂë¶³`×mgA¼®Í#{:v}å·ZK¤‰³ÎHû6WCv/F¡Ýâ ÇB¡d'+Ʋ!,÷]!{ªcˆÑ¶÷18ž}L—­ìuòÛÎVç•ÅÞ¹ÿù†æÿm ';Ÿê¿.ÀZYætL…iCCPICC profile(‘}‘=HÃ@†ß¦Š¢+ˆ8d¨�,ˆŠ8jŠP!Ô ­:˜\úM’GÁµààÏbÕÁÅYWWAüqquRt‘¿K -b<¸»‡÷¾÷åî;@¨—™fuŒšn›©D\ÌdWÅ®WôbaZ£2³Œ9IJÂw|Ý#À÷»Ïò¯ûsô©9‹‘x–¦M¼A<½iœ÷‰Ã¬(«ÄçÄc&]�ø‘ëŠÇoœ . <3l¦SóÄab±ÐÆJ³¢©OGTM§|!ã±Êy‹³V®²æ=ù C9}e™ë4G�À"– A„‚*J(ÃFŒv� ):�ûø‡]¿D.…\%0r,  ²ëÿƒß½µò“^R(t¾8ÎÇ(е 4jŽó}ì8� ø \é-¥Ì|’^ki‘# ¸¸niÊp¹ =²)»R�¦�ÏïgôMY`àèYóúÖ<Çé�¦^%o€ƒC Z ìuŸww·÷íßšfÿ~d%r¡™ÉèjbKGDùC» pHYs5^5^ "¢wtIMEã 8—<¦IDATxÚíÛ[¨TUðßÌ1=&X©¡™i™[…ìŇ"ÌCEvÙ– …Y=ˆ„!IAÚCE7ëAÍ-Dšv# /]ÉæhvÕHSI…R�·ãîa¶rœfΙÑs³§ùÃfXk}k1ÿÿ^·ï[kSGuÔQGÿ[dΦrn2bƒÐ1=’ß~hÀ‰$¯!©£5y²8†ÝÉÓŠ�Ø•¤wQðÕ�4žk4Æ`†c®ê†—ôþH~7áÛV¾9Ñ¥äBƒqÆáZô©¢ž{kñ1–‘m�"À÷“õÈĦâ>ŒMÑPþs‚Ȇ3 jÂó¸<ÅsÚƒA䥊È…f⹘ԋõ–—_!› ÝP#ä¡QưJ*d1³Æ–ö>• г†ÈÇø¹RzÔ�Ë‚È?• pQ��ß�Y•Vê�Ã5"ÀÜ ²«XÁ–;i��w⸣Þö€/k€ü^¼~Ú¦.$š� ½Ùz±¿³ væB} {À§x å, "-Ðê3 ah�Ý Æ¾Sdø(æhÊWƒ•͡Ƙ{cCÿvl§ãëSC`Dd?ÞKyç½Å: 3r¡I…[á‰XYCËá_X”¸ÏqâÑNkSþ'†‘ƒ'÷`.M½/ÀÓxæäœ�`Q.ô^LÒ—$Kæ¼¶Þàì¤rZ± ·–r‰×ÜLßFëÚ¸ù{10ÛÆæµï `|{ñ€ëò³Ü¢6Y¢é”Ad/–¦�ü!ÜD6—aûEAº)[�ñr ØD>,Ó¶¡ =æ4‚ÈÆî ©Àöú‚t¯l£×R&ÀÖrŒšC�˜Q�}¬˜K’I%-øµÌ@Á\ )ÈÞþ‚ȼ]K= šŽG‹½“-QgIZØ=¬¹dtd¹Ð,¼Z¤øg¼U4Ôʪ†¼ÇÔ·Êùõ{%ßz,Ä]EŠ[pw9R´ŒŽÄX�‚ð{ òMØ\‚ü¡dǸþd< V”h šðGñáX€›JØoÆ” ²©m@¤Ö¦e� �Ä#˜ÚŽí<^à$•>ÝÒšw0úW±«“ÿ7º›-¸§”Ÿ�-aÈŸËo¨ò0¡ò-xcÚs’::Ø€[Rè¬ÂÌ òcG†ÙÊ×¥Ð/˜ôÆË!ßaˆYŸá¸ê?=jÁ“žQ0Éu„¯ÈäBËÇÔªßàÎ ªìL°Ü!@þJµâšÎ”|¹,¯b>"{Φ�H&“¯ª�üL9ÛFÊ�Ü^Á5UDþ³ù‘§wBCå q>šú[•�ß�i‹;©±²9*ØX Þßø ²]w �Ìï:=®ÞÝøcƒ¨t¤KHp?Öt3ñ}x0“ó;;»ñŠ/K7‡z%ƇѫQæ¡G÷Ðf‡x…üIËù]H~~™Ý•êfÏ´bÙŠ&ŠßËé,ìy°kÉŸ•‰ëä¯Ðwfù'Lž÷µéÃVtýøÊtVC¹Ð•¸·ã² «'Wé}|ÔÕIt‰bô—ÿ˜b.ÀÅ8Gþø}�|dv'vÄ›F.ëœ�ꨣŽ:ꨣ2ü ºÞ4è¡ZCIEND®B`‚jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/icons/jollygood96.png000066400000000000000000000152361523150047600240060ustar00rootroot00000000000000‰PNG  IHDR``â˜w8àzTXtRaw profile type exifxÚ­šYvܺ’Eÿ1Šú†ƒv­šA ¿vLuÖµ}õž$+SL¢9 h³þï·ù¾bMÞÄ$%ל-_±Æêoн_÷ÕÙx~Ÿ¯ü|ÄßŸŽ›·<‡¯á¹`=ç7ާ÷ $>ÇûçãFÆ3Nyz>x ôΞ7ó™ä3Pð÷¸{þ6Õß7-XÎó¯mמ‹î9_þŽB0fb¼à�_Á{~û{R`¡†ÆoÇoœ×#鹿ë¯ñ3o¡û&€oï¾ÄÏŽçxx‡ù”Šü%NÏq—¾�߉ÒÇ9ÿvgÿqFa;o?~}ˆßÞ³ì½îêZ̆pågQ¯¥œwœØ g8—e¾…‰÷r¾+ßÅ6;ÈÚd©ÝØÎÕy"¾]tÓ5·Ý:¯Ã ¦ýò«÷ƒ¸ë±ÄW?NR¢~»íÅ�Ÿ ¹d.pØ¿ÍÅ�ûV½7+Üy:ÎôŽÁÈñçoóõÀO¿? ´·–¹s¶¼ÅŠyy 8ÓÐÌéoÎ"!n?1M'¾ÎÜûõKÈ`:a.,°Ù~‡èɽ×V8y6N�öö‹“ù @ˆ¸wb2Twt6SÒ.;+Þ‹sı�ŸÆÌ}ˆ¾“—Lò“YúB&9Åë½¹FÜ9×'/$"…„ÔÐ@$+Æ3ýV(¡fRH1¥”“¤’jj9ä˜SÎY²âT“ Q’d)R¥•PbI%)¥ÔÒª¯K¦æ*µÔZ[ã¦-6Æjœß8Ð}=öÔs—^zímP>#Ž4ò�QFmú&`fž2ˬ³-·(¥WZyÉ*«.pcûvÜiç-»ìºÛ[Öž¬~ÎÚ×Ìý>kîÉš?‰Òóä=ky áN’挌ùèȸh(h¯9³ÅÅè5sš3[}0!$Ï,“&g:ÍŒËù´Ý[îÞ3÷�y3D÷ßæÍ—9£©ûodÎhê>dî×¼}“µÙ܆“ íBbj�±ºWi¾4好šŸ^øwõ¼£lÛçZ��o ™8ôJ–LD(Ô´a·“3ïâ c!U5.~Úè5۶ƈ ÙˆÉá×�’¾«»t€«¯I}–ÚÂ^DŠ‘MXýè{mæ±—Ï�xõYôJDfš7éY)—¶Z®ür!ÁæÚò‡»¾ ÌBìˆzŽ�šó½³€•¬íj' Mb93¯]…E­�×Ù~I¨c&’;EVª÷™:w’3…Ú†i©¶ìfèi̲0?Nδçv`�SÏj|?«¡žXMºa’,ŸÇnv.i¯1{‰°ÌM�‡N¶â޳õ2rp»Ñ‡cl«ç°ÎFë´tFœD§™ßÄïïº'pgAýYД·è™œžø½¢§ùþÝÅá÷Y#,£Nû%Ý”çꡌ?Ö·y?ÐI]®‰\;‰3o2ètBü}´#G»È$ J³ÑÁè�í]ß=Óf�5'->kys·çJC“ÞVn» x‰×'5ß\ñmŒâ‰0É(Óƒ©õn ÉRª›9ÕH¢£·ºenIÓ¬º†„'.Ÿ‚•ÙOý@›«lØôZÒ‡OXûØ’ëŠ3’ò's�C|¿ƒÕt“~#)w°ž¨}S(¾æ©ÅÉí©ŽÅlbPGëf±;¶iIhÉ-ûÖS,ÛëÍ£ÈD%×Pþ+—˜Ç“•a2ì¼·ÿòÕ“€þ¶äî@'Ì"aÁ‘Ñı+oÁëÈq‡@ı4“W®cÌÊ È7w«Ók]‚:vô¼æ_"uZiÒ›h>ä Q »‡˜Wµ¦5;6žËµT> LÙ8…&ŸQ1˜¯�$rÿçÒ‰\s@sB‰E…0·’zÐ"GÓÊMM«â…¦¢©Õ¦•°�6! Üf®ë6Ía³ÜMGЉx˜L ¶­Ü†ä&‹h”5J²7(éNó |€J“Úî ¿K‡D[{6)8Zä è¡miW!¼ðq+üÁž‚.ÇÏá#‡ÔJV6s5Ià“j´V‡'F ‡« ó€íp‹¥�Rèš9Ž`I…ðÅ£/s;ª¾�iMkv«ƒã0-ÎŽkØTÅýE á*I�Ñ©Îfl@Íÿ¸}êÏ´¡*}õÕ”í£èNó�LWEN£uUvÜ­œ‘!ššÐ0�sinêÙØ[Ñj05FMU”›óBcÁ`½ w%\™Ú µPÿ5¦ehÀ‹RB‡ìí°º}¨ AÙQEvPy}UÈè&xáÈŠŠÄ=#G^PEèDAVêÒE4%àB„'YµÙÛux[TT.Ý¢Ôáö3ܼÃOðˆågªjr쎨Íñq!ÖÃÑhô¬ÕK¼A̓æ×6œDšx}Ã�sõ §xӢˠv¦Ö¨N(Zë.Ö±2ÐJTÀédÊ‚•˜;ó b[á[FK‡*€4)¨¸f®8 €ÍŠ<–¤ž’àM}«Gi(šõHÈ£­™Ï�U­éíVŒ^(¹/ü+·\Âηèµ ô/ÄÚT±µGkJ½ÛOÕ¤3ßÊKM4‹†ÒŽ=f”ž¥è/n‰C´�„é¶�HWá#fDÌkžxº4÷QåWå�IŒ¨Q˜y”Wõ¤‰[ô ¦d<'ÂqÙ¢ÙwýÉŽ™Ç�á—sÎjÀ·�C”`¡³¤ Úà9«âÙÂE!+ýØ h•ºÈY œ6ê¬H˜]â¸ä£†E­+VÓ¥>RK!68 ÆÙmE‚O:ÃüVh4e ·ü ;-¼ƒ‡sî….h±rʪ¸G'Ðhbâ™b¡m÷H{‚òi �*¼ -9´¾ä ŸÌW€úŸV‰P?ÍêžfÝþ6+©¡×0ó& >=è ­Ð‡�ÔíŽe‰ôn îÓD¨�ކÑÇE¿¼šúàûWÌ:B$w~Á-fn:óZO/½z6-0¨XËL¨´y·(¤{º‡‘}t�ÚšÊ"TLů£vÑ6Ur(ÁòªP‘¥�ÞïzÒ@RQøšÚ½¯ÒsQO®¡C®Õ“[û8áÓîX<9FæåÉQïwßL-yy³ä„ñXr¨Ý_æV 3$êIO§a¥Lxr6g«Sc¾;º›dP7DGÍa¦sǪz®õ"(ìHú£�¤ïH}©GÐ�Ih¨¬,Ž×�Ky<.¨_’yá9k;ˆÎû‹èÐÚTzøK¾5—pÛÊÀ¥G×:_*%æÉbÚi„Xq$à^Ÿ·¦®£}. ó÷5ôÍkŠ�…Ïl�î�<³­®Ž_�¨øÔ ¨‰° ¸¶(¿0êì;¨<œ¶™  êìv6¾BSr6¾Œ2õñÌÝ+F›ÍzØBw&õãÐ…×­ ¢�«{BE¨9?¦¥z¬<çßýçs~:ûÏàÞ¨gHÁ«ÀÒÕÈÄ0PÁ}á¬\Ȭ´Ö×Ç�(á7«hþÞ+ª>S¯è‰ÓØjÙe‡„ªeF«#ΔZvZné–…ö  ’D› rC³Ü^Ý7$¾€Åü(ùŒû5“æ»T¾gReuV¬í)r½„RN±[Õšú,ÀÉH ŒŠ=šé�¿×ìèÃ!¯�«ÈÓxà¶ÔSд½—+r —ånD&H�T3ú{ïý‘á/ ëCš_¤‡ù^{ Z­¥ áøˆ;&kÒ�V7jußN&¥xÁ_‚¤7@ rBÏx×›îÒÃI¯þ.´ ÝÆVµþì'!ƒ?Rµ HAg*×�H× �B’ã„LD‚b°6�×�MN—c€ÈÔTfÁŽ”?fKŒUº¡xe�—á,/>_ÄÓYæ•®9cÌ€êt ‘|»`Wìˆ÷µØ·‹ Sa!ªx ¯'é`@µb>Šý°ö»Æ@iðþŠp_¤{Òì0 z:LÄ4|ÃFmP?þwâÖ(sOç½£j@µb<¾€ãЧ½�yWãŠ$õʈP­Ø§àE¥x›—pÒ%Jg.n²÷Ìv›Ø7`§ õ ›¤nlšÝÓ‰2§à ÄAß!Yºœ­¦4¡ÕC”9WñK³å6iFBqµb{ü hZ8¬)àËšäfšÐº‚– ñD³ x&hY7od‘¿7Ë€ AϺ™Û5[O³ Ø+èY/ãìf%k�q�frZ’ZÜL^š–æü¼™ n$ ÿTжç 3XP!cì�΢¹:wU�;ö¸m`þ‚3‚¶C²`<÷ tâÐ�¸`OïÇñGcb¿KŽÙhœ]äA u øcж3?Ðïéï®±/¾ŽCÕʉäá× ȼ oÓNÊ[Ñäê4DÀ¹ÊÇJv¨åŒjÅY¸0h\“%Ij‡êáˆM-´úH�i¼†ÉIºv.X�efz{ÖA`]æU+&‹ý÷7 >l†›L[{ )†$õî :×d*æã€a¦³W9s ×�k²Í çæãTì,g‘”ñ÷ pý·ŠÀ[­ ÕüJ|Þ6è]ŠE8cUìö=f­sîÉ;tÚÄ-Ü_TÞkÆÞ7ÅwðýN¨Vœ�s‚¶Cr-NOR+»èø�ùÚ‡]†Óû�Z…“Ô³ýÛ­W32ó_ÆNìípâPâÃõÿßÅô¿Nê_¸hžçðË sí‡ú{ ]»û-å¿�¤–â®NS�ÞÏ€›–€�ä’,ò“ŸUúîï8¶½ÌÞñ=³ßãñ ÷Z\'vvWãsEk…/?±Ž�ùxÇUAóµ¸8™5`“²,ãjŸTkðhfÑu�m¾Ì¿†™Æ¤Ç'Æ5*ŽešxØ€yfâÝÃNãàÇÇǃ–{ /üX­ØSíÓžšôÆ‘/Xhw6üÅŠH>å³ÏÖ4`÷¼{}Cп±au½|[°©Å߆šŒ{ Í™ÿÒv9 r”<`3sã!zq¯àö67 ®)ˆGïHµâHüÔàKÀîy²Ìœ ›qx›Š¿\¯%e/.ÖYÌÀ¥C\Ú‡³’Ùå&eÝ%_ÞÙŽ3¨&sK·v6•¹G”¸ü¼$5Ÿ 2ŠQ¿»Û4<5Ô—îGµb?ùüª2âÿ"îsÞêÊ®ˆùuè øÔoùéíüPn]Ý�³Ì—v»õíø{Ùy¡íšÕhbÆ"'ÈÇúß]"�˜quÒo«„RôuX÷zZ¾ý@;±Vüd…>/oßïZ2�‡qT’Üš*UMÊjÃðbQÔŒ«VÛ“wÊn.)þJœ›±o-ñë)‚à!|±Í ˜R­˜��|fDæã˜$Õ=Ô…õ0¯ s@½ÑÁEñtY’–‹­—6 ê5?ëð&Æ Ôj)ž’¤õÅJ/ÌO¾@!°nE=½/vP½â×[­.†öšƒ7q9ÎORËM¤ù,Âýg:�†í¾˜ã œ&vO2«9 Öe@”YšEªòýáÚ‰\€‹’´¹ñ‘FöŒû¾öZSöÝ{އ_��lOóÛ6?ÃAI:2â7d@”Ð.s†º—nêÑ‘¼AÝt¦VâŽ61àî©7Žì Ý!ë¦1.ümøj4 k%Ú9·z„XŸÅòµ¯c�åòÅ/ŒÆÍÊÅDÕ+ÆèÓáh‰?œ"®ÁÒ1&þŸ#.Í6l@1�÷¼1$þ£ø\gjÕa@Ñ3¾�ŒñïÃÔ$õ¿Ñ¾ñ° 蜣G׆ʕ&©W×Ç͇½Qk’Z€c7@áŸÇ!§v�r±3ìfh&ࡊŠŠ¹Õwß]%5ž»¾žú5iÊæ£+Ñ·Êü)ÛzŸÄæ-lÀqIêâ«÷f+ü˜¦¿C¦Z±¾‰3Õ^œ¶¾x,fòné°ܵ¶°Y̼Ã�¬µÖ/ÇÞIªÚJOĈ”ׯõñZ�«ä/�h^Çg[MüË«Y0�,RÁõò×S­Ç‘Iê±V¬�FåEnÅþ8}†Ñj%ýg‘kºf[Ùª-‚Q{“^÷D}vÆÉò—�ľD/Êwþš…»ÊÎNk Öê÷ÂÖãí#Ÿc4E¾Áé.u$Ñ+_<±ÿ��ã<Ǫ»Í²AÑï’,v›�äoÞÛ¦(¦Þ‰wÝŒW‹Ï<Õy£�³�@ �@ PÿÇ-ìž#3á¡IEND®B`‚jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/jollygood.6000066400000000000000000000326531523150047600220770ustar00rootroot00000000000000.\" jollygood.6 .\" Copyright (c) 2022-2026 Rupert Carmichael .\" All rights reserved. .\" .\" Redistribution and use of this script, with or without modification, is .\" permitted provided that the following conditions are met: .\" .\" 1. Redistributions of this script must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED .\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO .\" EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, .\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, .\" PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; .\" OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, .\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF .\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd July 2, 2026 .Dt JOLLYGOOD 6 .Os .Sh NAME .Nm jollygood .Nd The Jolly Good Reference Frontend .Sh SYNOPSIS .Nm .Op Fl abcfghorsvwx .Op Fl a Ar value .Op Fl b Ar frames .Op Fl c Ar corename .Op Fl e Ar sysname .Op Fl g Ar value .Op Fl o Ar file .Op Fl r Ar value .Op Fl s Ar value .Op Fl x Ar value .Op Ar .Sh DESCRIPTION The Jolly Good Reference Frontend is the simplest possible frontend for The Jolly Good API. It is intended to remain this way, and to avoid unnecessary feature creep, ensuring that it is easy to learn from. In order to deliver a smooth and enjoyable experience, a few slightly more advanced features are required. In these instances, effort has been taken to ensure readability and understandability of the code that implements these concepts. .Pp The options are as follows: .Bl -tag -width indent .It Fl a , -video Ar value Specify which Video API to use. .D1 0 = OpenGL Core Profile .D1 1 = OpenGL ES .D1 2 = OpenGL Compatibility Profile .D1 3 = Vulkan .It Fl b , -bmark Ar frames Run N frames in Benchmark mode. .It Fl c , -core Ar corename Specify which core to use. .It Fl e , -sys Ar sysname Specify which system to emulate. .It Fl f , -fullscreen Start in Fullscreen mode. .It Fl g , -cheatauto Ar value Auto-activate cheats if present. .D1 0 = Disabled .D1 1 = Enabled .It Fl h , -help Show Usage. .It Fl o , -wave Ar file .It Fl r , -rsqual Ar value Resampler Quality (0 to 10). .It Fl s , -shader Ar value Select a Post-processing shader. .D1 0 = Nearest Neighbour (None) .D1 1 = Linear .D1 2 = Sharp Bilinear .D1 3 = Anti-Aliased Nearest Neighbour .D1 4 = CRT-Yee64 .D1 5 = CRTea .D1 6 = LCD .It Fl v , -verbose Enable verbose log output for core and frontend. .It Fl w , -window Start in Windowed mode. .It Fl x , -scale Ar value Video Scale Factor (1 to 8). .El .Ss CONFIGURATION FILES Configuration files are located in .Pa $XDG_CONFIG_HOME/jollygood/ . .Pp The following filenames are used: .Pp .Bl -tag -width -offset indent -compact .It Pa settings.ini General settings. .It Pa corename_input.ini Input definitions for a specific emulator core. .It Pa corename.ini Core-specific overrides to general settings. .El .Pp User data is located in .Pa $XDG_DATA_HOME/jollygood/ . .Pp The User data directory contains the following subdirectories: .Pp .Bl -tag -width -offset indent -compact .It Pa assets/corename/ User assets such as sample data for Nestopia JG. .It Pa bios/ Directory the frontend will load BIOS images from. .It Pa cheats/corename/ Directory the frontend will load Cheat files from. .It Pa save/corename/ SRAM, Memory Card, or other types of save data. .It Pa screenshots/ Screenshots with a timestamp and random number. .It Pa state/corename/ Save States. .El .Pp Cores may be used locally by placing their build output directories into a .Pa cores/ directory created in the jgrf source directory. .Bd -literal -offset inden $ mkdir cores $ cp -r /path/to/core/ cores/ .Ed .Pp Cores that exist locally will be preferred over cores installed system-wide. .Ss INPUT Frontend Key Definitions: .Pp .Bl -tag -width -offset indent -compact .It Cm ESC Quit .It Cm TAB Open Menu .It Cm ` Fast-forward .It Cm F1 Soft Reset .It Cm F2 Hard Reset .It Cm F5 Save State (Slot 0) .It Cm F6 Save State (Slot 1) .It Cm F7 Load State (Slot 0) .It Cm F8 Load State (Slot 1) .It Cm F9 Screenshot .It Cm F12 Toggle Cheats On/Off .It Cm F Toggle Fullscreen/Windowed Mode .It Cm M Toggle Audio Playback (Mute/Unmute) .It Cm Shift+TAB Toggle Hotkey Processing (Use with emulated keyboards) .It Cm Shift Ns + Ns Aq Cm Port Number Configure Input .El .Pp Menu Key Definitions: .Pp .Bl -tag -width -offset indent -compact .It Cm ESC Exit Menu .It Cm TAB/LEFT Back .It Cm ENTER/RIGHT Activate selected menu item .It Cm UP/DOWN Navigate Up and down .El .Pp Input for games is not preconfigured. For example use .Cm Shift Ns + Ns Cm 1 for the first controller port and .Cm Shift Ns + Ns Cm 2 for the second controller port. .Pp Effort has been made to ensure emulator cores know what devices must be plugged in. Console output will indicate which emulated device is plugged into which emulated port. .Pp Axis and Button input is considered separate. Digital buttons cannot be assigned to emulated axes. This is a design choice. Use the ESC key to skip defining axes if you do not have a physical input devices with axes. Ideally, buy a budget USB gamepad with analog sticks/triggers if you want to play games that require analog input. The reverse, assigning physical axes to emulated digital buttons, is possible. .Pp Force Feedback data is sent to the physical device that handles the emulated device's axis definitions. Currently, this only exists in PlayStation and Game Boy games. In the case of the Game Boy, there is only a single controller, so force feedback data is sent to this controller. .Ss SETTINGS The General Settings configuration file accepts the following sections. .Pp .Bl -tag -width -offset indent -compact .It Ic [audio] .Bl -tag -width -offset indent -compact .It Ic rsqual = Aq Cm 0-10 Resampler Quality .Aq Default: 3 .El .It Ic [video] .Bl -tag -width -offset indent -compact .It Ic api = Aq Cm 0-2 Video Driver Profile .Aq Default: 0 .D1 0 = OpenGL Core Profile .D1 1 = OpenGL ES .D1 2 = OpenGL Compatibility Profile .D1 3 = Vulkan .It Ic scale = Cm N Video Scale Factor .Aq Default: 3 .It Ic shader = Aq Cm 0-6 Post-processing shader .Aq Default: 3 .D1 0 = Nearest Neighbour .D1 1 = Linear .D1 2 = Sharp Bilinear .D1 3 = AANN .D1 4 = CRT-Yee64 .D1 5 = CRTea .D1 6 = LCD .It Ic crtea_mode = Aq Cm 0-4 CRTea custom mode .Aq Default: 2 .D1 0 = Scanlines .D1 1 = Aperture Grille Lite .D1 2 = Aperture Grille .D1 3 = Shadow Mask .D1 4 = Custom .It Ic crtea_masktype = Aq Cm 0-3 CRTea custom mode settings .Aq Default: 2 .D1 0 = Scanlines .D1 1 = Aperture Grille Lite .D1 2 = Aperture Grille .D1 3 = Shadow Mask .It Ic crtea_maskstr = Cm N CRTea mask strength .Aq Default: 5 .It Ic crtea_scantr = Cm N CRTea scanline strength .Aq Default: 6 .It Ic crtea_sharpness = Cm N CRTea sharpness. .Aq Default: 7 .It Ic crtea_curve = Cm N CRTea Screen Curve Strength .Aq Default: 2 .It Ic crtea_corner = Cm N CRtea Corner Size .Aq Default: 3 .It Ic crtea_tcurve = Cm N CRTea Trinitron Curve Strength .Aq Default: 10 .El .It Ic [input] .Bl -tag -width -offset indent -compact .It Ic axis_deadzone = Cm 0-9 Axis Deadzone Size .Aq Default: 5 .El .It Ic [misc] .Bl -tag -width -offset indent -compact .It Ic cheatauto = Aq Cm 0-1 Cheat Auto-activation at boot .Aq Default: 0 .D1 0 = Disabled .D1 1 = Enabled .It Ic corelog = Aq Cm 0-3 Core logging options .Aq Default: 1 .D1 0 = Debug .D1 1 = Info .D1 2 = Warning .D1 3 = Error .It Ic frontendlog = Aq Cm 0-3 Frontend logging options .Aq Default: 1 .D1 0 = Debug .D1 1 = Info .D1 2 = Warning .D1 3 = Error .It Ic textscale = Cm N Text Scale Factor .Aq Default: 2 .El .El .Pp The settings may be overridden in a core specific configuration file. Additionally, you may add a section for Emulator Settings. .Ss CHEATS When a core supports cheats, the cheat file must be named the same as the game, but with a file extension of ".json". The file must be located in the cheats directory for the core, for example: .Pp .D1 Pa ~/.local/share/jollygood/cheats/corename/gamename.json .Pp The file must be formatted as such: .Bd -literal -offset indent {"cheats":[ {"codes":["CHEAT001"], "enabled":true, "desc":"Single-code Cheat"}, {"codes":["CHEAT002"], "enabled":false, "desc":"Disabled Single-code Cheat"}, {"codes":["CHEAT003", "CHEAT004"], "enabled":true, "desc":"Multi-code Cheat"}, // {"codes":["CHEAT005"], "enabled":true, "desc":"Commented Cheat"}, {"codes":["CHEAT006"], "enabled":true, "desc":"Final Cheat has no comma"} ]} .Ed .Pp The format for the cheat codes is different for each system, and some systems support multiple types of codes. All codes are represented as JSON strings. .Sh ENVIRONMENT .Nm supports the following environment variables. .Pp .Bl -tag -width -offset indent -compact .It Ev XDG_CONFIG_HOME The XDG base directory. This is the path is used to find the frontend and core configuration files. Set by default to .Pa $HOME/.config/jollygood/ . .It Ev XDG_DATA_HOME The directory for user-specific data files. This path is used to find data files used by the cores. Set by default to .Pa $HOME/.local/share/jollygood/ . .It JOLLYGOOD_CORE_DIRS A sequence of path prefixes to tell the frontend which directories to search for Jolly Good core modules. If set, these directories take precedence when searching for core modules to load. Include a colon (:) separator between the path names on a UNIX system, and use the semicolon (;) separator between path names on a Windows system. .It JOLLYGOOD_ASSET_DIRS A sequence of path prefixes to tell the frontend which directories to search for asset-containing subdirectories. If set, these directories take precedence when searching for the location of core assets. Include a colon (:) separator between the path names on a UNIX system, and use the semicolon (;) separator between path names on a Windows system. .El .Sh EXAMPLES .Bl -tag -width indent .It Nm Print the frontend's help and exit. .It Nm Ar file Run the rom file from the command line. .It Nm Fl c Cm mednafen Ar rom.sfc Run a Super Nintendo rom using the Faust emulator included in the Mednafen core instead of the default BSNES core. .It Nm Fl c Cm bsnes Qo Ar Super Game Boy (World) (Rev 2).sfc Qc Ar rom.gb Run a Game Boy rom using the BSNES core and an auxiliary Super Game Boy Rom. .It Nm Fl c Cm sameboy Ar rom1.gb Ar rom2.gb Run a multiplayer linked game with two Game Boy roms using the SameBoy core. .It Nm Fl a Cm 2 Ar file Run the frontend in a OpenGL Compatibility Profile. .It Cm time Nm Fl b Cm 1000 Ar file Run the frontend for .Cm 1000 frames in Benchmark mode. .It Nm Fl f Ar file Run the frontend in Fullscreen mode. .It Nm Fl o Pa rom.wav Ar file Output the rom audio to .Pa rom.wav . .It Nm Fl r Cm 3 Ar file Run the frontend with Resampler Quality set to .Cm 3 . .It Nm Fl s Cm 5 Ar file Run the frontend using the CRTea shader. .It Nm Fl x Cm 3 Ar Run the frontend wth a Video Scale Factor of .Cm 3 . .El .Pp Example .Pa settings.ini configuration file for the .Nm frontend. .Bd -literal -offset indent [audio] ;N = Resampler Quality (0-10) rsqual = 3 [video] ;0 = OpenGL Core Profile, 1 = OpenGL ES, 2 = OpenGL Compatibility Profile ;3 = Vulkan api = 0 ;0 = Disable, 1 = Enable fullscreen = 0 ;N = Scale video output by N (1-8) scale = 3 ;0 = Nearest Neighbour, 1 = Linear, 2 = Sharp Bilinear, 3 = AANN, ;4 = CRT-Yee64, 5 = CRTea, 6 = LCD shader = 3 ; Settings for CRTea ;0 = Scanlines, 1 = Aperture Grille Lite, 2 = Aperture Grille, ;3 = Shadow Mask, 4 = Custom crtea_mode = 2 ; Settings for CRTea Custom Mode ;0 = Scanlines, 1 = Aperture Grille Lite, 2 = Aperture Grille, 3 = Shadow Mask crtea_masktype = 2 ;N = Mask Strength crtea_maskstr = 5 ;N = Scanline Strength crtea_scantr = 6 ;N = Sharpness crtea_sharpness = 7 ;N = CRT Screen Curve Strength crtea_curve = 2 ;N = CRT Corner Size crtea_corner = 3 ;N = Trinitron Curve Strength crtea_tcurve = 10 [misc] ;0 = Disabled, 1 = Enabled cheatauto = 0 ;0 = Debug, 1 = Info, 2 = Warning, 3 = Error corelog = 1 ;0 = Debug, 1 = Info, 2 = Warning, 3 = Error frontendlog = 1 ;N = Text Scale textscale = 2 .Ed .Pp Example .Pa jollycv.ini configuration file for the JollyCV core. .Bd -literal -offset indent [jollycv] ;0 = Show Overscan, 1 = Mask Overscan mask_overscan = 0 ;0 = TeaTime, 1 = SYoung palette = 0 ;N = Resampler Quality (0-10) rsqual = 3 ;0 = NTSC, 1 = PAL region = 0 .Ed .Pp Example .Pa input_snes.ini configuration file for all Super Nintendo emulators. .Bd -literal -offset indent [snespad1] Up = j0a1- Down = j0a1+ Left = j0a0- Right = j0a0+ Select = j0b4 Start = j0b5 A = j0b1 B = j0b2 X = j0b0 Y = j0b3 L = j0b6 R = j0b7 .Ed .Pp Example .Pa cheats/bsnes/Super Castlevania IV (USA).json cheat file for the .Pa Super Castlevania IV (USA).sfc rom and the BSNES core. .Bd -literal -offset indent {"cheats":[ {"codes":["DD24-AFD7"], "enabled":true, "desc":"Fully Powered Up Whip With First Power Up"}, // {"codes":["bbb3-d40f"], "enabled":true, "desc":"99 Hearts"} // {"codes":["0094EAFF"], "enabled":true, "desc":"1st heart = 99 Hearts"}, {"codes":["028005=80"], "enabled":true, "desc":"Stop Timer"} ]} .Ed .Sh AUTHORS .An -nosplit The Jolly Good API and Reference Frontend were written by .An Rupert Carmichael . .Sh BUGS .Lk https://gitlab.com/jgemu/jgrf/-/issues "Issue tracker" jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/jollygood.desktop000066400000000000000000000012561523150047600233760ustar00rootroot00000000000000# https://www.freedesktop.org/wiki/Specifications/desktop-entry-spec/ [Desktop Entry] Type=Application Version=1.0 Name=jollygood GenericName=Frontend for The Jolly Good API NoDisplay=true Comment=The Jolly Good Reference Frontend Icon=jollygood TryExec=jollygood Exec=jollygood %F Terminal=false MimeType=application/x-atari-7800-rom;application/x-atari-lynx-rom;application/x-fds-disk;application/x-gameboy-rom;application/x-gamegear-rom;application/x-gba-rom;application/x-genesis-32x-rom;application/x-genesis-rom;application/x-nes-rom;application/x-neo-geo-pocket-rom;application/x-sms-rom; Categories=Game;Emulator; Keywords=emulator; StartupNotify=false StartupWMClass=jollygood jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/mk/000077500000000000000000000000001523150047600204045ustar00rootroot00000000000000jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/mk/common.mk000066400000000000000000000043531523150047600222320ustar00rootroot00000000000000AR ?= ar CC ?= cc CXX ?= c++ CC_FOR_BUILD ?= $(CC) CXX_FOR_BUILD ?= $(CXX) DOXYGEN ?= doxygen GLSLANG ?= glslangValidator PKG_CONFIG ?= pkg-config STRIP ?= strip PREFIX ?= /usr/local EXEC_PREFIX ?= $(PREFIX) BINDIR ?= $(EXEC_PREFIX)/bin LIBDIR ?= $(EXEC_PREFIX)/lib INCLUDEDIR ?= $(PREFIX)/include DATAROOTDIR ?= $(PREFIX)/share DATADIR ?= $(DATAROOTDIR) DOCDIR ?= $(DATAROOTDIR)/doc/$(NAME) HTMLDIR ?= $(DOCDIR) MANDIR ?= $(DATAROOTDIR)/man CFLAGS ?= -O2 CXXFLAGS ?= -O2 HOST = $(shell $(LINKER) -dumpmachine) WARNINGS_DEF := -Wall -Wextra -Wshadow -Wmissing-declarations WARNINGS_DEF_C := $(WARNINGS_DEF) -Wmissing-prototypes -pedantic WARNINGS_DEF_CXX := $(WARNINGS_DEF) -pedantic override LIBS_PRIVATE := Libs.private: override REQUIRES_PRIVATE := Requires.private: override DEPDIR := $(SOURCEDIR)/deps override OBJDIR := objs override PREREQ := $(OBJDIR)/.tag override HOST := $(HOST) ifneq (,$(findstring darwin, $(HOST))) override PLATFORM := Darwin else ifneq (,$(findstring dragonfly, $(HOST))) override PLATFORM := DragonFlyBSD else ifneq (,$(findstring freebsd, $(HOST))) override PLATFORM := FreeBSD else ifneq (,$(findstring haiku, $(HOST))) override PLATFORM := Haiku else ifneq (,$(findstring linux, $(HOST))) override PLATFORM := Linux else ifneq (,$(findstring midipix, $(HOST))) override PLATFORM := Midipix else ifneq (,$(findstring netbsd, $(HOST))) override PLATFORM := NetBSD else ifneq (,$(findstring openbsd, $(HOST))) override PLATFORM := OpenBSD else ifneq (,$(or $(findstring cygwin, $(HOST)), \ $(findstring mingw, $(HOST)), \ $(findstring msvc, $(HOST)), \ $(findstring msys, $(HOST)), \ $(findstring windows, $(HOST)))) override PLATFORM := Windows else override PLATFORM := endif ifeq ($(PLATFORM), Windows) override BIN_EXT := .exe else override BIN_EXT := endif # Info command override COMPILE_INFO = $(info $(subst $(SOURCEDIR)/,,$(1))) override .DEFAULT_GOAL := all override PHONY := all clean install install-docs install-strip uninstall $(OBJDIR)/.tag: @mkdir -p -- $(if $(MKDIRS),$(MKDIRS:%=$(OBJDIR)/%),$(OBJDIR)) @touch $@ install-docs:: all @mkdir -p $(DESTDIR)$(DOCDIR) ifneq ($(DOCS),) for i in $(DOCS); do \ cp $(SOURCEDIR)/$$i \ $(DESTDIR)$(DOCDIR); \ done endif include $(SOURCEDIR)/mk/deps.mk jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/mk/deps.mk000066400000000000000000000030121523150047600216640ustar00rootroot00000000000000CFLAGS_JG = $(shell $(PKG_CONFIG) --cflags jg) CFLAGS_PTHREAD := -pthread PKGCONF_CHDR := libchdr CFLAGS_CHDR = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_CHDR)) LIBS_CHDR = $(shell $(PKG_CONFIG) --libs $(PKGCONF_CHDR)) PKGCONF_EPOXY := epoxy CFLAGS_EPOXY = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_EPOXY)) LIBS_EPOXY = $(shell $(PKG_CONFIG) --libs $(PKGCONF_EPOXY)) PKGCONF_FLAC := flac CFLAGS_FLAC = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_FLAC)) LIBS_FLAC = $(shell $(PKG_CONFIG) --libs $(PKGCONF_FLAC)) PKGCONF_LIBCRYPTO := libcrypto CFLAGS_LIBCRYPTO = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_LIBCRYPTO)) LIBS_LIBCRYPTO = $(shell $(PKG_CONFIG) --libs $(PKGCONF_LIBCRYPTO)) PKGCONF_LZO := lzo2 CFLAGS_LZO = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_LZO)) LIBS_LZO = $(shell $(PKG_CONFIG) --libs $(PKGCONF_LZO)) PKGCONF_SDL3 := sdl3 CFLAGS_SDL3 = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_SDL3)) LIBS_SDL3 = $(shell $(PKG_CONFIG) --libs $(PKGCONF_SDL3)) PKGCONF_VULKAN := vulkan CFLAGS_VULKAN = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_VULKAN)) LIBS_VULKAN = $(shell $(PKG_CONFIG) --libs $(PKGCONF_VULKAN)) PKGCONF_ZLIB := zlib CFLAGS_ZLIB = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_ZLIB)) LIBS_ZLIB = $(shell $(PKG_CONFIG) --libs $(PKGCONF_ZLIB)) PKGCONF_ZSTD := libzstd CFLAGS_ZSTD = $(shell $(PKG_CONFIG) --cflags $(PKGCONF_ZSTD)) LIBS_ZSTD = $(shell $(PKG_CONFIG) --libs $(PKGCONF_ZSTD)) -include $(SOURCEDIR)/mk/core.mk -include $(SOURCEDIR)/mk/miniz.mk -include $(SOURCEDIR)/mk/samplerate.mk -include $(SOURCEDIR)/mk/speexdsp.mk jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/mk/miniz.mk000066400000000000000000000017721523150047600220720ustar00rootroot00000000000000DIR_MINIZ := $(wildcard $(DEPDIR)/miniz) ifneq ($(DIR_MINIZ),) USE_EXTERNAL_MINIZ ?= 0 else override USE_EXTERNAL_MINIZ := 1 endif ifeq ($(USE_EXTERNAL_MINIZ), 0) CFLAGS_MINIZ := -I$(DIR_MINIZ) LIBS_MINIZ := MKDIRS += deps/miniz SRCS_MINIZ := deps/miniz/miniz.c OBJS_MINIZ := $(patsubst %,$(OBJDIR)/%,$(SRCS_MINIZ:.c=.o)) else CFLAGS_MINIZ = $(shell $(PKG_CONFIG) --cflags miniz) LIBS_MINIZ = $(shell $(PKG_CONFIG) --libs miniz) OBJS_MINIZ := endif ifneq (,$(and $(findstring miniz,$(LIBS_REQUIRES)), \ $(filter-out 0,$(USE_EXTERNAL_MINIZ)))) override REQUIRES_PRIVATE += miniz endif ifneq ($(DIR_MINIZ),) FLAGS_MINIZ := -std=c99 $(WARNINGS_DEF_C) ifneq ($(PLATFORM), Windows) FLAGS_MINIZ += -fvisibility=hidden endif BUILD_MINIZ = $(call COMPILE_C, $(FLAGS_MINIZ)) $(OBJDIR)/deps/miniz/%.o: $(DIR_MINIZ)/%.c $(OBJDIR)/.tag $(call COMPILE_INFO,$(BUILD_MINIZ)) @$(BUILD_MINIZ) ifeq ($(USE_EXTERNAL_MINIZ), 0) install-docs:: cp $(DIR_MINIZ)/LICENSE $(DESTDIR)$(DOCDIR)/LICENSE-miniz endif endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/mk/speexdsp.mk000066400000000000000000000022431523150047600225710ustar00rootroot00000000000000DIR_SPEEXDSP := $(wildcard $(DEPDIR)/speex) ifneq ($(DIR_SPEEXDSP),) USE_VENDORED_SPEEXDSP ?= 0 else override USE_VENDORED_SPEEXDSP := 0 endif ifneq ($(USE_VENDORED_SPEEXDSP), 0) CFLAGS_SPEEXDSP := -I$(DEPDIR) LIBS_SPEEXDSP := $(if $(findstring -lm,$(LIBS)),,-lm) MKDIRS += deps/speex SRCS_SPEEXDSP := deps/speex/resample.c OBJS_SPEEXDSP := $(patsubst %,$(OBJDIR)/%,$(SRCS_SPEEXDSP:.c=.o)) else CFLAGS_SPEEXDSP = $(shell $(PKG_CONFIG) --cflags speexdsp) LIBS_SPEEXDSP = $(shell $(PKG_CONFIG) --libs speexdsp) OBJS_SPEEXDSP := endif ifneq ($(findstring speexdsp,$(LIBS_REQUIRES)),) ifneq ($(USE_VENDORED_SPEEXDSP), 0) override LIBS_PRIVATE += $(LIBS_SPEEXDSP) else override REQUIRES_PRIVATE += speexdsp endif endif ifneq ($(DIR_SPEEXDSP),) FLAGS_SPEEXDSP := -std=c99 $(WARNINGS_DEF_C) ifneq ($(PLATFORM), Windows) FLAGS_SPEEXDSP += -fvisibility=hidden endif BUILD_SPEEXDSP = $(call COMPILE_C, $(FLAGS_SPEEXDSP)) $(OBJDIR)/deps/speex/%.o: $(DIR_SPEEXDSP)/%.c $(OBJDIR)/.tag $(call COMPILE_INFO,$(BUILD_SPEEXDSP)) @$(BUILD_SPEEXDSP) ifneq ($(USE_VENDORED_SPEEXDSP), 0) install-docs:: cp $(DIR_SPEEXDSP)/COPYING $(DESTDIR)$(DOCDIR)/COPYING-speexdsp endif endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/000077500000000000000000000000001523150047600205645ustar00rootroot00000000000000jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/audio.c000066400000000000000000000307611523150047600220400ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __APPLE__ #define _POSIX_C_SOURCE 200112L #endif #include #include #include #include #include #include #include #include "jgrf.h" #include "audio.h" #include "cli.h" #include "input.h" #include "settings.h" #include "wave_writer.h" // Divergence tolerance between current and average input samples per frame #define SPFTOLERANCE 28 static jgrf_gdata_t *gdata = NULL; // Pointer to audio information from the emulator core static jg_audioinfo_t *audinfo = NULL; // Info about audio to be input to the core (via microphone) static jg_audioinfo_t micinfo = { JG_SAMPFMT_INT16, 48000, 1, 800, NULL }; // Audio Output static SDL_AudioStream *audiostream = NULL; // Audio Input static SDL_AudioStream *audiostream_in = NULL; // Speex Resampler static SpeexResamplerState *resampler = NULL; static int err; // Wave Writer static wave_writer *ww; static wave_writer_format wwformat; static wave_writer_error wwerror; // Buffers for audio samples static void *corebuf = NULL; // Audio buffer for the core static int16_t *rsbuf = NULL; // Buffer for audio data to be resampled static int16_t *outbuf = NULL; // Buffer for final output samples static mavg_t mavg_in = { 0, 0, {0} }; // Moving Average for input sizes static ringbuf_t rbuf_in = { 0, 0, 0, 0, NULL }; // Ring buffer (input audio) static struct timespec req, rem; static int ma_offset = 0; // Offset the input moving average chunk size static int mute = 0; extern int waveout; // Wave File Output // Add a new sample chunk size to the moving average and recalculate static inline void jgrf_audio_mavg(mavg_t *mavg, size_t newval) { mavg->buf[mavg->pos++] = newval; if (mavg->pos == MAVGSIZE) mavg->pos = 0; double sum = 0; for (uint32_t i = 0; i < MAVGSIZE; ++i) sum += mavg->buf[i]; double divisor = MAVGSIZE; mavg->avg = sum / divisor; } // Seed the moving average input sample chunk size static inline void jgrf_audio_mavg_seed(mavg_t *mavg, size_t seed) { for (uint32_t i = 0; i < MAVGSIZE; ++i) mavg->buf[i] = seed; jgrf_audio_mavg(mavg, seed); } // Enqueue and Dequeue samples static inline int16_t jgrf_rbuf_deq(ringbuf_t *rbuf) { if (rbuf->cursize == 0) return 0; int16_t sample = rbuf->buffer[rbuf->head]; rbuf->head = (rbuf->head + 1) % rbuf->bufsize; --rbuf->cursize; return sample; } static inline void jgrf_rbuf_enq(ringbuf_t *rbuf, int16_t *data, size_t size) { for (uint32_t i = 0; i < size; ++i) { rbuf->buffer[rbuf->tail] = data[i]; rbuf->tail = (rbuf->tail + 1) % rbuf->bufsize; ++rbuf->cursize; if (rbuf->cursize >= rbuf->bufsize - 1) break; } } static inline void jgrf_rbuf_enqf(ringbuf_t *rbuf, float *data, size_t size) { for (uint32_t i = 0; i < size; ++i) { data[i] *= 32768.0; if (data[i] >= 32767.0) rbuf->buffer[rbuf->tail] = 32767; else if (data[i] <= -32768.0) rbuf->buffer[rbuf->tail] = -32768; else rbuf->buffer[rbuf->tail] = (int16_t)(lrintf(data[i])); rbuf->tail = (rbuf->tail + 1) % rbuf->bufsize; ++rbuf->cursize; if (rbuf->cursize >= rbuf->bufsize - 1) break; } } // Set timing information to align audio processing with core framerate void jgrf_audio_timing(double frametime) { int spf = (audinfo->rate / (int)(frametime + 0.5)) * audinfo->channels; if (abs(spf - (int)(mavg_in.avg + 0.5)) >= SPFTOLERANCE) { int oldavg = (int)mavg_in.avg; jgrf_audio_mavg_seed(&mavg_in, spf); jgrf_log(JG_LOG_DBG, "Moving Averge set: %f fps, %d spf (old: %d, diff: %d)\n", frametime, spf, oldavg, abs(spf - oldavg)); } } // Poll audio capture and push to core /*static void jgrf_audio_capture_poll(void) { if (!audiostream_in) return; int avail = SDL_GetAudioStreamAvailable(audiostream_in); if (avail <= 0) return; static int16_t capbuf[4096]; int toread = avail; if (toread > (int)sizeof(capbuf)) toread = sizeof(capbuf); int got = SDL_GetAudioStreamData(audiostream_in, capbuf, toread); if (got > 0) { micinfo.buf = (void*)capbuf; jgrf_data_push(JG_DATA_AUDIO, 0, &micinfo, got / sizeof(int16_t)); } }*/ // Callback used by core to tell the frontend how many samples are ready void jgrf_audio_cb_core(void *udata, size_t in_size) { (void)udata; /* If Benchmark mode is set, or the core calls this function with no samples ready, do nothing. */ if (bmark || !in_size) return; // Adjust input moving average calculation to reflect this input size jgrf_audio_mavg(&mavg_in, in_size); // Keep the output queue aligned to size of the ideal samples per frame size_t spf = (audinfo->rate / corefps) * audinfo->channels; // Input moving average samples per frame - make sure the number is even uint32_t ma_insamps = (uint32_t)(mavg_in.avg) + ((uint32_t)(mavg_in.avg) % 2); // Control the size of the input queue if (rbuf_in.cursize == 0) // Buffer ~half samples if empty ma_offset = -((in_size >> 1) + ((in_size >> 1) % audinfo->channels)); else if (rbuf_in.cursize < spf) ma_offset = -audinfo->channels; // Eat Less else if (rbuf_in.cursize > (spf + (spf >> 2))) ma_offset = audinfo->channels; // Eat More else if (rbuf_in.cursize == spf) ma_offset = 0; // Goldilocks Zone ma_insamps += ma_offset; // Calculate rates for use in resampling uint32_t in_rate = (ma_insamps * corefps) / audinfo->channels; uint32_t out_rate = audinfo->rate / (fforward ? fforward + 1 : 1); /* Use SDL's audio stream queue depth as the feedback signal. Target 3-4 frames worth of samples in the SDL stream buffer. */ int qbytes = SDL_GetAudioStreamAvailable(audiostream); int qsamps = qbytes / sizeof(int16_t); out_rate += (4 - (qsamps / spf)) * screenfps; // Change the resampling ratio err = speex_resampler_set_rate_frac(resampler, in_rate, out_rate, in_rate, out_rate); // Debug output //jgrf_log(JG_LOG_DBG, "i - r: %d, s: %d, c: %d, ma: %d (%f), fps: %d\n", // in_rate, in_size, rbuf_in.cursize, ma_insamps, mavg_in.avg, corefps); uint32_t out_size = (ma_insamps * (double)out_rate) / (double)in_rate; // Enqueue samples from the core for resampling and output if (audinfo->sampfmt == JG_SAMPFMT_INT16) jgrf_rbuf_enq(&rbuf_in, audinfo->buf, in_size); else jgrf_rbuf_enqf(&rbuf_in, audinfo->buf, in_size); // Dequeue the moving average number of samples to be resampled for (uint32_t i = 0; i < ma_insamps; ++i) rsbuf[i] = jgrf_rbuf_deq(&rbuf_in); // Perform resampling if (audinfo->channels == 2) { ma_insamps >>= 1; out_size >>= 1; err = speex_resampler_process_interleaved_int(resampler, mute ? NULL : rsbuf, &ma_insamps, outbuf, &out_size); out_size <<= 1; } else { err = speex_resampler_process_int(resampler, 0, mute ? NULL : rsbuf, &ma_insamps, outbuf, &out_size); } while ((SDL_GetAudioStreamAvailable(audiostream) / sizeof(int16_t)) + out_size >= (audinfo->spf * 6)) nanosleep(&req, &rem); // Push resampled audio directly into the SDL AudioStream SDL_PutAudioStreamData(audiostream, outbuf, out_size * sizeof(int16_t)); //jgrf_log(JG_LOG_DBG, "o - r: %d, s: %d, c: %d\n", // out_rate, out_size, // (SDL_GetAudioStreamAvailable(audiostream) / sizeof(int16_t))); // Wave file output if (waveout) wave_writer_put_samples(ww, out_size / audinfo->channels, outbuf); } // Pass the core's audio information into the frontend void jgrf_audio_set_info(jg_audioinfo_t *ptr) { audinfo = ptr; } // Initialize the audio device and allocate buffers int jgrf_audio_init(void) { jg_setting_t *settings = jgrf_settings_ptr(); gdata = jgrf_gdata_ptr(); // Set up Wave Writer if (waveout) { wwformat.num_channels = audinfo->channels; wwformat.sample_rate = audinfo->rate; wwformat.sample_bits = 16; ww = wave_writer_open(jgrf_cli_wave(), &wwformat, &wwerror); } SDL_AudioSpec audiospec; audiospec.channels = audinfo->channels; audiospec.freq = audinfo->rate; audiospec.format = SDL_AUDIO_S16; // Open an audio stream bound to the default playback device (no callback) audiostream = SDL_OpenAudioDeviceStream( SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audiospec, NULL, NULL); // Set up Audio Capture if (gdata->hints & JG_HINT_INPUT_AUDIO) { SDL_AudioSpec capspec; capspec.channels = micinfo.channels; capspec.freq = micinfo.rate; capspec.format = SDL_AUDIO_S16; audiostream_in = SDL_OpenAudioDeviceStream( SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &capspec, NULL, NULL); } // Set up the Resampler resampler = speex_resampler_init(audinfo->channels, audinfo->rate, audinfo->rate, settings[AUDIO_RSQUAL].val, &err); if (audiostream && resampler) jgrf_log(JG_LOG_INF, "Audio: %dHz %s, Speex %d\n", audiospec.freq, audiospec.channels == 1 ? "Mono" : "Stereo", settings[AUDIO_RSQUAL].val); else jgrf_log(JG_LOG_WRN, "Audio: Error opening audio device.\n"); if (audiostream_in) jgrf_log(JG_LOG_INF, "Audio Capture: Enabled\n"); // Seed the moving averages jgrf_audio_mavg_seed(&mavg_in, audinfo->spf); if (!corefps) corefps = (audinfo->rate / audinfo->spf) * audinfo->channels; // Set delay time in nanoseconds req.tv_nsec = 100000; // 1/10th of a millisecond // Allocate audio buffers size_t bufsize = audinfo->spf * sizeof(int16_t) * 4; if (!(gdata->hints & JG_HINT_AUDIO_INTERNAL)) corebuf = (void*)calloc(1, bufsize << audinfo->sampfmt); rsbuf = (void*)calloc(1, bufsize); outbuf = (void*)calloc(1, bufsize * 2); size_t rbufsize = audinfo->spf * 6; rbuf_in.bufsize = rbufsize; rbuf_in.buffer = (void*)calloc(1, rbufsize * sizeof(int16_t)); // Pass the core audio buffer into the emulator if needed audinfo->buf = corebuf; return 1; } // Unpause the audio device void jgrf_audio_unpause(void) { if (audiostream) SDL_ResumeAudioStreamDevice(audiostream); if (audiostream_in) SDL_ResumeAudioStreamDevice(audiostream_in); } // Deinitialize the audio device and free buffers void jgrf_audio_deinit(void) { if (waveout) wave_writer_close(ww, &wwerror); if (audiostream_in) SDL_DestroyAudioStream(audiostream_in); if (audiostream) SDL_DestroyAudioStream(audiostream); if (resampler) speex_resampler_destroy(resampler); if (!(gdata->hints & JG_HINT_AUDIO_INTERNAL)) if (corebuf) free(corebuf); if (rbuf_in.buffer) free(rbuf_in.buffer); if (rsbuf) free(rsbuf); if (outbuf) free(outbuf); } // Toggle audio playback (mute/unmute) void jgrf_audio_toggle(void) { mute ^= 1; jgrf_log(JG_LOG_SCR, "Audio %s", mute ? "Muted" : "Unmuted"); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/audio.h000066400000000000000000000043611523150047600220420ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef AUDIO_H #define AUDIO_H #define MAVGSIZE 60 * 2 // 60 * N second moving average // Moving Average typedef struct mavg_t { double avg; // The moving average uint32_t pos; // Position in the buffer size_t buf[MAVGSIZE]; // The buffer } mavg_t; // Ring Buffer (Audio Queue) typedef struct ringbuf_t { uint32_t head; // Head position uint32_t tail; // Tail position uint32_t cursize; // Current queue size uint32_t bufsize; // Buffer size int16_t *buffer; // Pointer to the buffer } ringbuf_t; void jgrf_audio_set_info(jg_audioinfo_t*); void jgrf_audio_timing(double); void jgrf_audio_cb_core(void*, size_t); void jgrf_audio_unpause(void); int jgrf_audio_init(void); void jgrf_audio_deinit(void); void jgrf_audio_toggle(void); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/cheats.c000066400000000000000000000112471523150047600222040ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "parson.h" #include "jgrf.h" #include "cheats.h" #include "settings.h" static void (*jgrf_cheat_clear)(void); static void (*jgrf_cheat_set)(const char *); static char *chtfile = NULL; static int chtactive = 0; static int chtloaded = 0; // Parse the cheat file and activate any cheats void jgrf_cheats_toggle(void) { // If cheats are not loaded, simply return if (!chtloaded) { jgrf_log(JG_LOG_SCR, "No cheat file loaded"); return; } if (chtactive) { jgrf_cheat_clear(); chtactive = 0; jgrf_log(JG_LOG_SCR, "Cheats Deactivated"); return; } jgrf_cheat_clear(); // Disable any active cheats first // Parse the JSON string JSON_Value *root_value = json_parse_string_with_comments(chtfile); if (root_value == NULL || json_value_get_type(root_value) != JSONObject) { jgrf_log(JG_LOG_WRN, "Failed to parse cheat file\n"); return; } // Retrieve the cheats array JSON_Object *parent = json_value_get_object(root_value); JSON_Array *cheatarray = json_object_get_array(parent, "cheats"); // Loop through all the cheats for (size_t i = 0; i < json_array_get_count(cheatarray); ++i) { JSON_Object *cheat = json_array_get_object(cheatarray, i); // Apply any cheats set to "enabled" if (json_object_get_boolean(cheat, "enabled")) { chtactive = 1; jgrf_log(JG_LOG_DBG, "Cheat: %s\n", json_object_get_string(cheat, "desc")); // Enable all codes in the array JSON_Array *codes = json_object_get_array(cheat, "codes"); for (size_t j = 0; j < json_array_get_count(codes); ++j) jgrf_cheat_set(json_array_get_string(codes, j)); } } if (chtactive) jgrf_log(JG_LOG_SCR, "Cheats Activated"); // Free resources associated with parsing the JSON string json_value_free(root_value); } void jgrf_cheats_deinit(void) { if (chtfile) free(chtfile); } void jgrf_cheats_init(void (*chtclear)(void), void (*chtset)(const char *)) { // Set the function pointers jgrf_cheat_clear = chtclear; jgrf_cheat_set = chtset; // Grab the pointer to global data jgrf_gdata_t *gdata = jgrf_gdata_ptr(); // Set the cheat file path char chtfilepath[264]; snprintf(chtfilepath, sizeof(chtfilepath), "%s/%s.json", gdata->cheatpath, gdata->gamename); // Attempt to open the file FILE *file = fopen(chtfilepath, "rb"); if (!file) { jgrf_log(JG_LOG_DBG, "No cheat file: %s\n", chtfilepath); return; } fseek(file, 0, SEEK_END); size_t chtfilesize = ftell(file) * sizeof(char); rewind(file); chtfile = (char*)calloc(chtfilesize, sizeof(char)); if (!chtfile || !fread((void*)chtfile, chtfilesize, 1, file)) { jgrf_log(JG_LOG_WRN, "Failed to read file: %s\n", chtfilepath); fclose(file); return; } jgrf_log(JG_LOG_DBG, "Cheat file loaded: %s\n", chtfilepath); // Cheats have been loaded chtloaded = 1; // Activate any cheats if (jgrf_settings_ptr()[MISC_CHEATAUTO].val) jgrf_cheats_toggle(); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/cheats.h000066400000000000000000000032221523150047600222030ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef CHEATS_H #define CHEATS_H void jgrf_cheats_toggle(void); void jgrf_cheats_deinit(void); void jgrf_cheats_init(void (*)(void), void (*)(const char *)); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/cli.c000066400000000000000000000320671523150047600215070ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "musl_getopt.h" #include "jgrf.h" #include "cli.h" #include "paths.h" #include "settings.h" // Global Data pointer jgrf_gdata_t *gdata = NULL; // Variables for command line options static const char *os_def = "a:b:c:e:fg:hlo:r:s:vwx:"; static const struct option lopts[] = { { "video", required_argument, 0, 'a' }, { "benchmark", required_argument, 0, 'b' }, { "core", required_argument, 0, 'c' }, { "sys", required_argument, 0, 'e' }, { "fullscreen", no_argument, 0, 'f' }, { "cheatauto", required_argument, 0, 'g' }, { "help", no_argument, 0, 'h' }, { "licenses", no_argument, 0, 'l' }, { "wave", required_argument, 0, 'o' }, { "rsqual", required_argument, 0, 'r' }, { "shader", required_argument, 0, 's' }, { "verbose", no_argument, 0, 'v' }, { "window", no_argument, 0, 'w' }, { "scale", required_argument, 0, 'x' }, { 0, 0, 0, 0 } }; static const char *corename = NULL; static const char *sys = NULL; static const char *wavfile = NULL; static int video = -1; static int fullscreen = 0; static int cheatauto = -1; static int rsqual = -1; static int scale = -1; static int shader = -1; static int licenses = 0; static int verbose = 0; static int windowed = 0; int waveout = 0; // Check if a string is a number static inline int check_str_num(const char *optname, const char *arg) { char *endptr; long num = strtol(arg, &endptr, 10); (void)num; if (endptr == arg || *endptr != '\0' || errno == ERANGE) { // Not a number jgrf_log(JG_LOG_WRN, "Argument for '%s' must be a number.\n", optname); return 0; } return 1; } // Return the core name specified at the command line const char *jgrf_cli_core(void) { return corename; } // Return the emulated system name specified at the command line const char *jgrf_cli_sys(void) { return sys; } // Return the wave file name specified at the command line const char *jgrf_cli_wave(void) { return wavfile; } // Override settings with command line arguments void jgrf_cli_override(void) { jg_setting_t *settings = jgrf_settings_ptr(); if (verbose) { settings[MISC_CORELOG].val = 0; settings[MISC_FRONTENDLOG].val = 0; } if (video >= 0 && video <= settings[VIDEO_API].max) settings[VIDEO_API].val = video; else if (video != -1) jgrf_log(JG_LOG_WRN, "Setting 'video' outside of range: %d (%d-%d)\n", video, settings[VIDEO_API].min, settings[VIDEO_API].max); if (fullscreen) settings[VIDEO_FULLSCREEN].val = 1; if (cheatauto >= 0 && cheatauto <= settings[MISC_CHEATAUTO].max) settings[MISC_CHEATAUTO].val = cheatauto; else if (cheatauto != -1) jgrf_log(JG_LOG_WRN, "Setting 'cheatauto' outside of range: %d (%d-%d)" "\n", cheatauto, settings[MISC_CHEATAUTO].min, settings[MISC_CHEATAUTO].max); if (windowed) settings[VIDEO_FULLSCREEN].val = 0; if (rsqual >= 0 && rsqual <= settings[AUDIO_RSQUAL].max) settings[AUDIO_RSQUAL].val = rsqual; else if (rsqual != -1) jgrf_log(JG_LOG_WRN, "Setting 'rsqual' outside of range: %d (%d-%d)\n", rsqual, settings[AUDIO_RSQUAL].min, settings[AUDIO_RSQUAL].max); if (shader >= 0 && shader <= settings[VIDEO_SHADER].max) settings[VIDEO_SHADER].val = shader; else if (shader != -1) jgrf_log(JG_LOG_WRN, "Setting 'shader' outside of range: %d (%d-%d)\n", shader, settings[VIDEO_SHADER].min, settings[VIDEO_SHADER].max); if (scale >= 0 && scale <= settings[VIDEO_SCALE].max) settings[VIDEO_SCALE].val = scale; else if (scale != -1) jgrf_log(JG_LOG_WRN, "Setting 'scale' outside of range: %d (%d-%d)\n", scale, settings[VIDEO_SCALE].min, settings[VIDEO_SCALE].max); } // Command line parsing void jgrf_cli_parse(int argc, char *argv[]) { int c; int loptind; while ((c = getopt_long(argc, argv, os_def, lopts, &loptind)) != -1) { switch (c) { case 'a': { // Video API if (check_str_num("video", optarg)) video = atoi(optarg); break; } case 'b': { // Benchmark long long bmframes = atoll(optarg); if (bmframes < 1) jgrf_log(JG_LOG_WRN, "Argument for --bmark must be a " "number greater than 0\n"); else jgrf_benchmark(bmframes); break; } #ifndef JGRF_STATIC case 'c': { // Core selection corename = optarg; break; } #endif case 'e': { // System to emulate sys = optarg; break; } case 'h': { // Show usage jgrf_cli_usage(argv[0]); jgrf_quit(EXIT_SUCCESS); break; } case 'l': { // Show licenses licenses = 1; break; } case 'f': { // Start in fullscreen mode fullscreen = 1; break; } case 'g': { // Auto-activate cheats if present if (check_str_num("cheatauto", optarg)) cheatauto = atoi(optarg); break; } case 'o': { // Wave File Output wavfile = optarg; struct stat fbuf; if (stat(wavfile, &fbuf) == 0) { jgrf_log(JG_LOG_WRN, "WAV Writer: Refusing to overwrite %s!\n", wavfile); } else { jgrf_log(JG_LOG_WRN, "WAV Writer: Writing WAV to %s\n", wavfile); waveout = 1; } break; } case 'r': { // Resampler Quality if (check_str_num("rsqual", optarg)) rsqual = atoi(optarg); break; } case 's': { // Shader if (check_str_num("shader", optarg)) shader = atoi(optarg); break; } case 'v': { // Enable verbose log output for core and frontend verbose = 1; break; } case 'w': { // Start in windowed mode windowed = 1; break; } case 'x': { // Scale if (check_str_num("scale", optarg)) scale = atoi(optarg); break; } case '?': { fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); jgrf_quit(EXIT_FAILURE); break; } default: { break; } } } // Handle --licenses after all options are parsed so -c is available if (licenses) { jgrf_cli_licenses(); jgrf_quit(EXIT_SUCCESS); } // Last non-option is the ROM filename gdata = jgrf_gdata_ptr(); gdata->filename = argv[argc - 1]; // Count the number of auxiliary files gdata->numauxfiles = (unsigned)argc - optind - 1; if (gdata->numauxfiles > JGRF_AUXFILE_MAX) gdata->numauxfiles = JGRF_AUXFILE_MAX; for (int i = 0; i < gdata->numauxfiles; ++i) jgrf_auxfile_load(argv[optind + i], i); } void jgrf_cli_licenses(void) { gdata = jgrf_gdata_ptr(); fprintf(stdout, "The Jolly Good Reference Frontend\n" "Copyright (c) 2020-2026 Rupert Carmichael\n" "Licensed under the BSD 3-Clause \"New\" or \"Revised\" License.\n" "Refer to README for dependency licenses.\n\n"); #ifdef JGRF_STATIC jgrf_paths_set_corelib(); jgrf_paths_set_coreassets(); #else if (!corename) { fprintf(stdout, "Specify a core with -c to view its licenses.\n"); return; } snprintf(gdata->corename, sizeof(gdata->corename), "%s", corename); if (!jgrf_paths_env_coreassets()) jgrf_paths_set_coreassets(); #endif char lpath[448]; snprintf(lpath, sizeof(lpath), "%s%cLICENSES", gdata->coreassets, SEP); FILE *lfile = fopen(lpath, "r"); #ifdef JGRF_STATIC // Fall back to the binary's directory for static builds if (!lfile) { const char *fallback = gdata->binpath[0] ? gdata->binpath : "."; snprintf(lpath, sizeof(lpath), "%s%cLICENSES", fallback, SEP); lfile = fopen(lpath, "r"); } #endif if (!lfile) { fprintf(stdout, "License file not found\n"); return; } char buf[4096]; size_t nread; while ((nread = fread(buf, 1, sizeof(buf), lfile)) > 0) fwrite(buf, 1, nread, stdout); fclose(lfile); } void jgrf_cli_usage(char *binname) { #ifdef JGRF_STATIC jg_coreinfo_t *coreinfo = jg_get_coreinfo(""); fprintf(stdout, "%s %s\n", coreinfo->fname, coreinfo->version); #else fprintf(stdout, "The Jolly Good Reference Frontend %s\n", VERSION); #endif fprintf(stdout, "usage: %s [options] [auxiliary files] game\n", strrchr(binname, SEP) ? strrchr(binname, SEP) + 1 : binname); fprintf(stdout, " options:\n"); fprintf(stdout, " -a, --video " "Specify which Video API to use\n"); fprintf(stdout, " 0 = OpenGL Core Profile\n"); fprintf(stdout, " 1 = OpenGL ES\n"); fprintf(stdout, " 2 = OpenGL Compatibility " "Profile\n"); #ifdef JGRF_VULKAN fprintf(stdout, " 3 = Vulkan\n"); #endif fprintf(stdout, " -b, --bmark " "Run N frames in Benchmark mode\n"); #ifndef JGRF_STATIC fprintf(stdout, " -c, --core " "Specify which core to use\n"); #endif fprintf(stdout, " -e, --sys " "Specify which system to emulate\n"); fprintf(stdout, " -f, --fullscreen " "Start in Fullscreen mode\n"); fprintf(stdout, " -g, --cheatauto " "Auto-activate cheats if present\n"); fprintf(stdout, " 0 = Disabled\n"); fprintf(stdout, " 1 = Enabled\n"); fprintf(stdout, " -h, --help " "Show Usage\n"); fprintf(stdout, " -l, --licenses " #ifdef JGRF_STATIC "Show Licenses\n"); #else "Show Licenses for a core (requires -c)\n"); #endif fprintf(stdout, " -o, --wave " "Specify WAV output file\n"); fprintf(stdout, " -r, --rsqual " "Resampler Quality (0 to 10)\n"); fprintf(stdout, " -s, --shader " "Select a Post-processing shader\n"); fprintf(stdout, " 0 = Nearest Neighbour (None)" "\n"); fprintf(stdout, " 1 = Linear\n"); fprintf(stdout, " 2 = Sharp Bilinear\n"); fprintf(stdout, " 3 = Anti-Aliased Nearest " "Neighbour\n"); fprintf(stdout, " 4 = CRT-Yee64\n"); fprintf(stdout, " 5 = CRTea\n"); fprintf(stdout, " 6 = LCD\n"); fprintf(stdout, " -v, --verbose " "Enable verbose log output for core and frontend\n"); fprintf(stdout, " -w, --window " "Start in Windowed mode\n"); fprintf(stdout, " -x, --scale " "Video Scale Factor (1 to 8)\n\n"); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/cli.h000066400000000000000000000033531523150047600215100ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef CLI_H #define CLI_H const char *jgrf_cli_core(void); const char *jgrf_cli_sys(void); const char *jgrf_cli_wave(void); void jgrf_cli_override(void); void jgrf_cli_parse(int, char**); void jgrf_cli_licenses(void); void jgrf_cli_usage(char*); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/detect.c000066400000000000000000000304231523150047600222020ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "miniz.h" #include "musl_memmem.h" #include "jgrf.h" #include "detect.h" #define NUMCORES 3 // Up to 3 possible core choices typedef struct _corelist_t { const char *sys; // System const char *corename[NUMCORES]; } corelist_t; static corelist_t corelist[] = { {"32x", {"picodrive", "", ""}}, {"7800", {"prosystem", "", ""}}, {"coleco", {"jollycv", "", ""}}, {"gb", {"gambatte", "sameboy", ""}}, {"gba", {"mgba", "", ""}}, {"gg", {"cega", "", ""}}, {"md", {"genplus", "mednafen", "picodrive"}}, {"myvision", {"jollycv", "", ""}}, {"lynx", {"mednafen", "", ""}}, {"n64", {"cen64", "", ""}}, {"nds", {"melonds", "", ""}}, {"neogeo", {"geolith", "", ""}}, {"neogeocd", {"geolith", "", ""}}, {"nes", {"nestopia", "", ""}}, {"ngp", {"mednafen", "", ""}}, {"pce", {"mednafen", "", ""}}, {"pcfx", {"mednafen", "", ""}}, {"psx", {"mednafen", "", ""}}, {"sg", {"cega", "", ""}}, {"sms", {"cega", "", ""}}, {"snes", {"bsnes", "mednafen", ""}}, {"ss", {"mednafen", "", ""}}, {"vb", {"mednafen", "", ""}}, {"vectrex", {"vecx", "", ""}}, {"wswan", {"mednafen", "", ""}} }; typedef struct _extmap_t { const char *ext; const char *sys; } extmap_t; static extmap_t extmap[] = { {"32x", "32x"}, {"a78", "7800"}, {"bs", "snes"}, {"chd", "neogeocd"}, // Only Geolith supports .chd {"col", "coleco"}, {"dsi", "nds"}, {"fds", "nes"}, {"gb", "gb"}, {"gba", "gba"}, {"gbc", "gb"}, {"gg", "gg"}, {"lnx", "lynx"}, {"myv", "myvision"}, {"md", "md"}, {"n64", "n64"}, {"ndd", "n64"}, {"nds", "nds"}, {"neo", "neogeo"}, {"nes", "nes"}, {"ngc", "ngp"}, {"ngp", "ngp"}, {"nsf", "nes"}, {"pce", "pce"}, {"rom", "coleco"}, {"sfc", "snes"}, {"sg", "sg"}, {"sgx", "pce"}, {"smc", "snes"}, {"sms", "sms"}, {"st", "snes"}, {"vb", "vb"}, {"vec", "vectrex"}, {"ws", "wswan"}, {"wsc", "wswan"}, {"z64", "n64"}, }; static unsigned detect_attempt = 0; // Detect game type inside .zip archives static int jgrf_detect_zip(const char *filename) { mz_zip_archive zip_archive; memset(&zip_archive, 0, sizeof(zip_archive)); // Make sure it's actually a zip file if (!mz_zip_reader_init_file(&zip_archive, filename, 0)) return 0; // Cycle through files in the archive until one matches a known game type for (int i = 0; i < (int)mz_zip_reader_get_num_files(&zip_archive); ++i) { mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) { mz_zip_reader_end(&zip_archive); jgrf_log(JG_LOG_ERR, "Failed to stat archive. Exiting...\n"); } // Set the game name based on the file inside the zip if (jgrf_detect_sys(file_stat.m_filename)) { mz_zip_reader_end(&zip_archive); return 1; } } mz_zip_reader_end(&zip_archive); return 0; } // Detect what console the optical disc was designed for static int jgrf_detect_bincue(const char *filename, unsigned fcount) { uint8_t sectorbuf[2352]; const char *mcdmagic[4] = { // Not McDonald's Magic "SEGADISCSYSTEM ", "SEGABOOTDISC ", "SEGADISC ", "SEGADATADISC ", }; size_t mcdmagiclen = strlen(mcdmagic[0]); // 16 const char *pcemagic = "PC Engine CD-ROM SYSTEM"; size_t pcemagiclen = strlen(pcemagic); // 23 const char *pcfxmagic = "PC-FX:Hu_CD-ROM "; size_t pcfxmagiclen = strlen(pcfxmagic); // 16 const char *psxmagic = " Licensed by "; size_t psxmagiclen = strlen(psxmagic); // 16 const char *ssmagic = "SEGA SEGASATURN "; size_t ssmagiclen = strlen(ssmagic); // 16 const char *ngcdmagic = "NEO-GEO"; size_t ngcdmagiclen = strlen(ngcdmagic); // 8 FILE *file; file = fopen(filename, "r"); if (!file) return 0; jgrf_gdata_t *gdata = jgrf_gdata_ptr(); // Check heuristically for each system, least complex first // PSX fseek(file, 0x24e0, SEEK_SET); // 0x24e0 is the magic offset if (fread(sectorbuf, 1, 16, file)) { if (!memcmp(sectorbuf, psxmagic, psxmagiclen)) { snprintf(gdata->sys, sizeof(gdata->sys), "psx"); fclose(file); return 1; } } rewind(file); // Sega Saturn if (fread(sectorbuf, 1, 32, file)) { // Read data for both possible offsets if (!memcmp(sectorbuf, ssmagic, ssmagiclen) || !memcmp(sectorbuf + 16, ssmagic, ssmagiclen)) { snprintf(gdata->sys, sizeof(gdata->sys), "ss"); fclose(file); return 1; } } rewind(file); // Mega/Sega CD if (fread(sectorbuf, 1, 32, file)) { // Read data for both possible offsets for (int i = 0; i < 4; ++i) { if (!memcmp(sectorbuf, mcdmagic[i], mcdmagiclen) || !memcmp(sectorbuf + 16, mcdmagic[i], mcdmagiclen)) { snprintf(gdata->sys, sizeof(gdata->sys), "md"); fclose(file); return 1; } } } rewind(file); #if defined(__MINGW32__) || defined(__MINGW64__) /* On Windows, just assume PCE, since the search using mmemmem will fail. This means PC-FX can't be detected on Windows. */ snprintf(gdata->sys, sizeof(gdata->sys), "pce"); jgrf_log(JG_LOG_WRN, "Heuristic checks failed, assuming PCE CD\n"); return 1; #else // Loop through the bin, reading 2048 byte chunks at a time // Neo Geo CD if (fcount == 0) { // The signature is always in the first track while (fread(sectorbuf, 1, 2048, file) == 2048) { if (mmemmem(sectorbuf, 2048, (uint8_t*)ngcdmagic, ngcdmagiclen) != NULL) { snprintf(gdata->sys, sizeof(gdata->sys), "neogeocd"); fclose(file); return 1; } } rewind(file); } // PCE while (fread(sectorbuf, 1, 2048, file) == 2048) { if (mmemmem(sectorbuf, 2048, (uint8_t*)pcemagic, pcemagiclen) != NULL) { snprintf(gdata->sys, sizeof(gdata->sys), "pce"); fclose(file); return 1; } } rewind(file); // PC-FX while (fread(sectorbuf, 1, 2048, file) == 2048) { if (mmemmem(sectorbuf, 2048, (uint8_t*)pcfxmagic, pcfxmagiclen) != NULL) { snprintf(gdata->sys, sizeof(gdata->sys), "pcfx"); fclose(file); return 1; } } #endif fclose(file); return 0; } // Read a cuesheet to know how to access tracks for further heuristic checks static int jgrf_detect_cue(const char *filename) { FILE *file; file = fopen(filename, "r"); if (!file) return 0; // Parse cuesheet for FILE entries to read from to determine system type char line[256]; // Should be big enough... char binpath[256]; char binfullpath[512]; unsigned fcount = 0; // File count while (fgets(line, sizeof(line), file)) { if (strstr(line, "FILE")) { char *binfile = strchr(line, '"'); if (binfile) binfile++; else continue; char *end = strrchr(binfile, '"'); if (end) *end = '\0'; else continue; // If we got this far, try to read into the binary file snprintf(binpath, sizeof(binpath), "%s", filename); end = strrchr(binpath, SEP); if (end) *(end + 1) = '\0'; else *binpath = '\0'; snprintf(binfullpath, sizeof(binfullpath), "%s%s", binpath, binfile); if (jgrf_detect_bincue(binfullpath, fcount)) { fclose(file); return 1; } ++fcount; } } fclose(file); return 0; } // Read an M3U playlist to check for relevant cuesheets to be read static int jgrf_detect_m3u(const char *filename) { FILE *file; file = fopen(filename, "r"); if (!file) return 0; char line[256]; char cuepath[384]; // Cue paths are a bit larger than their containing M3U char cuefullpath[640]; while (fgets(line, sizeof(line), file)) { // Read .cue entries from the M3U, assuming relative paths snprintf(cuepath, sizeof(cuepath), "%s", filename); line[strlen(line) - 1] = '\0'; // Remove the newline character // Build the absolute path to the .cue file char *end = strrchr(cuepath, SEP); if (end) *(end + 1) = '\0'; else *cuepath = '\0'; snprintf(cuefullpath, sizeof(cuefullpath), "%s%s", cuepath, line); if (jgrf_detect_cue(cuefullpath)) { fclose(file); return 1; } } fclose(file); return 0; } // Set the default core for a detected system - can be overridden at CLI int jgrf_detect_core(void) { if (detect_attempt == NUMCORES) return 0; jgrf_gdata_t *gdata = jgrf_gdata_ptr(); for (size_t i = 0; i < sizeof(corelist) / sizeof(corelist_t); ++i) { if (!strcmp(gdata->sys, corelist[i].sys)) { // Ensure we are not at the end of the list if (!strlen(corelist[i].corename[detect_attempt])) return 0; // Copy the core name, prepare for next attempt if this one fails snprintf(gdata->corename, sizeof(gdata->corename), "%s", corelist[i].corename[detect_attempt++]); return 1; } } return 0; } // Detect the desired system based on file extension int jgrf_detect_sys(const char *filename) { const char *extptr = strrchr(filename, '.'); if (extptr != NULL) extptr++; else return 0; // Convert extension to lower case char ext[4]; snprintf(ext, sizeof(ext), "%s", extptr); for (size_t i = 0; i < strlen(ext); ++i) ext[i] = tolower(ext[i]); // Grab global data pointer jgrf_gdata_t *gdata = jgrf_gdata_ptr(); // Generic file extensions if (!strcmp(ext, "bin")) return 0; else if (!strcmp(ext, "cue")) return jgrf_detect_cue(filename); else if (!strcmp(ext, "m3u")) return jgrf_detect_m3u(filename); else if (!strcmp(ext, "zip")) return jgrf_detect_zip(filename); // Do not load a .chd into memory if (!strcmp(ext, "chd")) gdata->pathonly = 1; // Non-generic file extensions for (size_t i = 0; i < sizeof(extmap) / sizeof(extmap_t); ++i) { if (!strcmp(ext, extmap[i].ext)) { snprintf(gdata->sys, sizeof(gdata->sys), "%s", extmap[i].sys); return 1; } } return 0; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/detect.h000066400000000000000000000031231523150047600222040ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef DETECT_H #define DETECT_H int jgrf_detect_core(void); int jgrf_detect_sys(const char*); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/dips.c000066400000000000000000000103621523150047600216710ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "tconfig.h" #include "jgrf.h" #include "dips.h" static ini_table_s *conf; static jgrf_gdata_t *gdata; static size_t numdips = 0; static jg_setting_t *dips = NULL; static int *dips_default = NULL; // Build the path to the .dip file for the loaded game static void jgrf_dips_path(char *out, size_t outlen) { snprintf(out, outlen, "%s%c%s.dip", gdata->savepath, SEP, gdata->gamename); } // Pull DIP descriptors from the core and load saved DIP positions void jgrf_dips_load(void) { gdata = jgrf_gdata_ptr(); #ifdef JGRF_STATIC dips = jg_get_dips(&numdips); #else dips = jgrf_jgapi_ptr()->jg_get_dips(&numdips); #endif if (!numdips) { jgrf_log(JG_LOG_DBG, "No DIP switches\n"); return; } // Snapshot defaults so the user can reset dips_default = (int*)calloc(numdips, sizeof(int)); for (size_t i = 0; i < numdips; ++i) dips_default[i] = dips[i].val; // Load saved .dip if present char path[384]; jgrf_dips_path(path, sizeof(path)); conf = ini_table_create(); if (!ini_table_read_from_file(conf, path)) { jgrf_log(JG_LOG_DBG, "DIP file not found: %s\n", path); ini_table_destroy(conf); return; } char key[32]; for (size_t i = 0; i < numdips; ++i) { snprintf(key, sizeof(key), "%zu", i); if (ini_table_check_entry(conf, "dipswitch", key)) { int val; ini_table_get_entry_as_int(conf, "dipswitch", key, &val); if (val >= dips[i].min && val <= dips[i].max) dips[i].val = val; else jgrf_log(JG_LOG_WRN, "DIP value out of range at index %zu: %d\n", i, val); } } ini_table_destroy(conf); } // Write current DIP state if any DIPs exist void jgrf_dips_save(void) { if (!numdips) return; char path[384]; jgrf_dips_path(path, sizeof(path)); conf = ini_table_create(); char key[32]; char ibuf[32]; for (size_t i = 0; i < numdips; ++i) { snprintf(key, sizeof(key), "%zu", i); snprintf(ibuf, sizeof(ibuf), "%d", dips[i].val); ini_table_create_entry(conf, "dipswitch", key, ibuf); } ini_table_write_to_file(conf, path); ini_table_destroy(conf); } // Reset all DIPs to core-provided defaults void jgrf_dips_default(void) { if (!numdips || !dips_default) return; for (size_t i = 0; i < numdips; ++i) dips[i].val = dips_default[i]; jgrf_rehash_core(); } void jgrf_dips_deinit(void) { if (dips_default) { free(dips_default); dips_default = NULL; } numdips = 0; dips = NULL; } jg_setting_t* jgrf_dips_ptr(size_t *num) { *num = numdips; return dips; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/dips.h000066400000000000000000000032511523150047600216750ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef DIPS_H #define DIPS_H void jgrf_dips_load(void); void jgrf_dips_save(void); void jgrf_dips_default(void); void jgrf_dips_deinit(void); jg_setting_t* jgrf_dips_ptr(size_t *); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/font/000077500000000000000000000000001523150047600215325ustar00rootroot00000000000000jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/font/6x8.h000066400000000000000000000203421523150047600223310ustar00rootroot00000000000000// U+0000 - U+007F - Public Domain 6x8 console font static uint8_t sixbyeight[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0000 (nul) 0x38, 0x44, 0x6c, 0x44, 0x54, 0x44, 0x38, 0x00, // U+0001 0x38, 0x7c, 0x54, 0x7c, 0x44, 0x7c, 0x38, 0x00, // U+0002 0x00, 0x28, 0x7c, 0x7c, 0x7c, 0x38, 0x10, 0x00, // U+0003 0x00, 0x10, 0x38, 0x7c, 0x7c, 0x38, 0x10, 0x00, // U+0004 0x10, 0x38, 0x38, 0x10, 0x7c, 0x7c, 0x10, 0x00, // U+0005 0x00, 0x10, 0x38, 0x7c, 0x7c, 0x10, 0x38, 0x00, // U+0006 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, // U+0007 0xfc, 0xfc, 0xfc, 0xcc, 0xcc, 0xfc, 0xfc, 0xfc, // U+0008 0x00, 0x00, 0x78, 0x48, 0x48, 0x78, 0x00, 0x00, // U+0009 0xfc, 0xfc, 0x84, 0xb4, 0xb4, 0x84, 0xfc, 0xfc, // U+000A 0x00, 0x1c, 0x0c, 0x34, 0x48, 0x48, 0x30, 0x00, // U+000B 0x38, 0x44, 0x44, 0x38, 0x10, 0x38, 0x10, 0x00, // U+000C 0x10, 0x18, 0x14, 0x10, 0x30, 0x70, 0x60, 0x00, // U+000D 0x0c, 0x34, 0x2c, 0x34, 0x2c, 0x6c, 0x60, 0x00, // U+000E 0x00, 0x54, 0x38, 0x6c, 0x38, 0x54, 0x00, 0x00, // U+000F 0x20, 0x30, 0x38, 0x3c, 0x38, 0x30, 0x20, 0x00, // U+0010 0x08, 0x18, 0x38, 0x78, 0x38, 0x18, 0x08, 0x00, // U+0011 0x10, 0x38, 0x7c, 0x10, 0x7c, 0x38, 0x10, 0x00, // U+0012 0x28, 0x28, 0x28, 0x28, 0x28, 0x00, 0x28, 0x00, // U+0013 0x3c, 0x54, 0x54, 0x34, 0x14, 0x14, 0x14, 0x00, // U+0014 0x38, 0x44, 0x30, 0x28, 0x18, 0x44, 0x38, 0x00, // U+0015 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x78, 0x00, // U+0016 0x10, 0x38, 0x7c, 0x10, 0x7c, 0x38, 0x10, 0x38, // U+0017 0x10, 0x38, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x00, // U+0018 0x10, 0x10, 0x10, 0x10, 0x7c, 0x38, 0x10, 0x00, // U+0019 0x00, 0x10, 0x18, 0x7c, 0x18, 0x10, 0x00, 0x00, // U+001A 0x00, 0x10, 0x30, 0x7c, 0x30, 0x10, 0x00, 0x00, // U+001B 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x7c, 0x00, // U+001C 0x00, 0x28, 0x28, 0x7c, 0x28, 0x28, 0x00, 0x00, // U+001D 0x10, 0x10, 0x38, 0x38, 0x7c, 0x7c, 0x00, 0x00, // U+001E 0x7c, 0x7c, 0x38, 0x38, 0x10, 0x10, 0x00, 0x00, // U+001F 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0020 (space) 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, // U+0021 (!) 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0022 (") 0x00, 0x28, 0x7c, 0x28, 0x28, 0x7c, 0x28, 0x00, // U+0023 (#) 0x10, 0x3c, 0x50, 0x38, 0x14, 0x78, 0x10, 0x00, // U+0024 ($) 0x44, 0x44, 0x08, 0x10, 0x20, 0x44, 0x44, 0x00, // U+0025 (%) 0x30, 0x48, 0x48, 0x3c, 0x48, 0x48, 0x34, 0x00, // U+0026 (&) 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0027 (') 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x00, // U+0028 (() 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x00, // U+0029 ()) 0x00, 0x10, 0x54, 0x38, 0x54, 0x10, 0x00, 0x00, // U+002A (*) 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00, // U+002B (+) 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, // U+002C (,) 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, // U+002D (-) 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, // U+002E (.) 0x04, 0x04, 0x08, 0x10, 0x20, 0x40, 0x40, 0x00, // U+002F (/) 0x38, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x38, 0x00, // U+0030 (0) 0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, // U+0031 (1) 0x38, 0x44, 0x04, 0x08, 0x10, 0x20, 0x7c, 0x00, // U+0032 (2) 0x38, 0x44, 0x04, 0x18, 0x04, 0x44, 0x38, 0x00, // U+0033 (3) 0x24, 0x24, 0x44, 0x7c, 0x04, 0x04, 0x04, 0x00, // U+0034 (4) 0x7c, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00, // U+0035 (5) 0x38, 0x40, 0x40, 0x78, 0x44, 0x44, 0x38, 0x00, // U+0036 (6) 0x7c, 0x04, 0x04, 0x08, 0x10, 0x10, 0x10, 0x00, // U+0037 (7) 0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x38, 0x00, // U+0038 (8) 0x38, 0x44, 0x44, 0x3c, 0x04, 0x44, 0x38, 0x00, // U+0039 (9) 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, // U+003A (:) 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x20, // U+003B (;) 0x00, 0x0c, 0x30, 0x40, 0x30, 0x0c, 0x00, 0x00, // U+003C (<) 0x00, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x00, 0x00, // U+003D (=) 0x00, 0x60, 0x18, 0x04, 0x18, 0x60, 0x00, 0x00, // U+003E (>) 0x38, 0x44, 0x04, 0x08, 0x10, 0x00, 0x10, 0x00, // U+003F (?) 0x38, 0x4c, 0x54, 0x54, 0x4c, 0x40, 0x38, 0x00, // U+0040 (@) 0x38, 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x00, // U+0041 (A) 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x78, 0x00, // U+0042 (B) 0x38, 0x44, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00, // U+0043 (C) 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00, // U+0044 (D) 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7c, 0x00, // U+0045 (E) 0x7c, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x00, // U+0046 (F) 0x38, 0x44, 0x40, 0x5c, 0x44, 0x44, 0x38, 0x00, // U+0047 (G) 0x44, 0x44, 0x44, 0x7c, 0x44, 0x44, 0x44, 0x00, // U+0048 (H) 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, // U+0049 (I) 0x04, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38, 0x00, // U+004A (J) 0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x00, // U+004B (K) 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x00, // U+004C (L) 0x44, 0x6c, 0x54, 0x44, 0x44, 0x44, 0x44, 0x00, // U+004D (M) 0x44, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x44, 0x00, // U+004E (N) 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, // U+004F (O) 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x00, // U+0050 (P) 0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x0c, // U+0051 (Q) 0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x44, 0x00, // U+0052 (R) 0x38, 0x44, 0x40, 0x38, 0x04, 0x44, 0x38, 0x00, // U+0053 (S) 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, // U+0054 (T) 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, // U+0055 (U) 0x44, 0x44, 0x44, 0x44, 0x28, 0x28, 0x10, 0x00, // U+0056 (V) 0x44, 0x44, 0x44, 0x44, 0x54, 0x6c, 0x44, 0x00, // U+0057 (W) 0x44, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00, // U+0058 (X) 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00, // U+0059 (Y) 0x7c, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7c, 0x00, // U+005A (Z) 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x18, 0x00, // U+005B ([) 0x40, 0x40, 0x20, 0x10, 0x08, 0x04, 0x04, 0x00, // U+005C (\) 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x30, 0x00, // U+005D (]) 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, // U+005E (^) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, // U+005F (_) 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0060 (`) 0x00, 0x00, 0x3c, 0x44, 0x44, 0x44, 0x3c, 0x00, // U+0061 (a) 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x78, 0x00, // U+0062 (b) 0x00, 0x00, 0x38, 0x44, 0x40, 0x44, 0x38, 0x00, // U+0063 (c) 0x04, 0x04, 0x3c, 0x44, 0x44, 0x44, 0x3c, 0x00, // U+0064 (d) 0x00, 0x00, 0x38, 0x44, 0x7c, 0x40, 0x38, 0x00, // U+0065 (e) 0x18, 0x24, 0x20, 0x78, 0x20, 0x20, 0x20, 0x00, // U+0066 (f) 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x38, // U+0067 (g) 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, // U+0068 (h) 0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x7c, 0x00, // U+0069 (i) 0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x48, 0x30, // U+006A (j) 0x40, 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x00, // U+006B (k) 0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x1c, 0x00, // U+006C (l) 0x00, 0x00, 0x78, 0x54, 0x54, 0x54, 0x54, 0x00, // U+006D (m) 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, // U+006E (n) 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, // U+006F (o) 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x78, 0x40, // U+0070 (p) 0x00, 0x00, 0x3c, 0x44, 0x44, 0x44, 0x3c, 0x04, // U+0071 (q) 0x00, 0x00, 0x58, 0x64, 0x40, 0x40, 0x40, 0x00, // U+0072 (r) 0x00, 0x00, 0x3c, 0x40, 0x38, 0x04, 0x78, 0x00, // U+0073 (s) 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x1c, 0x00, // U+0074 (t) 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x3c, 0x00, // U+0075 (u) 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00, // U+0076 (v) 0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x28, 0x00, // U+0077 (w) 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // U+0078 (x) 0x00, 0x00, 0x44, 0x44, 0x44, 0x3C, 0x08, 0x70, // U+0079 (y) 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00, // U+007A (z) 0x08, 0x10, 0x10, 0x20, 0x10, 0x10, 0x08, 0x00, // U+007B ({) 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, // U+007C (|) 0x20, 0x10, 0x10, 0x08, 0x10, 0x10, 0x20, 0x00, // U+007D (}) 0x00, 0x00, 0x24, 0x58, 0x00, 0x00, 0x00, 0x00, // U+007E (~) 0x10, 0x38, 0x6c, 0x44, 0x44, 0x7c, 0x00, 0x00 // U+007F (©) }; jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/font/8x8.h000066400000000000000000000203441523150047600223350ustar00rootroot00000000000000// U+0000 - U+007F - Public Domain 8x8 console font static uint8_t eightbyeight[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0000 (nul) 0x7e, 0x81, 0xa5, 0x81, 0x9d, 0xb9, 0x81, 0x7e, // U+0001 0x7e, 0xff, 0xdb, 0xff, 0xe3, 0xc7, 0xff, 0x7e, // U+0002 0x6c, 0xfe, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00, // U+0003 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00, // U+0004 0x38, 0x7c, 0x38, 0xfe, 0xfe, 0x10, 0x10, 0x7c, // U+0005 0x00, 0x18, 0x3c, 0x7e, 0xff, 0x7e, 0x18, 0x7e, // U+0006 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00, 0x00, // U+0007 0xff, 0xff, 0xe7, 0xc3, 0xc3, 0xe7, 0xff, 0xff, // U+0008 0x00, 0x3c, 0x66, 0x42, 0x42, 0x66, 0x3c, 0x00, // U+0009 0xff, 0xc3, 0x99, 0xbd, 0xbd, 0x99, 0xc3, 0xff, // U+000A 0x0f, 0x07, 0x0f, 0x7d, 0xcc, 0xcc, 0xcc, 0x78, // U+000B 0x3c, 0x66, 0x66, 0x66, 0x3c, 0x18, 0x7e, 0x18, // U+000C 0x3f, 0x33, 0x3f, 0x30, 0x30, 0x70, 0xf0, 0xe0, // U+000D 0x7f, 0x63, 0x7f, 0x63, 0x63, 0x67, 0xe6, 0xc0, // U+000E 0x99, 0x5a, 0x3c, 0xe7, 0xe7, 0x3c, 0x5a, 0x99, // U+000F 0x80, 0xe0, 0xf8, 0xfe, 0xf8, 0xe0, 0x80, 0x00, // U+0010 0x02, 0x0e, 0x3e, 0xfe, 0x3e, 0x0e, 0x02, 0x00, // U+0011 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x7e, 0x3c, 0x18, // U+0012 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x00, // U+0013 0x7f, 0xdb, 0xdb, 0x7b, 0x1b, 0x1b, 0x1b, 0x00, // U+0014 0x3f, 0x60, 0x7c, 0x66, 0x66, 0x3e, 0x06, 0xfc, // U+0015 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7e, 0x7e, 0x00, // U+0016 0x18, 0x3c, 0x7e, 0x18, 0x7e, 0x3c, 0x18, 0xff, // U+0017 0x18, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x00, // U+0018 0x18, 0x18, 0x18, 0x18, 0x7e, 0x3c, 0x18, 0x00, // U+0019 0x00, 0x18, 0x0c, 0xfe, 0x0c, 0x18, 0x00, 0x00, // U+001A 0x00, 0x30, 0x60, 0xfe, 0x60, 0x30, 0x00, 0x00, // U+001B 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, 0x00, // U+001C 0x00, 0x24, 0x66, 0xff, 0x66, 0x24, 0x00, 0x00, // U+001D 0x00, 0x18, 0x3c, 0x7e, 0xff, 0xff, 0x00, 0x00, // U+001E 0x00, 0xff, 0xff, 0x7e, 0x3c, 0x18, 0x00, 0x00, // U+001F 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0020 (space) 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00, // U+0021 (!) 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0022 (") 0x6c, 0x6c, 0xfe, 0x6c, 0xfe, 0x6c, 0x6c, 0x00, // U+0023 (#) 0x18, 0x7e, 0xc0, 0x7c, 0x06, 0xfc, 0x18, 0x00, // U+0024 ($) 0x00, 0xc6, 0xcc, 0x18, 0x30, 0x66, 0xc6, 0x00, // U+0025 (%) 0x38, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0x76, 0x00, // U+0026 (&) 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0027 (') 0x0c, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0c, 0x00, // U+0028 (() 0x30, 0x18, 0x0c, 0x0c, 0x0c, 0x18, 0x30, 0x00, // U+0029 ()) 0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00, // U+002A (*) 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, // U+002B (+) 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, // U+002C (,) 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, // U+002D (-) 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, // U+002E (.) 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x00, // U+002F (/) 0x7c, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0x7c, 0x00, // U+0030 (0) 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, // U+0031 (1) 0x7c, 0xc6, 0x06, 0x7c, 0xc0, 0xc0, 0xfe, 0x00, // U+0032 (2) 0xfc, 0x06, 0x06, 0x3c, 0x06, 0x06, 0xfc, 0x00, // U+0033 (3) 0x0c, 0xcc, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x00, // U+0034 (4) 0xfe, 0xc0, 0xfc, 0x06, 0x06, 0xc6, 0x7c, 0x00, // U+0035 (5) 0x7c, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0x7c, 0x00, // U+0036 (6) 0xfe, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x00, // U+0037 (7) 0x7c, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0x7c, 0x00, // U+0038 (8) 0x7c, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x7c, 0x00, // U+0039 (9) 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, // U+003A (:) 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30, // U+003B (;) 0x0c, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0c, 0x00, // U+003C (<) 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00, // U+003D (=) 0x30, 0x18, 0x0c, 0x06, 0x0c, 0x18, 0x30, 0x00, // U+003E (>) 0x3c, 0x66, 0x0c, 0x18, 0x18, 0x00, 0x18, 0x00, // U+003F (?) 0x7c, 0xc6, 0xde, 0xde, 0xde, 0xc0, 0x7e, 0x00, // U+0040 (@) 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0x00, // U+0041 (A) 0xfc, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xfc, 0x00, // U+0042 (B) 0x7c, 0xc6, 0xc0, 0xc0, 0xc0, 0xc6, 0x7c, 0x00, // U+0043 (C) 0xf8, 0xcc, 0xc6, 0xc6, 0xc6, 0xcc, 0xf8, 0x00, // U+0044 (D) 0xfe, 0xc0, 0xc0, 0xf8, 0xc0, 0xc0, 0xfe, 0x00, // U+0045 (E) 0xfe, 0xc0, 0xc0, 0xf8, 0xc0, 0xc0, 0xc0, 0x00, // U+0046 (F) 0x7c, 0xc6, 0xc0, 0xc0, 0xce, 0xc6, 0x7c, 0x00, // U+0047 (G) 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00, // U+0048 (H) 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, // U+0049 (I) 0x06, 0x06, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, // U+004A (J) 0xc6, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xc6, 0x00, // U+004B (K) 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0x00, // U+004C (L) 0xc6, 0xee, 0xfe, 0xfe, 0xd6, 0xc6, 0xc6, 0x00, // U+004D (M) 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0x00, // U+004E (N) 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, // U+004F (O) 0xfc, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, 0xc0, 0x00, // U+0050 (P) 0x7c, 0xc6, 0xc6, 0xc6, 0xd6, 0xde, 0x7c, 0x06, // U+0051 (Q) 0xfc, 0xc6, 0xc6, 0xfc, 0xd8, 0xcc, 0xc6, 0x00, // U+0052 (R) 0x7c, 0xc6, 0xc0, 0x7c, 0x06, 0xc6, 0x7c, 0x00, // U+0053 (S) 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // U+0054 (T) 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0x00, // U+0055 (U) 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x38, 0x00, // U+0056 (V) 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0x6c, 0x00, // U+0057 (W) 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00, // U+0058 (X) 0xc6, 0xc6, 0xc6, 0x7c, 0x18, 0x30, 0xe0, 0x00, // U+0059 (Y) 0xfe, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xfe, 0x00, // U+005A (Z) 0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00, // U+005B ([) 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x02, 0x00, // U+005C (\) 0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00, // U+005D (]) 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, // U+005E (^) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // U+005F (_) 0x18, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, // U+0060 (`) 0x00, 0x00, 0x7c, 0x06, 0x7e, 0xc6, 0x7e, 0x00, // U+0061 (a) 0xc0, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xfc, 0x00, // U+0062 (b) 0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc6, 0x7c, 0x00, // U+0063 (c) 0x06, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0x7e, 0x00, // U+0064 (d) 0x00, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0x7c, 0x00, // U+0065 (e) 0x1c, 0x36, 0x30, 0x78, 0x30, 0x30, 0x78, 0x00, // U+0066 (f) 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0x7e, 0x06, 0xfc, // U+0067 (g) 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, // U+0068 (h) 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3c, 0x00, // U+0069 (i) 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0xc6, 0x7c, // U+006A (j) 0xc0, 0xc0, 0xcc, 0xd8, 0xf8, 0xcc, 0xc6, 0x00, // U+006B (k) 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00, // U+006C (l) 0x00, 0x00, 0xcc, 0xfe, 0xfe, 0xd6, 0xd6, 0x00, // U+006D (m) 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, // U+006E (n) 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, // U+006F (o) 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, // U+0070 (p) 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0x7e, 0x06, 0x06, // U+0071 (q) 0x00, 0x00, 0xfc, 0xc6, 0xc0, 0xc0, 0xc0, 0x00, // U+0072 (r) 0x00, 0x00, 0x7e, 0xc0, 0x7c, 0x06, 0xfc, 0x00, // U+0073 (s) 0x18, 0x18, 0x7e, 0x18, 0x18, 0x18, 0x0e, 0x00, // U+0074 (t) 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, // U+0075 (u) 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x7c, 0x38, 0x00, // U+0076 (v) 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xfe, 0x6c, 0x00, // U+0077 (w) 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x00, // U+0078 (x) 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0xfc, // U+0079 (y) 0x00, 0x00, 0xfe, 0x0c, 0x38, 0x60, 0xfe, 0x00, // U+007A (z) 0x0e, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0e, 0x00, // U+007B ({) 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, // U+007C (|) 0x70, 0x18, 0x18, 0x0e, 0x18, 0x18, 0x70, 0x00, // U+007D (}) 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // U+007E (~) 0x00, 0x10, 0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0x00 // U+007F (©) }; jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/input.c000066400000000000000000001067141523150047600221000ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "tconfig.h" #include "jgrf.h" #include "audio.h" #include "cheats.h" #include "menu.h" #include "settings.h" #include "video.h" #include "input.h" #include "osd.h" #define MAXPORTS 12 #define MAXAXES 6 #define MAXBUTTONS 32 #define BDEADZONE 16384 // Deadzone for axes acting as buttons #define jgrf_getticks SDL_GetTicks // Pointers to members of core input state typedef struct jgrf_jsmap_t { int16_t *axis[MAXAXES]; uint8_t *abtn[MAXAXES * 2]; // Axes acting as buttons uint8_t *hatpos[4]; uint8_t *button[MAXBUTTONS]; } jgrf_jsmap_t; typedef struct jgrf_kbmap_t { uint8_t *key[SDL_SCANCODE_COUNT]; } jgrf_kbmap_t; typedef struct jgrf_msmap_t { uint8_t index; uint8_t *button[MAXBUTTONS]; } jgrf_msmap_t; static jg_videoinfo_t *vidinfo; // Video Info for calculating mouse input static jg_inputinfo_t *inputinfo[MAXPORTS]; // Core Input Info static jg_inputstate_t coreinput[MAXPORTS]; // Input states shared with core static jg_setting_t *settings = NULL; static jgrf_jsmap_t jsmap[MAXPORTS]; // Pointer maps for joystick/gamepad input static unsigned rumblemap[MAXPORTS]; // Ensure force feedback lines up properly static jgrf_kbmap_t kbmap; // Pointer map for keyboard input static jgrf_msmap_t msmap; // Pointer map for mouse input static jgrf_gdata_t *gdata; // Global data pointer // SDL Joystick and Haptic pointers static SDL_Joystick *joystick[MAXPORTS]; // Arrays to keep track of what joystick ports have a device plugged in static int jsports[MAXPORTS]; static SDL_JoystickID jsiid[MAXPORTS]; // Joystick Instance ID // Array to keep track of what axes are triggers vs regular "stick" axes static unsigned trigger[MAXPORTS]; /* Bitmask of axes the gamepad mapping reports as inverted. Derived per-device from the mapping itself (not gated on platform), so MFi pads on macOS that invert Y get corrected while already-correct pads are left untouched. */ static unsigned axisinv[MAXPORTS]; // Undefined inputs point here - this is like /dev/null for input events static uint8_t undef8; static int16_t undef16; // Configuration related globals static ini_table_s *iconf; static int confactive = 0; static int confchanged = 0; static int confindex = 0; static jg_inputinfo_t *confiinfo = NULL; // Currently configured device static int conf_last_axis = -1; // Last axis that was configured static int conf_last_port = -1; // Port of last configured axis static uint64_t conftimer = 0; static int menuactive = 0; static int discard_keyup = 0; static int deadzone = 5120; // Keyboard Mode static int kbmode = 0; // Default to Keyboard Mode (hotkeys disabled) off void jgrf_input_config_enable(int e) { confactive = e; } void jgrf_input_menu_enable(int e) { menuactive = e; if (!jgrf_get_paused()) jgrf_osd_toggle(menuactive); } // Map a core input axis definition void jgrf_input_map_axis(int index, uint32_t dnum, const char* value) { //printf("axis: %d, %d, %s\n", index, dnum, value); if (value[0] == 'j') { // Joystick uint8_t inum = value[1] - '0'; // Index (Frontend) if (value[2] == 'a') { // Axis int anum = atoi(&value[3]); jsmap[inum].axis[anum] = &(coreinput[index].axis[dnum]); rumblemap[inum] = index; // Synchronize force feedback with axes } } conftimer = jgrf_getticks(); } // Map a core input button definition void jgrf_input_map_button(int index, uint32_t dnum, const char* value) { //printf("button: %d, %d, %s\n", index, dnum, value); if (value[0] == 'j') { // Joystick uint8_t inum = value[1] - '0'; // Index (Frontend) if (value[2] == 'a') { // Axis acting as Button int anum = atoi(&value[3]) * 2; if (value[4] == '+') anum++; jsmap[inum].abtn[anum] = &(coreinput[index].button[dnum]); } else if (value[2] == 'b') { // Button int bnum = atoi(&value[3]); jsmap[inum].button[bnum] = &(coreinput[index].button[dnum]); } else if (value[2] == 'h') { // Hat switch uint8_t hinum = value[1] - '0'; // Index (Frontend) // Assume hat switch 0 - are there devices with more than one? jsmap[hinum].hatpos[value[4] - '0'] = &(coreinput[index].button[dnum]); } } else if (value[0] == 'm') { // Mouse if (value[1] == 'b') { int bnum = atoi(&value[2]); msmap.index = index; msmap.button[bnum] = &(coreinput[index].button[dnum]); } } else { // Keyboard int knum = atoi(value); kbmap.key[knum] = &(coreinput[index].button[dnum]); *kbmap.key[knum] = 0; } conftimer = jgrf_getticks(); } // Read an input config file and assign inputs static void jgrf_inputcfg_read(jg_inputinfo_t *iinfo) { char path[256]; snprintf(path, sizeof(path), "%s%s_input.ini", gdata->configpath, gdata->corename); for (int i = 0; i < iinfo->numaxes; ++i) { if (ini_table_check_entry(iconf, iinfo->name, iinfo->defs[i])) { jgrf_input_map_axis(iinfo->index, i, ini_table_get_entry(iconf, iinfo->name, iinfo->defs[i])); } } for (int i = 0; i < iinfo->numbuttons; ++i) { if (ini_table_check_entry(iconf, iinfo->name, iinfo->defs[i + iinfo->numaxes])) { jgrf_input_map_button(iinfo->index, i, ini_table_get_entry(iconf, iinfo->name, iinfo->defs[i + iinfo->numaxes])); } } } // Unmap/undefine all joystick map pointers static void jgrf_input_undef_port(int port) { for (int j = 0; j < MAXAXES; ++j) { jsmap[port].axis[j] = &undef16; jsmap[port].abtn[j * 2] = jsmap[port].abtn[(j * 2) + 1] = &undef8; } for (int j = 0; j < 4; ++j) jsmap[port].hatpos[j] = &undef8; for (int j = 0; j < MAXBUTTONS; ++j) jsmap[port].button[j] = &undef8; } // Set all joystick mappings to undefined void jgrf_input_undef(void) { for (int i = 0; i < MAXPORTS; ++i) { jgrf_input_undef_port(i); } // Set all keyboard mappings to undefined for (int j = 0; j < SDL_SCANCODE_COUNT; ++j) kbmap.key[j] = &undef8; // Set all mouse mappings to undefined for (int j = 0; j < MAXBUTTONS; ++j) msmap.button[j] = &undef8; } // Re-apply bindings to all currently plugged in devices static void jgrf_input_rebind(void) { gdata = jgrf_gdata_ptr(); // Undefine all live mappings jgrf_input_undef(); // Re-read bindings for every plugged-in device for (int i = 0; i < gdata->numinputs; ++i) { if (inputinfo[i] && inputinfo[i]->name) { jgrf_inputcfg_read(inputinfo[i]); } } } /* Resolve a device's port from its instance id. Track this mapping manually rather than reading SDL_GetJoystickPlayerIndex(), because on macOS the GameController framework owns the player index and reassigns it asynchronously after a device is opened, which misroutes or drops input events. */ static int jgrf_input_port(SDL_JoystickID which) { for (int i = 0; i < MAXPORTS; ++i) { if (jsports[i] && jsiid[i] == which) return i; } return -1; } // Initialize input int jgrf_input_init(void) { // Grab pointer to settings settings = jgrf_settings_ptr(); // Set the axis deadzone deadzone = settings[INPUT_AXIS_DEADZONE].val * 1024; jgrf_input_undef(); // Initialize the input configuration structure char path[256]; snprintf(path, sizeof(path), "%s%s_input.ini", gdata->configpath, gdata->corename); iconf = ini_table_create(); if (!ini_table_read_from_file(iconf, path)) jgrf_log(JG_LOG_WRN, "Input configuration file not found: %s\n", path); // Set pointer to video info - Move coordinate computation? vidinfo = jgrf_video_get_info(); return 1; } // Deinitialize input and save changes if necessary void jgrf_input_deinit(void) { // Deinitialize joysticks int numjs = 0; SDL_JoystickID *jsids = SDL_GetJoysticks(&numjs); if (jsids) SDL_free(jsids); for (int i = 0; i < MAXPORTS; ++i) { if (joystick[i]) { SDL_CloseJoystick(joystick[i]); joystick[i] = NULL; } } jgrf_input_deinit_core(); // Write out the input config file if (confchanged) { char path[256]; snprintf(path, sizeof(path), "%s%s_input.ini", gdata->configpath, gdata->corename); ini_table_write_to_file(iconf, path); } // Clean up the input config data ini_table_destroy(iconf); } // Free memory allocated for core input states void jgrf_input_deinit_core(void) { for (int i = 0; i < MAXPORTS; ++i) { if (coreinput[i].axis) free(coreinput[i].axis); if (coreinput[i].button) free(coreinput[i].button); if (coreinput[i].coord) free(coreinput[i].coord); if (coreinput[i].rel) free(coreinput[i].rel); } } // Rehash input void jgrf_input_rehash(void) { // Grab pointer to settings settings = jgrf_settings_ptr(); deadzone = settings[INPUT_AXIS_DEADZONE].val * 1024; } // Handle joystick hotplug additions static void jgrf_input_hotplug_add(SDL_Event *event) { SDL_JoystickID which = event->jdevice.which; int port = 0; /* Ignore if this device is already tracked. Startup enumeration and the queued ADDED event can reference the same device. Without this guard, it would be opened twice and occupy two ports. */ if (jgrf_input_port(which) != -1) return; for (int i = 0; i < MAXPORTS; ++i) { if (!jsports[i]) { joystick[i] = SDL_OpenJoystick(which); if (!joystick[i]) { // Opening can fail (e.g. macOS permissions) jgrf_log(JG_LOG_WRN, "Failed to open joystick: %s\n", SDL_GetError()); return; } SDL_SetJoystickPlayerIndex(joystick[i], i); jsports[i] = 1; jsiid[i] = SDL_GetJoystickID(joystick[i]); port = i; jgrf_log(JG_LOG_INF, "Joystick %d Connected: %s\n", port + 1, SDL_GetJoystickName(joystick[port])); if (SDL_IsGamepad(event->jdevice.which)) { trigger[i] = 0; axisinv[i] = 0; SDL_Gamepad *gp = SDL_OpenGamepad(event->jdevice.which); int nbindings = 0; SDL_GamepadBinding **bindings = SDL_GetGamepadBindings(gp, &nbindings); if (bindings) { for (int b = 0; b < nbindings; ++b) { if (bindings[b]->output_type == SDL_GAMEPAD_BINDTYPE_AXIS) { if (bindings[b]->output.axis.axis == SDL_GAMEPAD_AXIS_LEFT_TRIGGER && bindings[b]->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) { trigger[port] |= 1 << bindings[b]->input.axis.axis; } if (bindings[b]->output.axis.axis == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER && bindings[b]->input_type == SDL_GAMEPAD_BINDTYPE_AXIS) { trigger[port] |= 1 << bindings[b]->input.axis.axis; } /* Honour inverted stick axes as declared by the gamepad mapping. An axis is inverted when its input and output ranges run in opposite directions (one ascending, one descending). This is how MFi controllers on macOS surface their inverted Y to the raw joystick layer. Triggers are excluded - they are handled above. */ if (bindings[b]->input_type == SDL_GAMEPAD_BINDTYPE_AXIS && bindings[b]->output.axis.axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && bindings[b]->output.axis.axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { int in_desc = bindings[b]->input.axis.axis_min > bindings[b]->input.axis.axis_max; int out_desc = bindings[b]->output.axis.axis_min > bindings[b]->output.axis.axis_max; if (in_desc != out_desc) { axisinv[port] |= 1 << bindings[b]->input.axis.axis; jgrf_log(JG_LOG_DBG, "Axis %d inverted\n", bindings[b]->input.axis.axis); } } } } SDL_free(bindings); } SDL_CloseGamepad(gp); } // Normalize initial axis values: minimum for trigger, 0 for center int numaxes = SDL_GetNumJoystickAxes(joystick[i]); for (int j = 0; j < numaxes; ++j) *jsmap[i].axis[j] = (trigger[i] & (1 << j)) ? -32768 : 0; break; } } } // Handle joystick hotplug removals static void jgrf_input_hotplug_remove(SDL_Event *event) { for (int i = 0; i < MAXPORTS; ++i) { // If it's the one that got disconnected... if (jsiid[i] == event->jdevice.which) { jsports[i] = 0; // This is unplugged jgrf_log(JG_LOG_INF, "Joystick %d Disconnected\n", i + 1); SDL_CloseJoystick(joystick[i]); joystick[i] = NULL; jsports[i] = 0; break; } } } // Retrieve inputinfo data so the frontend knows what the core has plugged in void jgrf_input_query(jg_inputinfo_t* (*get_inputinfo)(int)) { // Make sure cursor state is defaulted SDL_HideCursor(); SDL_SetCursor(NULL); for (int i = 0; i < gdata->numinputs; ++i) { inputinfo[i] = get_inputinfo(i); if (inputinfo[i]->name) { jgrf_log(JG_LOG_INF, "Emulated Input %d: %s, %s, %d axes, %d buttons\n", i + 1, inputinfo[i]->name, inputinfo[i]->fname, inputinfo[i]->numaxes, inputinfo[i]->numbuttons); // Allocate memory for the emulated buttons/axes/coords coreinput[i].axis = (int16_t*)calloc(inputinfo[i]->numaxes, sizeof(int16_t)); coreinput[i].button = (uint8_t*)calloc(inputinfo[i]->numbuttons, sizeof(uint8_t)); coreinput[i].coord = // There are always X, Y, and Z coords (int32_t*)calloc(3, sizeof(int32_t)); // Magic Number coreinput[i].rel = // There is always X and Y relative motion (int32_t*)calloc(2, sizeof(int32_t)); // Magic Number // Read configuration for this emulated device jgrf_inputcfg_read(inputinfo[i]); } if (inputinfo[i]->type == JG_INPUT_GUN) jgrf_video_set_cursor(SDL_SYSTEM_CURSOR_CROSSHAIR); else if (inputinfo[i]->type == JG_INPUT_TOUCH) jgrf_video_set_cursor(SDL_SYSTEM_CURSOR_POINTER); } } // Pass pointers to input states into the core void jgrf_input_set_states(void (*set_inputstate)(jg_inputstate_t*, int)) { gdata = jgrf_gdata_ptr(); for(int i = 0; i < gdata->numinputs; ++i) set_inputstate(&coreinput[i], i); } // Calculate mouse input coordinates, taking into account aspect ratio and scale static inline void jgrf_input_coords_scaled(float x, float y, int32_t *xcoord, int32_t *ycoord) { float xscale, yscale, xo, yo; jgrf_video_get_scale_params(&xscale, &yscale, &xo, &yo); *xcoord = (x - xo) / ((vidinfo->aspect * vidinfo->h * xscale)/(float)vidinfo->w); *ycoord = ((y - yo) / yscale) + vidinfo->y; } // Run an input configuration iteration to set up a specific definition static void jgrf_inputcfg(jg_inputinfo_t *iinfo) { if (confindex >= (iinfo->numaxes + iinfo->numbuttons)) { confactive = 0; // Turn off input config mode confindex = 0; jgrf_video_text(2, 0, ""); // Disable display of input config info jgrf_input_rebind(); // Apply newly saved bindings to live devices if (menuactive) jgrf_menu_text_redraw(); // Turn the menu text back on return; } else { confactive = 1; conftimer = jgrf_getticks(); } // Display input config information on screen if (iinfo->name) { char msg[128]; snprintf(msg, sizeof(msg), "%s\n%s\n%d axes, %d buttons\nConfigure %s", iinfo->fname, iinfo->name, iinfo->numaxes, iinfo->numbuttons, iinfo->defs[confindex]); jgrf_video_text(2, 1, msg); } } // Create config definitions from input events, then configure them to be used static void jgrf_inputcfg_handler(SDL_Event *event) { char defbuf[32]; int axis_configured = -1; switch(event->type) { case SDL_EVENT_KEY_UP: { if (discard_keyup) { --discard_keyup; break; } if (event->key.scancode == SDL_SCANCODE_ESCAPE) { ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], ""); confindex++; jgrf_inputcfg(confiinfo); break; } if (event->key.repeat) break; if (confindex < confiinfo->numaxes) { jgrf_log(JG_LOG_WRN, "Trying to assign digital inputs to axes" " is a losing endeavour. ESC to skip.\n"); jgrf_log(JG_LOG_SCR, "Analog input required"); break; } snprintf(defbuf, sizeof(defbuf), "%d", event->key.scancode); ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); confindex++; jgrf_inputcfg(confiinfo); break; } case SDL_EVENT_JOYSTICK_BUTTON_DOWN: { if (confindex < confiinfo->numaxes) { jgrf_log(JG_LOG_WRN, "Trying to assign digital inputs to axes" " is a losing endeavour. ESC to skip.\n"); jgrf_log(JG_LOG_SCR, "Analog input required"); break; } int port = jgrf_input_port(event->jbutton.which); if (port < 0) break; snprintf(defbuf, sizeof(defbuf), "j%db%d", port, event->jbutton.button); ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); confindex++; jgrf_inputcfg(confiinfo); break; } case SDL_EVENT_JOYSTICK_AXIS_MOTION: { int port = jgrf_input_port(event->jaxis.which); if (port < 0) break; // Triggers require special handling if (trigger[port] & (1 << event->jaxis.axis)) { // Axes set to axis input if (confindex < confiinfo->numaxes) { if (event->jaxis.value >= BDEADZONE) { snprintf(defbuf, sizeof(defbuf), "j%da%d", port, event->jaxis.axis); ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); axis_configured = port; } } else { // Axes set to button input if (event->jaxis.value >= BDEADZONE) { snprintf(defbuf, sizeof(defbuf), "j%da%d+", port, event->jaxis.axis); ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); axis_configured = port; } } } else { // Normal axis // Axes set to axis input if (confindex < confiinfo->numaxes) { if (abs(event->jaxis.value) >= BDEADZONE) { snprintf(defbuf, sizeof(defbuf), "j%da%d", port, event->jaxis.axis); ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); axis_configured = port; } } else { // Axes set to button input if (abs(event->jaxis.value) >= 32767) { // Handle the case of hat switches pretending to be axes snprintf(defbuf, sizeof(defbuf), "j%da%d%c", port, event->jaxis.axis, event->jaxis.value > 0 ? '+' : '-'); ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); axis_configured = port; break; } if (abs(event->jaxis.value) >= BDEADZONE) { snprintf(defbuf, sizeof(defbuf), "j%da%d%c", port, event->jaxis.axis, event->jaxis.value > 0 ? '+' : '-'); ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); axis_configured = port; } } } break; } case SDL_EVENT_JOYSTICK_HAT_MOTION: { if (confindex < confiinfo->numaxes) { jgrf_log(JG_LOG_WRN, "Trying to assign digital inputs to axes" " is a losing endeavour. ESC to skip.\n"); jgrf_log(JG_LOG_SCR, "Analog input required"); break; } int port = jgrf_input_port(event->jhat.which); if (port < 0) break; if (event->jhat.value & SDL_HAT_UP) snprintf(defbuf, sizeof(defbuf), "j%dh00", port); else if (event->jhat.value & SDL_HAT_DOWN) snprintf(defbuf, sizeof(defbuf), "j%dh01", port); else if (event->jhat.value & SDL_HAT_LEFT) snprintf(defbuf, sizeof(defbuf), "j%dh02", port); else if (event->jhat.value & SDL_HAT_RIGHT) snprintf(defbuf, sizeof(defbuf), "j%dh03", port); if (event->jhat.value != 0) { ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); confindex++; jgrf_inputcfg(confiinfo); } break; } case SDL_EVENT_MOUSE_BUTTON_DOWN: { if (confindex < confiinfo->numaxes) break; snprintf(defbuf, sizeof(defbuf), "mb%d", event->button.button); ini_table_create_entry(iconf, confiinfo->name, confiinfo->defs[confindex], defbuf); confindex++; jgrf_inputcfg(confiinfo); break; } case SDL_EVENT_JOYSTICK_ADDED: { jgrf_input_hotplug_add(event); break; } case SDL_EVENT_JOYSTICK_REMOVED: { jgrf_input_hotplug_remove(event); break; } default: { break; } } if (axis_configured != -1) { confindex++; jgrf_inputcfg(confiinfo); conf_last_axis = event->jaxis.axis; conf_last_port = axis_configured; } } // Main input event handler void jgrf_input_handler(SDL_Event *event) { /* Correct inverted stick axes at the source (e.g. MFi Y on macOS), once, before any branch consumes the event. This keeps both input configuration and gameplay reading SDL-convention values. The event is our own local copy, so mutating it here is safe. */ if (event->type == SDL_EVENT_JOYSTICK_AXIS_MOTION) { int port = jgrf_input_port(event->jaxis.which); if (port >= 0 && (axisinv[port] & (1 << event->jaxis.axis))) event->jaxis.value = (event->jaxis.value == INT16_MIN) ? INT16_MAX : -event->jaxis.value; } if (confactive) { unsigned delay = 60; // Delay consecutive inputs by 60 ticks if (event->type == SDL_EVENT_JOYSTICK_AXIS_MOTION) { /* In some cases, there is an extra axis which takes input from two other axes (triggers), which shows up in the SDL event queue before the legitimate axes. This is a hack to ignore events on the extra axis, which will have an even index, since we count from 0 and axes typically come in pairs. Reference: https://github.com/atar-axis/xpadneo/issues/334 */ int port = jgrf_input_port(event->jaxis.which); if (port < 0) return; // If this is the axis we just configured, ignore until neutral if (port == conf_last_port && event->jaxis.axis == conf_last_axis) { if (abs(event->jaxis.value) < BDEADZONE) { // Axis returned to neutral, clear the lock conf_last_axis = -1; conf_last_port = -1; } return; // Ignore this event either way } int extra = SDL_GetNumJoystickAxes(joystick[port]) - 1; if ((event->jaxis.axis == extra) && !(extra & 1)) return; delay = 420; // Larger delay required for axes } // Determine ticks since the last input definition was configured uint64_t delta = jgrf_getticks() - conftimer; // If the delta is large enough, pass the event to input config if (delta > delay) jgrf_inputcfg_handler(event); return; } else if (menuactive) { switch (event->type) { case SDL_EVENT_KEY_UP: jgrf_menu_input_handler(event); break; case SDL_EVENT_JOYSTICK_ADDED: jgrf_input_hotplug_add(event); break; case SDL_EVENT_JOYSTICK_REMOVED: jgrf_input_hotplug_remove(event); break; default: break; } return; } // Check if we need to change keyboard mode // SDL3: event->key.mod replaces event->key.keysym.mod // SDL3: event->key.scancode replaces event->key.keysym.scancode if ((event->key.mod & SDL_KMOD_SHIFT) && (event->key.scancode == SDL_SCANCODE_TAB)) { if (event->type == SDL_EVENT_KEY_UP) { kbmode ^= 1; jgrf_log(JG_LOG_SCR, "Hotkeys %s", kbmode ? "Disabled" : "Enabled"); } return; } // This needs to be fixed and worked into the rest of the system one day... if (!kbmode && (event->type == SDL_EVENT_KEY_UP || event->type == SDL_EVENT_KEY_DOWN)) { switch (event->key.scancode) { case SDL_SCANCODE_ESCAPE: { jgrf_schedule_quit(); break; } case SDL_SCANCODE_GRAVE: { jgrf_set_speed(event->type == SDL_EVENT_KEY_DOWN ? 1 : 0); break; } case SDL_SCANCODE_F1: { if (event->type == SDL_EVENT_KEY_UP) jgrf_reset(0); break; } case SDL_SCANCODE_F2: { if (event->type == SDL_EVENT_KEY_UP) jgrf_reset(1); break; } case SDL_SCANCODE_F5: { if (event->type == SDL_EVENT_KEY_UP) jgrf_state_save(0); break; } case SDL_SCANCODE_F6: { if (event->type == SDL_EVENT_KEY_UP) jgrf_state_save(1); break; } case SDL_SCANCODE_F7: { if (event->type == SDL_EVENT_KEY_UP) jgrf_state_load(0); break; } case SDL_SCANCODE_F8: { if (event->type == SDL_EVENT_KEY_UP) jgrf_state_load(1); break; } case SDL_SCANCODE_F9: { if (event->type == SDL_EVENT_KEY_UP) jgrf_video_screenshot(); break; } case SDL_SCANCODE_F12: { if (event->type == SDL_EVENT_KEY_UP) jgrf_cheats_toggle(); break; } case SDL_SCANCODE_F: { if (event->type == SDL_EVENT_KEY_UP) jgrf_video_fullscreen(); break; } case SDL_SCANCODE_M: { if (event->type == SDL_EVENT_KEY_UP) jgrf_audio_toggle(); break; } case SDL_SCANCODE_P: { if (event->type == SDL_EVENT_KEY_UP) jgrf_pause_toggle(); break; } case SDL_SCANCODE_1: case SDL_SCANCODE_2: case SDL_SCANCODE_3: case SDL_SCANCODE_4: case SDL_SCANCODE_5: case SDL_SCANCODE_6: case SDL_SCANCODE_7: case SDL_SCANCODE_8: { // Want to play 12-player sports games? Hand-edit the config file. if (event->type == SDL_EVENT_KEY_UP && (event->key.mod & SDL_KMOD_SHIFT)) { int port = atoi(SDL_GetScancodeName(event->key.scancode)); if (port > gdata->numinputs || port < 1) break; port--; /* Discard a keyup event to prevent it from registering as a desired input definition */ discard_keyup = 1; jgrf_input_config(inputinfo[port]); } break; } case SDL_SCANCODE_TAB: { if (event->type == SDL_EVENT_KEY_UP) { jgrf_input_menu_enable(1); jgrf_menu_display(); } return; } default: { break; } } } // Game input events switch(event->type) { case SDL_EVENT_KEY_UP: { *kbmap.key[event->key.scancode] = 0; break; } case SDL_EVENT_KEY_DOWN: { *kbmap.key[event->key.scancode] = 1; break; } case SDL_EVENT_JOYSTICK_BUTTON_UP: { int port = jgrf_input_port(event->jbutton.which); if (port < 0) break; *jsmap[port].button[event->jbutton.button] = 0; break; } case SDL_EVENT_JOYSTICK_BUTTON_DOWN: { int port = jgrf_input_port(event->jbutton.which); if (port < 0) break; *jsmap[port].button[event->jbutton.button] = 1; break; } case SDL_EVENT_JOYSTICK_AXIS_MOTION: { int port = jgrf_input_port(event->jaxis.which); if (port < 0) break; // Value is already corrected for inversion at the handler entry. *jsmap[port].axis[event->jaxis.axis] = abs(event->jaxis.value) > deadzone ? event->jaxis.value : 0; if (abs(event->jaxis.value) > BDEADZONE) { *jsmap[port].abtn[event->jaxis.axis * 2] = event->jaxis.value < 0; *jsmap[port].abtn[(event->jaxis.axis * 2) + 1] = event->jaxis.value > 0; } else { *jsmap[port].abtn[event->jaxis.axis * 2] = 0; *jsmap[port].abtn[(event->jaxis.axis * 2) + 1] = 0; } break; } case SDL_EVENT_JOYSTICK_HAT_MOTION: { int port = jgrf_input_port(event->jhat.which); if (port < 0) break; *jsmap[port].hatpos[0] = event->jhat.value & SDL_HAT_UP; *jsmap[port].hatpos[1] = (event->jhat.value & SDL_HAT_DOWN) >> 2; *jsmap[port].hatpos[2] = (event->jhat.value & SDL_HAT_LEFT) >> 3; *jsmap[port].hatpos[3] = (event->jhat.value & SDL_HAT_RIGHT) >> 1; break; } case SDL_EVENT_MOUSE_MOTION: { jgrf_input_coords_scaled(event->motion.x, event->motion.y, &coreinput[msmap.index].coord[0], &coreinput[msmap.index].coord[1]); coreinput[msmap.index].rel[0] += event->motion.xrel; coreinput[msmap.index].rel[1] += event->motion.yrel; break; } case SDL_EVENT_MOUSE_BUTTON_UP: { *msmap.button[event->button.button] = 0; break; } case SDL_EVENT_MOUSE_BUTTON_DOWN: { *msmap.button[event->button.button] = 1; break; } case SDL_EVENT_JOYSTICK_ADDED: { jgrf_input_hotplug_add(event); break; } case SDL_EVENT_JOYSTICK_REMOVED: { jgrf_input_hotplug_remove(event); break; } default: { break; } } } // Callback used by core to rumble the physical input device void jgrf_input_rumble(void *udata, int port, float strength, size_t len) { (void)udata; uint16_t str = (uint16_t)(strength * 0xFFFF); if (len == 0) SDL_RumbleJoystick(joystick[rumblemap[port]], 0, 0, 0); else SDL_RumbleJoystick(joystick[rumblemap[port]], str, str, len); } void jgrf_input_config(jg_inputinfo_t *iinfo) { if (!iinfo || !iinfo->name) return; confiinfo = iinfo; confindex = 0; confchanged = 1; jgrf_inputcfg(confiinfo); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/input.h000066400000000000000000000041251523150047600220760ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef INPUT_H #define INPUT_H int jgrf_input_init(void); void jgrf_input_deinit(void); void jgrf_input_deinit_core(void); void jgrf_input_rehash(void); void jgrf_input_query(jg_inputinfo_t *(*)(int)); void jgrf_input_set_states(void (*)(jg_inputstate_t*, int)); void jgrf_input_handler(SDL_Event*); void jgrf_input_rumble(void*, int, float, size_t); void jgrf_input_undef(void); void jgrf_input_map_axis(int, uint32_t, const char*); void jgrf_input_map_button(int, uint32_t, const char*); void jgrf_input_config_enable(int); void jgrf_input_menu_enable(int); void jgrf_input_config(jg_inputinfo_t *iinfo); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/jgrf.c000066400000000000000000000677371523150047600217040ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include "md5.h" #include "miniz.h" #include "jgrf.h" #include "audio.h" #include "cheats.h" #include "cli.h" #include "detect.h" #include "dips.h" #include "input.h" #include "menu.h" #include "osd.h" #include "paths.h" #include "settings.h" #include "video.h" // Jolly Good API calls static jgapi_t jgapi; // Keep track of which internal systems have been loaded successfully static struct _loaded { int core; int game; int audio; int video; int input; int settings; } loaded = { 0, 0, 0, 0, 0, 0 }; // Pointer to core info struct static jg_coreinfo_t *coreinfo = NULL; // Frontend knows the game and path info and passes this to the core static jg_fileinfo_t gameinfo; static jg_fileinfo_t auxinfo[JGRF_AUXFILE_MAX]; static jg_pathinfo_t pathinfo; // Global data struct for miscellaneous information static jgrf_gdata_t gdata; jgrf_gdata_t *jgrf_gdata_ptr(void) { return &gdata; } jgapi_t *jgrf_jgapi_ptr(void) { return &jgapi; } // Program should keep running (1) or, shut down (0) static int running = 1; // Benchmark mode int bmark = 0; // Pause int pause = 0; // Number of extra frames to run for fast-forwarding purposes int fforward = 0; // Frame timing int corefps = 60; int screenfps = 60; size_t framecount = 0; size_t bmarkframes = 0; // Discern the name of the game, with and without file extension static void jgrf_gamename(const char *filename) { // Set game's name based on the path snprintf(gdata.gamename, sizeof(gdata.gamename), "%s", basename((char*)filename)); snprintf(gdata.gamefname, sizeof(gdata.gamefname), "%s", basename((char*)filename)); // Strip the file extension off for (int i = strlen(gdata.gamename) - 1; i > 0; --i) { if (gdata.gamename[i] == '.') { gdata.gamename[i] = '\0'; break; } } gameinfo.fname = gdata.gamefname; gameinfo.name = gdata.gamename; } // Handle log output from the frontend void jgrf_log(int level, const char *fmt, ...) { va_list va; char buffer[512]; static const char *lchr = "diwe"; static const char *lcol[4] = { "\033[0;35m", "\033[0m", "\033[0;33m", "\033[1;31m" }; va_start(va, fmt); vsnprintf(buffer, sizeof(buffer), fmt, va); va_end(va); FILE *fout = level == 1 ? stdout : stderr; jg_setting_t *settings = jgrf_settings_ptr(); if (level == JG_LOG_SCR) { jgrf_video_text(0, corefps, buffer); } else if (level >= settings[MISC_FRONTENDLOG].val) { fprintf(fout, "%s%c: %s\033[0m", lcol[level], lchr[level], buffer); fflush(fout); } if (level == JG_LOG_ERR) jgrf_quit(EXIT_FAILURE); } // Handle log output from the core static void jgrf_core_log(int level, const char *fmt, ...) { va_list va; char buffer[512]; static const char *lchr = "DIWE"; static const char *lcol[4] = { "\033[0;35m", "\033[0;36m", "\033[7;33m", "\033[1;7;31m" }; va_start(va, fmt); vsnprintf(buffer, sizeof(buffer), fmt, va); va_end(va); FILE *fout = level == 1 ? stdout : stderr; jg_setting_t *settings = jgrf_settings_ptr(); if (level == JG_LOG_SCR) { jgrf_video_text(1, corefps, buffer); } else if (level >= settings[MISC_CORELOG].val) { fprintf(fout, "%s%c: %s\033[0m", lcol[level], lchr[level], buffer); fflush(fout); } if (level == JG_LOG_ERR) jgrf_quit(EXIT_FAILURE); } // Pass callbacks into the core static void jgrf_callbacks_set(void) { jgapi.jg_set_cb_log(&jgrf_core_log); jgapi.jg_set_cb_audio(&jgrf_audio_cb_core, NULL); jgapi.jg_set_cb_frametime(&jgrf_frametime, NULL); jgapi.jg_set_cb_rumble(&jgrf_input_rumble, NULL); } // Load and set up the core static void jgrf_core_load(const char *corepath) { memset(&jgapi, 0, sizeof(jgapi)); #ifdef JGRF_STATIC (void)corepath; jgapi.jg_api_version = &jg_api_version; jgapi.jg_init = &jg_init; jgapi.jg_deinit = &jg_deinit; jgapi.jg_reset = &jg_reset; jgapi.jg_exec_frame = &jg_exec_frame; jgapi.jg_game_load = &jg_game_load; jgapi.jg_game_unload = &jg_game_unload; jgapi.jg_state_load = &jg_state_load; jgapi.jg_state_load_raw = &jg_state_load_raw; jgapi.jg_state_save = &jg_state_save; jgapi.jg_state_save_raw = &jg_state_save_raw; jgapi.jg_state_size = &jg_state_size; jgapi.jg_media_set = &jg_media_set; jgapi.jg_media_mount = &jg_media_mount; jgapi.jg_cheat_clear = &jg_cheat_clear; jgapi.jg_cheat_set = &jg_cheat_set; jgapi.jg_rehash = &jg_rehash; jgapi.jg_data_push = &jg_data_push; jgapi.jg_get_coreinfo = &jg_get_coreinfo; jgapi.jg_get_systemlist = &jg_get_systemlist; jgapi.jg_get_bioslist = &jg_get_bioslist; jgapi.jg_get_audioinfo = &jg_get_audioinfo; jgapi.jg_get_videoinfo = &jg_get_videoinfo; jgapi.jg_get_inputinfo = &jg_get_inputinfo; jgapi.jg_get_inputlist = &jg_get_inputlist; jgapi.jg_get_settings = &jg_get_settings; jgapi.jg_get_mediainfo = &jg_get_mediainfo; jgapi.jg_setup_video = &jg_setup_video; jgapi.jg_setup_audio = &jg_setup_audio; jgapi.jg_set_inputstate = &jg_set_inputstate; jgapi.jg_set_gameinfo = &jg_set_gameinfo; jgapi.jg_set_auxinfo = &jg_set_auxinfo; jgapi.jg_set_paths = &jg_set_paths; jgapi.jg_set_cb_log = &jg_set_cb_log; jgapi.jg_set_cb_audio = &jg_set_cb_audio; jgapi.jg_set_cb_frametime = &jg_set_cb_frametime; jgapi.jg_set_cb_rumble = &jg_set_cb_rumble; #else jgapi.handle = SDL_LoadObject(corepath); if (!jgapi.handle) jgrf_log(JG_LOG_ERR, "%s\n", SDL_GetError()); SDL_GetError(); #define LOAD_SYM(fn) do { \ SDL_FunctionPointer fptr = SDL_LoadFunction(jgapi.handle, #fn); \ memcpy(&jgapi.fn, &fptr, sizeof(fptr)); \ } while (0) LOAD_SYM(jg_api_version); LOAD_SYM(jg_init); LOAD_SYM(jg_deinit); LOAD_SYM(jg_reset); LOAD_SYM(jg_exec_frame); LOAD_SYM(jg_game_load); LOAD_SYM(jg_game_unload); LOAD_SYM(jg_state_load); LOAD_SYM(jg_state_load_raw); LOAD_SYM(jg_state_save); LOAD_SYM(jg_state_save_raw); LOAD_SYM(jg_state_size); LOAD_SYM(jg_media_set); LOAD_SYM(jg_media_mount); LOAD_SYM(jg_cheat_clear); LOAD_SYM(jg_cheat_set); LOAD_SYM(jg_rehash); LOAD_SYM(jg_data_push); LOAD_SYM(jg_get_coreinfo); LOAD_SYM(jg_get_systemlist); LOAD_SYM(jg_get_bioslist); LOAD_SYM(jg_get_audioinfo); LOAD_SYM(jg_get_videoinfo); LOAD_SYM(jg_get_inputinfo); LOAD_SYM(jg_get_inputlist); LOAD_SYM(jg_get_settings); LOAD_SYM(jg_get_dips); LOAD_SYM(jg_get_mediainfo); LOAD_SYM(jg_setup_video); LOAD_SYM(jg_setup_audio); LOAD_SYM(jg_set_inputstate); LOAD_SYM(jg_set_gameinfo); LOAD_SYM(jg_set_auxinfo); LOAD_SYM(jg_set_paths); LOAD_SYM(jg_set_cb_log); LOAD_SYM(jg_set_cb_audio); LOAD_SYM(jg_set_cb_frametime); LOAD_SYM(jg_set_cb_rumble); #undef LOAD_SYM #endif if (jgapi.jg_api_version == NULL || jgapi.jg_api_version() < 10100) jgrf_log(JG_LOG_ERR, "Core built against unsupported API version\n"); // Set up values in global data struct coreinfo = jgapi.jg_get_coreinfo(gdata.sys); // Read emulator settings jgrf_settings_emu(jgapi.jg_get_settings); // Match gdata.sys with coreinfo->sys in case of user overriding core at CLI snprintf(gdata.sys, sizeof(gdata.sys), "%s", coreinfo->sys); // Populate frontend's global data with information from the core snprintf(gdata.corefname, sizeof(gdata.corefname), "%s", coreinfo->fname); snprintf(gdata.coreversion, sizeof(gdata.coreversion), "%s", coreinfo->version); #ifdef JGRF_STATIC snprintf(gdata.userassets, sizeof(gdata.userassets), "%sassets", gdata.datapath); snprintf(gdata.biospath, sizeof(gdata.biospath), "%sbios", gdata.datapath); snprintf(gdata.cheatpath, sizeof(gdata.cheatpath), "%scheats", gdata.datapath); snprintf(gdata.savepath, sizeof(gdata.savepath), "%ssave", gdata.datapath); snprintf(gdata.statepath, sizeof(gdata.statepath), "%sstate", gdata.datapath); gdata.numinputs = coreinfo->numinputs; #else snprintf(gdata.userassets, sizeof(gdata.userassets), "%sassets%c%s", gdata.datapath, SEP, coreinfo->name); snprintf(gdata.biospath, sizeof(gdata.biospath), "%sbios", gdata.datapath); snprintf(gdata.cheatpath, sizeof(gdata.cheatpath), "%scheats%c%s", gdata.datapath, SEP, coreinfo->name); snprintf(gdata.savepath, sizeof(gdata.savepath), "%ssave%c%s", gdata.datapath, SEP, coreinfo->name); snprintf(gdata.statepath, sizeof(gdata.statepath), "%sstate%c%s", gdata.datapath, SEP, coreinfo->name); gdata.numinputs = coreinfo->numinputs; #endif // Copy path values into the pathinfo struct, then feed them to the core pathinfo.base = gdata.datapath; pathinfo.core = gdata.coreassets; pathinfo.user = gdata.userassets; pathinfo.bios = gdata.biospath; pathinfo.save = gdata.savepath; jgapi.jg_set_paths(pathinfo); // Pass function pointers for callbacks from core to frontend jgrf_callbacks_set(); // Set up the videoinfo/audioinfo pointers in the frontend jgrf_menu_set_vinfo(jgapi.jg_get_videoinfo()); jgrf_video_set_info(jgapi.jg_get_videoinfo()); jgrf_audio_set_info(jgapi.jg_get_audioinfo()); // Hand pointers to input states to the core jgrf_input_set_states(jgapi.jg_set_inputstate); // Initialize the emulator core loaded.core = jgapi.jg_init(); if (!loaded.core) jgrf_log(JG_LOG_ERR, "Failed to initialize core. Exiting...\n"); } // Unload the core static void jgrf_core_unload(void) { jgapi.jg_deinit(); if (jgapi.handle) SDL_UnloadObject(jgapi.handle); } // Generate the CRC32 checksum of the game data static void jgrf_hash_crc32(void) { gameinfo.crc = mz_crc32(gdata.crc, gameinfo.data, gameinfo.size); } // Convert a nybble's hexadecimal representation to a lower case ASCII char static inline char jgrf_nyb_hexchar(unsigned nyb) { nyb &= 0xf; // Lower nybble only if (nyb >= 10) nyb += 'a' - 10; else nyb += '0'; return (char)nyb; } // Generate the MD5 checksum of the game data static void jgrf_hash_md5(void) { MD5_CTX c; size_t md5len = gameinfo.size; uint8_t *dataptr = gameinfo.data; uint8_t digest[16]; MD5_Init(&c); /*while (md5len > 0) { // Use for large file sizes md5len > 512 ? MD5_Update(&c, dataptr, 512) : MD5_Update(&c, dataptr, md5len); md5len -= 512; dataptr += 512; }*/ MD5_Update(&c, dataptr, md5len); MD5_Final(digest, &c); // Convert the digest to a string without dodgy calls to snprintf for (int i = 0; i < 16; ++i) { gdata.md5[i * 2] = jgrf_nyb_hexchar(digest[i] >> 4); gdata.md5[(i * 2) + 1] = jgrf_nyb_hexchar(digest[i]); } gdata.md5[32] = '\0'; gameinfo.md5 = gdata.md5; } // Compare a file's MD5 against an expected value static int jgrf_md5cmp(const char *path, const char *expected) { FILE *file = fopen(path, "rb"); if (!file) return -1; fseek(file, 0, SEEK_END); long fsize = ftell(file); rewind(file); uint8_t *data = (fsize > 0) ? malloc((size_t)fsize) : NULL; if (!data || !fread(data, (size_t)fsize, 1, file)) { free(data); fclose(file); return -1; } fclose(file); MD5_CTX c; uint8_t digest[16]; char md5str[33]; MD5_Init(&c); MD5_Update(&c, data, (size_t)fsize); MD5_Final(digest, &c); free(data); for (int i = 0; i < 16; ++i) { md5str[i * 2] = jgrf_nyb_hexchar(digest[i] >> 4); md5str[(i * 2) + 1] = jgrf_nyb_hexchar(digest[i]); } md5str[32] = '\0'; return memcmp(md5str, expected, 33) == 0; } // Check BIOS/firmware files requested by the core static void jgrf_bios_check(void) { size_t num = 0; jg_biosinfo_t *bl = jgapi.jg_get_bioslist(&num); if (!bl || !num) return; for (size_t i = 0; i < num; ++i) { char path[384]; snprintf(path, sizeof(path), "%s%c%s", gdata.biospath, SEP, bl[i].fname); int result = jgrf_md5cmp(path, bl[i].md5); jgrf_log(JG_LOG_DBG, "BIOS: %s - %s %s\n", bl[i].fname, bl[i].desc, result < 0 ? "Missing" : result ? "OK" : "Mismatch"); } } // Set number of frames for Benchmark mode void jgrf_benchmark(size_t frames) { bmarkframes = frames; bmark = 1; } // Toggle pause void jgrf_pause_toggle(void) { pause ^= 1; jgrf_osd_toggle(pause); } // Get pause status int jgrf_get_paused(void) { return pause; } // Load an Auxiliary File void jgrf_auxfile_load(const char *filename, int index) { FILE *file = fopen(filename, "rb"); if (!file) jgrf_log(JG_LOG_ERR, "Failed to open file: %s\n", filename); fseek(file, 0, SEEK_END); auxinfo[index].size = ftell(file); rewind(file); auxinfo[index].data = calloc(auxinfo[index].size, sizeof(uint8_t)); if (!auxinfo[index].data || !fread((void*)auxinfo[index].data, auxinfo[index].size, 1, file)) { jgrf_log(JG_LOG_ERR, "Failed to read file: %s\n", filename); fclose(file); return; } snprintf(gdata.auxfilepath[index], sizeof(gdata.auxfilepath[index]), "%s", filename); auxinfo[index].path = filename; // Close the file - some cores may want to load it again on their own terms fclose(file); snprintf(gdata.auxname[index], sizeof(gdata.auxname[index]), "%s", basename((char*)filename)); // Strip the file extension off for (int j = strlen(gdata.auxname[index]) - 1; j > 0; --j) { if (gdata.auxname[index][j] == '.') { gdata.auxname[index][j] = '\0'; break; } } auxinfo[index].name = gdata.auxname[index]; } // Load the game data from a .zip archive static int jgrf_game_load_archive(const char *filename) { // Don't load archived files if the emulator expects an archive if (gdata.hints & JG_HINT_MEDIA_ARCHIVED) return 0; mz_zip_archive zip_archive; memset(&zip_archive, 0, sizeof(zip_archive)); // Make sure it's actually a zip file if (!mz_zip_reader_init_file(&zip_archive, filename, 0)) return 0; // Open the first ROM in the archive for (int i = 0; i < (int)mz_zip_reader_get_num_files(&zip_archive); ++i) { mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) { mz_zip_reader_end(&zip_archive); jgrf_log(JG_LOG_ERR, "Failed to stat archive. Exiting...\n"); } // Extract the data into memory gameinfo.size = file_stat.m_uncomp_size; gameinfo.data = mz_zip_reader_extract_file_to_heap(&zip_archive, file_stat.m_filename, NULL, 0); // Get the CRC32 Checksum gameinfo.crc = gdata.crc = file_stat.m_crc32; // Get the MD5 Checksum jgrf_hash_md5(); // Set the game name based on the file inside the zip jgrf_gamename(file_stat.m_filename); // Set the game filename to the name of the zip gameinfo.path = filename; jgapi.jg_set_gameinfo(gameinfo); // If the game was not loaded correctly, then fail if (!jgapi.jg_game_load()) { mz_zip_reader_end(&zip_archive); jgrf_log(JG_LOG_ERR, "Failed to load ROM from archive. Exiting...\n"); } // Why don't you clean up after yourself? mz_zip_reader_end(&zip_archive); loaded.game = 1; return 1; } mz_zip_reader_end(&zip_archive); return 0; } // Load a game that the core will open via path (large disc images) static void jgrf_game_load_path(const char *filename) { jgrf_gamename(filename); gameinfo.path = filename; gameinfo.data = NULL; gameinfo.size = 0; jgapi.jg_set_gameinfo(gameinfo); if (!jgapi.jg_game_load()) jgrf_log(JG_LOG_ERR, "Failed to load game. Exiting...\n"); loaded.game = 1; } static void jgrf_game_load(const char *filename) { // Load a game if (jgrf_game_load_archive(filename)) return; if (gdata.pathonly) { jgrf_game_load_path(filename); return; } FILE *file = fopen(filename, "rb"); if (!file) jgrf_log(JG_LOG_ERR, "Failed to open file. Exiting...\n"); fseek(file, 0, SEEK_END); gameinfo.size = ftell(file); rewind(file); gameinfo.data = calloc(gameinfo.size, sizeof(uint8_t)); if (!gameinfo.data || !fread((void*)gameinfo.data, gameinfo.size, 1, file)) jgrf_log(JG_LOG_ERR, "Failed to read file. Exiting...\n"); // Close the file - some cores may want to load it again on their own terms fclose(file); // Get the CRC32 Checksum jgrf_hash_crc32(); // Get the MD5 Checksum jgrf_hash_md5(); jgrf_gamename(filename); // Set the game name gameinfo.path = filename; jgapi.jg_set_gameinfo(gameinfo); // If the game could not be loaded, there is no point continuing if (!jgapi.jg_game_load()) jgrf_log(JG_LOG_ERR, "Failed to load game. Exiting...\n"); // This item is set mostly so there can be a clean shutdown loaded.game = 1; return; } // Push data to the core void jgrf_data_push(uint32_t type, int port, const void *ptr, size_t size) { jgapi.jg_data_push(type, port, ptr, size); } // Send a reset signal to the emulator core void jgrf_reset(int hard) { jgapi.jg_reset(hard); } // Select media by index void jgrf_media_set(unsigned index) { jgapi.jg_media_set(index); } // Mount or unmount media void jgrf_media_mount(unsigned state) { jgapi.jg_media_mount(state); } // Rehash the emulator core's settings void jgrf_rehash_core(void) { jgapi.jg_rehash(); } // Rehash the frontend's settings void jgrf_rehash_frontend(void) { jgrf_video_rehash(); // A subset of video settings allow live changes jgrf_input_rehash(); } // Rehash input void jgrf_rehash_input(void) { // Deinitialize core input states and undefine mappings jgrf_input_deinit_core(); jgrf_input_undef(); // Query core input devices jgrf_input_query(jgapi.jg_get_inputinfo); } // Call to stop and shut down at the end of the current iteration void jgrf_schedule_quit(void) { running = 0; } // Shut everything down, clean up, exit program void jgrf_quit(int status) { if (loaded.game) { jgrf_dips_save(); jgapi.jg_game_unload(); } if (loaded.core) jgrf_core_unload(); if (loaded.audio) jgrf_audio_deinit(); if (loaded.video) jgrf_video_deinit(); if (loaded.input) jgrf_input_deinit(); if (loaded.settings) jgrf_settings_deinit(); if (gameinfo.data) free(gameinfo.data); for (int i = 0; i < gdata.numauxfiles; ++i) if (auxinfo[i].data) free(auxinfo[i].data); jgrf_cheats_deinit(); jgrf_dips_deinit(); SDL_Quit(); exit(status); } // Retrieve the current fast-forward speed int jgrf_get_speed(void) { return fforward; } // Set the fast-forward speed: N = extra emulation frames per screen frame void jgrf_set_speed(int speed) { fforward = speed; } // Load state void jgrf_state_load(int slot) { char statepath[260]; snprintf(statepath, sizeof(statepath), "%s%c%s.st%d", gdata.statepath, SEP, gdata.gamename, slot); int success = jgapi.jg_state_load(statepath); success ? jgrf_log(JG_LOG_INF, "State Loaded: %s\n", statepath): jgrf_log(JG_LOG_WRN, "State Load failed: %s\n", statepath); jgrf_log(JG_LOG_SCR, "State %d %s", slot, success ? "loaded." : "load failed."); } // Save state void jgrf_state_save(int slot) { char statepath[260]; snprintf(statepath, sizeof(statepath), "%s%c%s.st%d", gdata.statepath, SEP, gdata.gamename, slot); int success = jgapi.jg_state_save(statepath); success ? jgrf_log(JG_LOG_INF, "State Saved: %s\n", statepath): jgrf_log(JG_LOG_WRN, "State Save failed: %s\n", statepath); jgrf_log(JG_LOG_SCR, "State %d %s", slot, success ? "saved." : "save failed."); } void jgrf_set_screenfps(int fps) { if (!fps) return; screenfps = fps; jgrf_log(JG_LOG_DBG, "Screen base FPS set: %dfps\n", screenfps); } // Callback to inform the frontend of current core framerate void jgrf_frametime(void *udata, double frametime) { (void)udata; jgrf_audio_timing(frametime); corefps = frametime + 0.5; } int main(int argc, char *argv[]) { if (argc < 2) { jgrf_cli_usage(argv[0]); jgrf_quit(EXIT_SUCCESS); } // Determine the binary's path relative to the current working directory if (strrchr(argv[0], SEP)) { int lastdirpos = strrchr(argv[0], SEP) - argv[0]; strncpy(gdata.binpath, argv[0], sizeof(gdata.binpath)); gdata.binpath[lastdirpos] = '\0'; } else { snprintf(gdata.binpath, sizeof(gdata.binpath), "."); } // Parse command line options jgrf_cli_parse(argc, argv); // Force DirectSound audio driver on Windows #if defined(_WIN32) || defined(__MINGW32__) || defined(__MINGW64__) putenv("SDL_AUDIO_DRIVER=directsound"); #endif // Allow joystick input when the window is not focused SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); // Keep window fullscreen if the window manager tries to iconify it SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); // Initialize SDL if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK)) { jgrf_log(JG_LOG_ERR, "Failed to initialize SDL: %s\n", SDL_GetError()); return EXIT_FAILURE; } // Set up user directories jgrf_paths_set_userdirs(); // Load settings loaded.settings = jgrf_settings_init(); // Set up function pointers for video jgrf_video_setfuncs(); // Detect the system required to play the game if (gdata.filename == NULL) { jgrf_cli_usage(argv[0]); jgrf_log(JG_LOG_ERR, "Invalid file specified. Exiting...\n"); } if (jgrf_cli_sys()) snprintf(gdata.sys, sizeof(gdata.sys), "%s", jgrf_cli_sys()); else jgrf_detect_sys(gdata.filename); // Set the core library and asset paths if (!jgrf_paths_env_corelib()) jgrf_paths_set_corelib(); if (!jgrf_paths_env_coreassets()) jgrf_paths_set_coreassets(); // Load the core jgrf_core_load(gdata.corelib); // Override any core specific settings jgrf_settings_override(gdata.corename); // Do final overrides using command line options jgrf_cli_override(); // Create any directories that are required jgrf_paths_mkdirs(); // Load Auxiliary files for (int i = 0; i < gdata.numauxfiles; ++i) jgapi.jg_set_auxinfo(auxinfo[i], i); // Set hints from the core gdata.hints = coreinfo->hints; // Show frontend/API version information jgrf_log(JG_LOG_INF, "The Jolly Good Reference Frontend %s\n", VERSION); jgrf_log(JG_LOG_DBG, "%s\n", JG_VERSION_NAME); // Show core information jgrf_log(JG_LOG_INF, "Core: %s (%s %s)\n", gdata.corename, gdata.corefname, gdata.coreversion); jgrf_log(JG_LOG_DBG, "Core System: %s\n", gdata.sys); jgrf_log(JG_LOG_DBG, "Core Library Path: %s\n", gdata.corelib); jgrf_log(JG_LOG_DBG, "Core Asset Path: %s\n", gdata.coreassets); // Check BIOS/firmware file status jgrf_bios_check(); // Load the game jgrf_game_load(gdata.filename); if (gameinfo.data) jgrf_log(JG_LOG_DBG, "CRC: %08X, MD5: %s\n", gameinfo.crc, gameinfo.md5); // Load any saved DIP switch settings jgrf_dips_load(); // Update hints in case loading a game caused a change gdata.hints = coreinfo->hints; // Set up function pointers for video jgrf_video_setfuncs(); jgrf_video_set_info(jgapi.jg_get_videoinfo()); // Show supported systems size_t numsystems = 0; jg_systeminfo_t *systemlist = jgapi.jg_get_systemlist(&numsystems); for (size_t i = 0; i < numsystems; ++i) jgrf_log(JG_LOG_DBG, "System: %s (%s), Extensions: %s\n", systemlist[i].fname, systemlist[i].name, systemlist[i].ext); // Set up video in the frontend and the core loaded.video = jgrf_video_init(); jgapi.jg_setup_video(); // Create the window jgrf_video_create(); // Initialize audio output loaded.audio = jgrf_audio_init(); jgapi.jg_setup_audio(); // Initialize input loaded.input = jgrf_input_init(); // Query core input devices jgrf_input_query(jgapi.jg_get_inputinfo); // Reset the core jgapi.jg_reset(2); // Activate Cheats jgrf_cheats_init(jgapi.jg_cheat_clear, jgapi.jg_cheat_set); // Allow the sound to flow jgrf_audio_unpause(); // Explicitly disable the screensaver SDL_DisableScreenSaver(); // SDL_Event struct to be filled and passed to SDL event queue SDL_Event event; int runframes = 0; double collector = 0; while (running) { // Divide the core framerate by the base framerate runframes = (corefps / screenfps); // Screenfps is monitor refresh // Collect the remainder of the same division operation collector += ((double)corefps / screenfps) - runframes; // When sufficient remainder has been collected, run an extra frame if (collector >= 1.0) { ++runframes; collector -= 1.0; } // Don't run emulation when paused. Add frames if fast-forwarding. if (pause) runframes = 0; else runframes += fforward; // Run the required number of emulator iterations (frames) for (int i = 0; i < runframes; ++i) jgapi.jg_exec_frame(); framecount += runframes; // Render and output the current video jgrf_video_render(runframes); jgrf_video_swapbuffers(); if (bmark && framecount >= bmarkframes) { jgrf_log(JG_LOG_INF, "Benchmark completed after %ld frames\n", bmarkframes); jgrf_quit(EXIT_SUCCESS); } // Poll for events while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: { running = 0; break; } case SDL_EVENT_WINDOW_MOVED: case SDL_EVENT_WINDOW_RESIZED: { jgrf_video_resize(); break; } default: { jgrf_input_handler(&event); break; } } } } // Clean up before exiting jgrf_quit(EXIT_SUCCESS); return 0; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/jgrf.h000066400000000000000000000136411523150047600216720ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MAIN_H #define MAIN_H #define VERSION "2.0.1" #define JGRF_AUXFILE_MAX 3 #ifdef __APPLE__ #define SOEXT "dylib" #elif defined(_WIN32) || defined(__MINGW32__) || defined(__MINGW64__) #define SOEXT "dll" #else #define SOEXT "so" #endif #if defined(_WIN32) || defined(__MINGW32__) || defined(__MINGW64__) #define SEP '\\' #define PATHSEP ";" #else #define SEP '/' #define PATHSEP ":" #endif #include typedef struct jgrf_gdata_t { // Global Data const char *filename; char binpath[128]; // Directory containing the binary char configpath[128]; // Base user config path char datapath[64]; // Base user data path char corename[64]; // Internally used core name char corefname[96]; // Core Full Name char coreversion[32]; // Core Version char gamename[128]; // Internally used game name char gamefname[128]; // Internally used game name with extension char corelib[384]; // Core library path char coreassets[384]; // Core asset path char userassets[128]; // User asset path char biospath[128]; // BIOS path char cheatpath[128]; // Cheat path char statepath[128]; // State path char savepath[128]; // Save path char sspath[128]; // Screenshot path char auxfilepath[JGRF_AUXFILE_MAX][128]; // Auxiliary file paths char auxname[JGRF_AUXFILE_MAX][128]; // Auxiliary file names minus path/ext uint32_t crc; // CRC32 Checksum char md5[33]; // MD5 Checksum char sys[24]; // Name of emulated system int numinputs; // Number of ports/inputs in core int numauxfiles; // Number of loaded support files uint32_t hints; // Hints from the core int pathonly; // Open via path in the core, do not load into memory } jgrf_gdata_t; jgrf_gdata_t *jgrf_gdata_ptr(void); typedef struct jgapi_t { void *handle; // Function Pointers unsigned (*jg_api_version)(void); int (*jg_init)(void); void (*jg_deinit)(void); void (*jg_reset)(int); void (*jg_exec_frame)(void); int (*jg_game_load)(void); int (*jg_game_unload)(void); int (*jg_state_load)(const char*); void (*jg_state_load_raw)(const void*); int (*jg_state_save)(const char*); const void* (*jg_state_save_raw)(void); size_t (*jg_state_size)(void); void (*jg_media_set)(unsigned); void (*jg_media_mount)(unsigned); void (*jg_cheat_clear)(void); void (*jg_cheat_set)(const char*); void (*jg_rehash)(void); void (*jg_data_push)(uint32_t, int, const void*, size_t); // Callback Setup void (*jg_set_cb_log)(jg_cb_log_t); void (*jg_set_cb_audio)(jg_cb_audio_t, void*); void (*jg_set_cb_frametime)(jg_cb_frametime_t, void*); void (*jg_set_cb_rumble)(jg_cb_rumble_t, void*); // Retrieve "info" structs from the core jg_coreinfo_t* (*jg_get_coreinfo)(const char*); jg_systeminfo_t* (*jg_get_systemlist)(size_t*); jg_videoinfo_t* (*jg_get_videoinfo)(void); jg_audioinfo_t* (*jg_get_audioinfo)(void); jg_inputinfo_t* (*jg_get_inputinfo)(int); jg_biosinfo_t* (*jg_get_bioslist)(size_t*); jg_inputinfo_t* (*jg_get_inputlist)(size_t*); jg_setting_t* (*jg_get_settings)(size_t*); jg_setting_t* (*jg_get_dips)(size_t*); jg_mediainfo_t* (*jg_get_mediainfo)(void); // Core setup void (*jg_setup_video)(void); void (*jg_setup_audio)(void); void (*jg_set_inputstate)(jg_inputstate_t*, int); void (*jg_set_gameinfo)(jg_fileinfo_t); void (*jg_set_auxinfo)(jg_fileinfo_t, int); void (*jg_set_paths)(jg_pathinfo_t); } jgapi_t; jgapi_t *jgrf_jgapi_ptr(void); void jgrf_log(int, const char*, ...); void jgrf_auxfile_load(const char*, int); void jgrf_benchmark(size_t); void jgrf_pause_toggle(void); int jgrf_get_paused(void); void jgrf_data_push(uint32_t, int, const void*, size_t); void jgrf_state_load(int); void jgrf_state_save(int); void jgrf_reset(int); void jgrf_media_set(unsigned); void jgrf_media_mount(unsigned); void jgrf_rehash_core(void); void jgrf_rehash_frontend(void); void jgrf_rehash_input(void); void jgrf_schedule_quit(void); void jgrf_quit(int); int jgrf_get_speed(void); void jgrf_set_speed(int); void jgrf_set_screenfps(int); void jgrf_state_save(int); void jgrf_state_load(int); void jgrf_frametime(void*, double); extern int bmark; // External benchmark mode variable extern int fforward; // External fast-forward level extern int corefps; // Emulator core's framerate rounded to nearest int extern int screenfps; // Screen's framerate rounted to nearest int #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/menu.c000066400000000000000000000512711523150047600217020ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __APPLE__ #define _POSIX_C_SOURCE 200112L #endif #include #include #include #include #include "ezmenu.h" #include "jgrf.h" #include "input.h" #include "settings.h" #include "dips.h" #include "video.h" #include "menu.h" #define DESCSIZE 256 #define NUMLINES 256 #define TEXTSIZE 1024 enum _menumode { FRONTEND, EMULATOR, DIPS, INPUT, MEDIA, SAVESETTINGS }; typedef struct _menunode_t { struct _menunode_t *parent; struct _menunode_t *child; struct _menunode_t *next; char desc[DESCSIZE]; int val; int mode; } menunode_t; // ezmenu context static struct ezmenu ezm; // Menu can remember its path in the menu 4 levels deep (8 bits per level) static uint32_t menupath = 0; // Pointer to the core's video information static jg_videoinfo_t *vidinfo = NULL; static jg_setting_t *settings = NULL; static jg_setting_t *emusettings = NULL; static size_t numemusettings = 0; static jg_setting_t *dips = NULL; static size_t numdips = 0; static jg_mediainfo_t *mediainfo = NULL; static menunode_t *menuroot = NULL; static menunode_t *menulevel = NULL; static jg_inputinfo_t *inputlist = NULL; static size_t inputlist_count = 0; static char textbuf[TEXTSIZE]; // Buffer for all text on screen static char *linebuf[NUMLINES]; // 64 lines of 255 + '\0' characters static int menumode = 0; static int settinglevel = 0; static int settingactive = 0; static int skip_redraw = 0; // Concatenate two strings in a Jolly Good manner static void strjcat(char *dest, const char *src, size_t n) { size_t i = 0; size_t dlen = strlen(dest); size_t slen = strlen(src); if (!n) return; if (dlen + slen > (n - 1)) slen = (n - 1) - dlen; for (i = 0; i < slen; ++i) dest[i + (dlen)] = src[i]; dest[i + dlen] = '\0'; } static menunode_t* jgrf_menu_node_create(void) { return (menunode_t*)calloc(1, sizeof(menunode_t)); } // Add a child node to another node, or a sibling if there is already a child static menunode_t* jgrf_menu_node_add_child(menunode_t *parent) { // Create the new node and assign the pointer to its parent menunode_t *newnode = jgrf_menu_node_create(); newnode->parent = parent; // If the parent node already had a child, add a sibling instead if (parent->child) { menunode_t *current = parent->child; while (current->next) current = current->next; current->next = newnode; } else { parent->child = newnode; } return newnode; } // Free a node and every node to the right or below it static void jgrf_menu_node_free(menunode_t *node) { if (node->next) jgrf_menu_node_free(node->next); if (node->child) jgrf_menu_node_free(node->child); free(node); node = NULL; } // Generate the nodes for a settings subtree static void jgrf_menu_gen(menunode_t *node, jg_setting_t *s, unsigned num) { // Generate the text to be displayed for (unsigned i = 0; i < num; ++i) { // Create a menu item node menunode_t *menuitem = jgrf_menu_node_add_child(node); snprintf(menuitem->desc, DESCSIZE, "%s", s[i].fname); menuitem->val = i; // Add child nodes containing the settings for each menu item char buf[DESCSIZE]; snprintf(buf, DESCSIZE, "%s", s[i].opts); char *bufptr; char *token = strtok_r(buf, ",", &bufptr); while (token != NULL) { while (*token == ' ') ++token; if (token[0] == 'N') { // Special case for numerical ranges unsigned range = abs(s[i].max - s[i].min) + 1; for (unsigned j = 0; j < range; ++j) { menunode_t *setting = jgrf_menu_node_add_child(menuitem); snprintf(setting->desc, DESCSIZE, "%d", j + s[i].min); setting->val = j + s[i].min; } } else { // Regular nodes menunode_t *setting = jgrf_menu_node_add_child(menuitem); snprintf(setting->desc, DESCSIZE, "%s", token); char vbuf[DESCSIZE]; snprintf(vbuf, DESCSIZE, "%s", token); char *vbufptr; char *vtoken = strtok_r(vbuf, " =", &vbufptr); setting->val = atoi(vtoken); } token = strtok_r(NULL, ",", &bufptr); } } } // Generate the nodes for Input Mapping static void jgrf_menu_gen_input(menunode_t *node) { inputlist = jgrf_jgapi_ptr()->jg_get_inputlist(&inputlist_count); if (!inputlist) return; /* Walk the slot list grouping consecutive slots by fname. Singleton families produce a single configurable node. Multi-instance families produce a parent node with one child per instance. */ size_t i = 0; while (i < inputlist_count) { if (inputlist[i].name == NULL || inputlist[i].fname == NULL) { ++i; continue; } // Determine the extent of the current family size_t j = i + 1; while (j < inputlist_count && inputlist[j].fname != NULL && strcmp(inputlist[j].fname, inputlist[i].fname) == 0) { ++j; } size_t family_size = j - i; menunode_t *family = jgrf_menu_node_add_child(node); snprintf(family->desc, DESCSIZE, "%s", inputlist[i].fname); if (family_size == 1) { // Singleton family: configure directly when selected family->val = (int)i; } else { // Multi-instance family: build a submenu of instances family->val = -1; for (size_t k = i; k < j; ++k) { menunode_t *instance = jgrf_menu_node_add_child(family); snprintf(instance->desc, DESCSIZE, "%s %d", inputlist[k].fname, inputlist[k].index + 1); instance->val = (int)k; } } i = j; } } // Generate the nodes for Media selection static void jgrf_menu_gen_media(menunode_t *node) { if (!mediainfo) return; for (unsigned i = 0; i < mediainfo->count; ++i) { menunode_t *entry = jgrf_menu_node_add_child(node); snprintf(entry->desc, DESCSIZE, "%s %u", mediainfo->name, i + 1); entry->val = (int)i; } menunode_t *eject = jgrf_menu_node_add_child(node); snprintf(eject->desc, DESCSIZE, "Eject"); eject->val = -1; } // Change menu level static void jgrf_menu_level(void) { menunode_t *node = menulevel; unsigned numlines = 0; ezmenu_setheader(&ezm, node->parent->desc); while (node) { snprintf(linebuf[numlines++], DESCSIZE, "%s", node->desc); node = node->next; } ezmenu_setlines(&ezm, linebuf, numlines); } // Redraw the text to be displayed on screen void jgrf_menu_text_redraw(void) { textbuf[0] = '\0'; for (int i = 0; i < ezm.h; ++i) { // menu line being drawn (offset from header) int mline = i - ezm.start; // active setting in relation to scroll offset int active = settingactive - ezm.yscroll; if (i == ezm.vissel) strjcat(textbuf, "> ", sizeof(textbuf)); else if (i > 1 && strlen(ezm.vislines[i])) strjcat(textbuf, " ", sizeof(textbuf)); strjcat(textbuf, ezm.vislines[i], sizeof(textbuf)); if (settinglevel && i >= ezm.start && i < ezm.end && active == mline) strjcat(textbuf, " *\n", sizeof(textbuf)); else strjcat(textbuf, "\n", sizeof(textbuf)); } jgrf_video_text(2, 1, textbuf); /* Ensure the menu can be redrawn if this function is called explicitly after redrawing has been disabled */ skip_redraw = 0; } static void jgrf_menu_select_frontend(int item) { menunode_t *node = menulevel; // Seek to the correct node for (int i = 0; i < item; ++i) node = node->next; if (!settinglevel && !node->child->child) settinglevel = 1; // Check if there is a level below if (node->child) { menulevel = node->child; jgrf_menu_level(); if (settinglevel) settingactive = settings[item].val - settings[item].min; } else { // No child, means it has a value to set settings[node->parent->val].val = node->val; settinglevel = 1; settingactive = item; jgrf_rehash_frontend(); jgrf_log(JG_LOG_SCR, "%s: %s%s", node->parent->desc, node->desc, settings[node->parent->val].flags & JG_SETTING_RESTART ? " (restart required)" : "" ); menupath >>= 8; } } // Emulator Settings and DIP Switches static void jgrf_menu_select_settings(int item, jg_setting_t *arr, size_t num) { if (!num) { jgrf_log(JG_LOG_SCR, "No Settings"); return; } menunode_t *node = menulevel; // Seek to the correct node for (int i = 0; i < item; ++i) node = node->next; if (!settinglevel && !node->child->child) settinglevel = 1; // Check if there is a level below if (node->child) { menulevel = node->child; jgrf_menu_level(); if (settinglevel) settingactive = arr[item].val - arr[item].min; } else { // No child, means it has a value to set arr[node->parent->val].val = node->val; settinglevel = 1; settingactive = item; jgrf_rehash_core(); if (arr[node->parent->val].flags & JG_SETTING_INPUT) jgrf_rehash_input(); jgrf_log(JG_LOG_SCR, "%s: %s%s", node->parent->desc, node->desc, arr[node->parent->val].flags & JG_SETTING_RESTART ? " (restart required)" : "" ); menupath >>= 8; } } static void jgrf_menu_select_input(int item) { menunode_t *node = menulevel; // Seek to the correct node for (int i = 0; i < item; ++i) { node = node->next; } // Check if there is a level below if (node->child) { menulevel = node->child; jgrf_menu_level(); } else { // No child, means it can be configured int slot = node->val; if (inputlist && slot >= 0 && (size_t)slot < inputlist_count) { jgrf_input_config_enable(1); jgrf_input_config(&inputlist[slot]); skip_redraw = 1; } // Ensure the menu system knows it has not really gone a layer deeper ezm.sel = menupath & 0xff; menupath >>= 8; ezmenu_update(&ezm); } } static void jgrf_menu_select_media(int item) { menunode_t *node = menulevel; for (int i = 0; i < item; ++i) node = node->next; if (node->child) { settinglevel = 1; menulevel = node->child; jgrf_menu_level(); settingactive = mediainfo->inserted ? (int)mediainfo->index : (int)mediainfo->count; } else { if (node->val < 0) { // Eject if (mediainfo->inserted) jgrf_media_mount(0); settingactive = (int)mediainfo->count; } else { // Select and insert if (mediainfo->inserted && mediainfo->index == (unsigned)node->val) return; if (mediainfo->inserted) jgrf_media_mount(0); // Unmount current jgrf_media_set((unsigned)node->val); jgrf_media_mount(1); // Mount new settingactive = item; } settinglevel = 1; menupath >>= 8; } } static void jgrf_menu_select_save(int item) { menunode_t *node = menulevel; // Seek to the correct node for (int i = 0; i < item; ++i) node = node->next; // Check if there is a level below if (node->child) { menulevel = node->child; jgrf_menu_level(); } else { switch (item) { #ifdef JGRF_STATIC case 0: { jgrf_settings_write(SETTINGS_FRONTEND|SETTINGS_EMULATOR); jgrf_log(JG_LOG_SCR, "Saved Settings"); break; } case 1: { jgrf_settings_default(SETTINGS_FRONTEND); jgrf_log(JG_LOG_SCR, "Restored Frontend Defaults"); break; } case 2: { jgrf_settings_default(SETTINGS_EMULATOR); jgrf_log(JG_LOG_SCR, "Restored Emulator Defaults"); break; } case 3: { jgrf_dips_default(); jgrf_log(JG_LOG_SCR, "Restored DIP Defaults"); break; } #else case 0: { jgrf_settings_write(SETTINGS_FRONTEND); jgrf_log(JG_LOG_SCR, "Saved Frontend Settings"); break; } case 1: { if (numemusettings) { jgrf_settings_write(SETTINGS_EMULATOR); jgrf_log(JG_LOG_SCR, "Saved Emulator Settings"); } else { jgrf_log(JG_LOG_SCR, "No Emulator Settings"); } break; } case 2: { jgrf_settings_write(SETTINGS_FRONTEND|SETTINGS_EMULATOR); jgrf_log(JG_LOG_SCR, "Saved Combined Settings"); break; } case 3: { jgrf_settings_default(SETTINGS_FRONTEND); jgrf_log(JG_LOG_SCR, "Restored Frontend Defaults"); break; } case 4: { jgrf_settings_default(SETTINGS_EMULATOR); jgrf_log(JG_LOG_SCR, "Restored Emulator Defaults"); break; } case 5: { jgrf_dips_default(); jgrf_log(JG_LOG_SCR, "Restored DIP Defaults"); break; } #endif } menupath >>= 8; } } // Initialize and display the menu on screen void jgrf_menu_display(void) { // Grab the frontend settings settings = jgrf_settings_ptr(); // Grab the emulator settings emusettings = jgrf_settings_emu_ptr(&numemusettings); // Grab the DIP switches (may be empty for most games) dips = jgrf_dips_ptr(&numdips); // Grab media info (may be NULL for non-disc/disk games) mediainfo = jgrf_jgapi_ptr()->jg_get_mediainfo ? jgrf_jgapi_ptr()->jg_get_mediainfo() : NULL; menuroot = jgrf_menu_node_create(); snprintf(menuroot->desc, DESCSIZE, "%s", jgrf_gdata_ptr()->corefname); menunode_t *node; node = jgrf_menu_node_add_child(menuroot); node->mode = FRONTEND; snprintf(node->desc, DESCSIZE, "Frontend Settings"); jgrf_menu_gen(node, settings, JGRF_SETTINGS_MAX); if (numemusettings) { node = jgrf_menu_node_add_child(menuroot); node->mode = EMULATOR; snprintf(node->desc, DESCSIZE, "Emulator Settings"); jgrf_menu_gen(node, emusettings, numemusettings); } if (numdips) { node = jgrf_menu_node_add_child(menuroot); node->mode = DIPS; snprintf(node->desc, DESCSIZE, "DIP Switches"); jgrf_menu_gen(node, dips, numdips); } node = jgrf_menu_node_add_child(menuroot); node->mode = INPUT; snprintf(node->desc, DESCSIZE, "Map Inputs"); jgrf_menu_gen_input(node); if (mediainfo) { node = jgrf_menu_node_add_child(menuroot); node->mode = MEDIA; snprintf(node->desc, DESCSIZE, "Media"); jgrf_menu_gen_media(node); } menunode_t *savenode = jgrf_menu_node_add_child(menuroot); savenode->mode = SAVESETTINGS; snprintf(savenode->desc, DESCSIZE, "Save/Restore Settings"); #ifdef JGRF_STATIC node = jgrf_menu_node_add_child(savenode); snprintf(node->desc, DESCSIZE, "Save Settings"); #else node = jgrf_menu_node_add_child(savenode); snprintf(node->desc, DESCSIZE, "Save Frontend Settings"); node = jgrf_menu_node_add_child(savenode); snprintf(node->desc, DESCSIZE, "Save Emulator Settings"); node = jgrf_menu_node_add_child(savenode); snprintf(node->desc, DESCSIZE, "Save Combined Settings"); #endif node = jgrf_menu_node_add_child(savenode); snprintf(node->desc, DESCSIZE, "Restore Frontend Defaults"); node = jgrf_menu_node_add_child(savenode); snprintf(node->desc, DESCSIZE, "Restore Emulator Defaults"); if (numdips) { node = jgrf_menu_node_add_child(savenode); snprintf(node->desc, DESCSIZE, "Restore DIP Defaults"); } for (unsigned i = 0; i < NUMLINES; ++i) linebuf[i] = (char*)calloc(DESCSIZE, 1); menulevel = menuroot->child; menupath = 0; ezmenu_init(&ezm, vidinfo->w, vidinfo->h, 10, 12); ezmenu_setheader(&ezm, menuroot->desc); ezmenu_setfooter(&ezm, ""); ezm.wraparound = 1; jgrf_menu_level(); ezmenu_update(&ezm); jgrf_menu_text_redraw(); } void jgrf_menu_input_handler(SDL_Event *event) { switch (event->key.scancode) { case SDL_SCANCODE_TAB: case SDL_SCANCODE_LEFT: { settinglevel = 0; if (menulevel != menuroot->child) { menulevel = menulevel->parent->parent->child; jgrf_menu_level(); ezm.sel = menupath & 0xff; menupath >>= 8; ezmenu_update(&ezm); jgrf_menu_text_redraw(); break; } } // fallthrough case SDL_SCANCODE_ESCAPE: { // Free the memory allocated for the menu tree jgrf_menu_node_free(menuroot); jgrf_video_text(2, 0, ""); free(ezm.vislines); for (unsigned i = 0; i < NUMLINES; ++i) free(linebuf[i]); jgrf_input_menu_enable(0); settinglevel = 0; break; } case SDL_SCANCODE_UP: { ezmenu_userinput(&ezm, EZM_UP); jgrf_menu_text_redraw(); break; } case SDL_SCANCODE_DOWN: { ezmenu_userinput(&ezm, EZM_DOWN); jgrf_menu_text_redraw(); break; } case SDL_SCANCODE_RETURN: case SDL_SCANCODE_RIGHT: { menupath = (menupath << 8) | ezm.sel; if (menulevel == menuroot->child) { // Read the mode tag off the selected top-level child menunode_t *sel = menuroot->child; for (int i = 0; i < ezm.sel; ++i) sel = sel->next; menumode = sel->mode; } switch (menumode) { case FRONTEND: jgrf_menu_select_frontend(ezm.sel); break; case EMULATOR: jgrf_menu_select_settings(ezm.sel, emusettings, numemusettings); break; case DIPS: jgrf_menu_select_settings(ezm.sel, dips, numdips); break; case INPUT: jgrf_menu_select_input(ezm.sel); break; case MEDIA: jgrf_menu_select_media(ezm.sel); break; case SAVESETTINGS: jgrf_menu_select_save(ezm.sel); break; } ezmenu_update(&ezm); if (!skip_redraw) jgrf_menu_text_redraw(); break; } default: { break; } } } void jgrf_menu_set_vinfo(jg_videoinfo_t *ptr) { vidinfo = ptr; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/menu.h000066400000000000000000000032461523150047600217060ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MENU_H #define MENU_H void jgrf_menu_text_redraw(void); void jgrf_menu_display(void); void jgrf_menu_input_handler(SDL_Event*); void jgrf_menu_set_vinfo(jg_videoinfo_t*); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/osd.c000066400000000000000000001263561523150047600215320ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * The Jolly Good On-Screen Display * * The OSD owns a fixed-size 640x480 RGBA8888 canvas. Three logical layers are * composited into it on demand: * * 1. Shade - uniform colour + alpha covering the visible region * 2. Effects - animated software pixel effects (steam, snow, ...) * 3. Text - bitmap font glyphs in fixed slots * * The composite order is shade -> effects -> text, matching the visual * intent: shade darkens the game underneath, effects appear over the dimmed * game, and text remains crisp on top. Each layer can be on or off in any * combination. * * The canvas is API-agnostic: the renderer treats it as a single RGBA8888 * texture and draws it as one alpha-blended quad over the post-shader output, * at an integer scale determined by the current render dimensions. The OSD * itself never sees the game pixels - the alpha blending happens on the GPU * during the final draw, not in software. * * The visible region (vis_w, vis_h) is the portion of the canvas that the * renderer actually shows. Drawing operations clip to this region so that * shade, effects and text never extend outside the visible game area, even * when the game render area is smaller than the canvas. */ #include #include #include #include #include #include #include "jg/jg.h" #include "jgrf.h" #include "osd.h" #include "settings.h" #include "font/6x8.h" #include "font/8x8.h" #define JGRF_OSD_ORANGE 0xd45500ff #define JGRF_OSD_TEXT_MAX 512 #define JGRF_OSD_LINE_GAP 2 static int osdactive = 0; static uint32_t osd_text_rgba = JGRF_OSD_ORANGE; /* ------------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------------ */ static inline uint32_t xorshift32(uint32_t *state) { uint32_t x = *state; x ^= x << 13; x ^= x >> 17; x ^= x << 5; *state = x; return x; } /* ------------------------------------------------------------------------ */ /* State */ /* ------------------------------------------------------------------------ */ /* The OSD canvas - RGBA8888, 0xAABBGGRR in little-endian byte order when read as uint32_t. It is stored as packed bytes so it can be uploaded directly as GL_RGBA / GL_UNSIGNED_BYTE without endianness concerns. */ static uint8_t canvas[JGRF_OSD_W * JGRF_OSD_H * 4]; // Current visible region, defaults to the full canvas static int vis_w = JGRF_OSD_W; static int vis_h = JGRF_OSD_H; static jg_setting_t *settings = NULL; // Bitmap font state static uint8_t *font = NULL; static unsigned fwidth = 8; static unsigned fheight = 8; /* ------------------------------------------------------------------------ */ /* Shade layer */ /* ------------------------------------------------------------------------ */ static uint32_t shade_rgba = 0; // 0 == off void jgrf_osd_shade_set(uint32_t rgba) { shade_rgba = rgba; } /* ------------------------------------------------------------------------ */ /* Effects layer */ /* ------------------------------------------------------------------------ */ typedef struct { void (*init)(void); void (*step)(int vw, int vh); void (*deinit)(void); } jgrf_osd_fx_t; static void fx_tea_init(void); static void fx_tea_step(int vw, int vh); static void fx_tea_deinit(void); static void fx_snow_init(void); static void fx_snow_step(int vw, int vh); static void fx_snow_deinit(void); static void fx_mush_init(void); static void fx_mush_step(int vw, int vh); static void fx_mush_deinit(void); static void fx_fire_init(void); static void fx_fire_step(int vw, int vh); static void fx_fire_deinit(void); static void fx_dmk_init(void); static void fx_dmk_step(int vw, int vh); static void fx_dmk_deinit(void); static const jgrf_osd_fx_t fx_table[JGRF_OSD_NUM_FX] = { [JGRF_OSD_FX_TEA] = { fx_tea_init, fx_tea_step, fx_tea_deinit }, [JGRF_OSD_FX_SNOW] = { fx_snow_init, fx_snow_step, fx_snow_deinit }, [JGRF_OSD_FX_MUSH] = { fx_mush_init, fx_mush_step, fx_mush_deinit }, [JGRF_OSD_FX_FIRE] = { fx_fire_init, fx_fire_step, fx_fire_deinit }, [JGRF_OSD_FX_DMK] = { fx_dmk_init, fx_dmk_step, fx_dmk_deinit } }; static unsigned fx_active[JGRF_OSD_NUM_FX]; void jgrf_osd_fx_enable(int fx_id) { if (fx_id < 0 || fx_id >= JGRF_OSD_NUM_FX || fx_active[fx_id]) return; fx_active[fx_id] = 1; if (fx_table[fx_id].init) fx_table[fx_id].init(); } void jgrf_osd_fx_disable(int fx_id) { if (fx_id < 0 || fx_id >= JGRF_OSD_NUM_FX || !fx_active[fx_id]) return; fx_active[fx_id] = 0; if (fx_table[fx_id].deinit) fx_table[fx_id].deinit(); } void jgrf_osd_fx_clear(void) { for (size_t i = 0; i < JGRF_OSD_NUM_FX; ++i) jgrf_osd_fx_disable(i); } static int fx_any_active(void) { for (size_t i = 0; i < JGRF_OSD_NUM_FX; ++i) if (fx_active[i]) return 1; return 0; } /* ------------------------------------------------------------------------ */ /* Text layer */ /* ------------------------------------------------------------------------ */ typedef struct { int frames; // 0 = Inactive, >0 = countdown, -1 = persistent int anchor; int x, y; uint32_t rgba; char text[JGRF_OSD_TEXT_MAX]; } jgrf_osd_text_slot_t; static jgrf_osd_text_slot_t text_slots[JGRF_OSD_NUM_TEXT_SLOTS]; void jgrf_osd_text(int slot, int frames, int anchor, int x, int y, uint32_t rgba, const char *text) { if (slot < 0 || slot >= JGRF_OSD_NUM_TEXT_SLOTS) return; jgrf_osd_text_slot_t *s = &text_slots[slot]; s->frames = frames; s->anchor = anchor; s->x = x; s->y = y; s->rgba = rgba ? rgba : osd_text_rgba; if (text) snprintf(s->text, sizeof(s->text), "%s", text); else s->text[0] = '\0'; } void jgrf_osd_text_clear(int slot) { if (slot < 0 || slot >= JGRF_OSD_NUM_TEXT_SLOTS) return; text_slots[slot].frames = 0; text_slots[slot].text[0] = '\0'; } /* Compute the display width of a string in canvas pixels. Newlines start a new line; the returned width is the longest single line. */ static int text_pixel_width(const char *text) { int max_w = 0; int cur_w = 0; for (size_t c = 0; text[c] != '\0'; ++c) { if (text[c] == '\n') { if (cur_w > max_w) max_w = cur_w; cur_w = 0; continue; } cur_w += fwidth; } if (cur_w > max_w) max_w = cur_w; return max_w; } // Compute the display height of a string in canvas pixels. static int text_pixel_height(const char *text) { int lines = 1; for (size_t c = 0; text[c] != '\0'; ++c) if (text[c] == '\n') ++lines; return lines * ((int)fheight + JGRF_OSD_LINE_GAP); } // Resolve a slot's anchor static void resolve_anchor(jgrf_osd_text_slot_t *s, int *out_x, int *out_y) { int tw = text_pixel_width(s->text); int th = text_pixel_height(s->text); int ax = 0; int ay = 0; switch (s->anchor) { default: case JGRF_OSD_ANCHOR_TL: { ax = s->x; ay = s->y; break; } case JGRF_OSD_ANCHOR_TR: { ax = vis_w - s->x - tw; ay = s->y; break; } case JGRF_OSD_ANCHOR_BL: { ax = s->x; ay = vis_h - s->y - th; break; } case JGRF_OSD_ANCHOR_BR: { ax = vis_w - s->x - tw; ay = vis_h - s->y - th; break; } } *out_x = ax; *out_y = ay; } static int text_active(void) { for (size_t i = 0; i < JGRF_OSD_NUM_TEXT_SLOTS; ++i) { const jgrf_osd_text_slot_t *s = &text_slots[i]; if (s->frames != 0 && s->text[0] != '\0') return 1; } return 0; } /* ------------------------------------------------------------------------ */ /* Canvas primitives */ /* ------------------------------------------------------------------------ */ // Write an opaque pixel, no clipping check static inline void canvas_set(int x, int y, uint32_t rgba) { uint8_t *p = &canvas[(y * JGRF_OSD_W + x) * 4]; p[0] = (rgba >> 24) & 0xff; // R p[1] = (rgba >> 16) & 0xff; // G p[2] = (rgba >> 8) & 0xff; // B p[3] = rgba & 0xff; // A } // Blend: dst = src * src.a + dst * (1 - src.a) -- src is given as 0xRRGGBBAA static inline void canvas_blend(int x, int y, uint32_t rgba) { uint8_t sa = rgba & 0xff; if (sa == 0) return; if (sa == 0xff) { canvas_set(x, y, rgba); return; } uint8_t *p = &canvas[(y * JGRF_OSD_W + x) * 4]; uint16_t sr = (rgba >> 24) & 0xff; uint16_t sg = (rgba >> 16) & 0xff; uint16_t sb = (rgba >> 8) & 0xff; uint16_t da = p[3]; /* Standard "over" with straight alpha. The canvas alpha grows toward opaque as more layers are added, which matches the renderer's SRC_ALPHA / ONE_MINUS_SRC_ALPHA blend at composite time. */ uint16_t inv = 255 - sa; p[0] = (uint8_t)((sr * sa + p[0] * inv + 127) / 255); p[1] = (uint8_t)((sg * sa + p[1] * inv + 127) / 255); p[2] = (uint8_t)((sb * sa + p[2] * inv + 127) / 255); p[3] = (uint8_t)(sa + (da * inv + 127) / 255); } // Clear the canvas to fully transparent static void canvas_clear(void) { memset(canvas, 0, sizeof(canvas)); } // Fill the visible region with a colour (alpha included) static void canvas_fill_visible(uint32_t rgba) { uint8_t r = (rgba >> 24) & 0xff; uint8_t g = (rgba >> 16) & 0xff; uint8_t b = (rgba >> 8) & 0xff; uint8_t a = rgba & 0xff; for (int y = 0; y < vis_h; ++y) { uint8_t *row = &canvas[(y * JGRF_OSD_W) * 4]; for (int x = 0; x < vis_w; ++x) { row[x * 4 + 0] = r; row[x * 4 + 1] = g; row[x * 4 + 2] = b; row[x * 4 + 3] = a; } } } /* ------------------------------------------------------------------------ */ /* Bitmap font rasteriser */ /* ------------------------------------------------------------------------ */ /* Draws text into the canvas at (xo, yo) with the active bitmap font. Glyphs that would extend outside the visible region are clipped. A single-pixel black outline is drawn behind each glyph for legibility against arbitrary backgrounds. */ static void jgrf_osd_render_text(int xo, int yo, uint32_t rgba, const char *text) { int x = xo; int y = yo; for (size_t c = 0; text[c] != '\0'; ++c) { if (text[c] == '\n') { y += fheight + JGRF_OSD_LINE_GAP; x = xo; continue; } // Skip glyphs entirely outside the visible region if (x + (int)fwidth <= 0 || x >= vis_w || y + (int)fheight <= 0 || y >= vis_h) { x += fwidth; continue; } const uint8_t *bitmap = &font[(uint8_t)text[c] << 3]; /* First pass: draw outline. Blacken the 8 neighbours of every set pixel, but only where the neighbour itself is unset, so the outline borders the glyph without thickening the strokes. */ for (unsigned gy = 0; gy < fheight; ++gy) { uint8_t row = bitmap[gy]; for (unsigned gx = 0; gx < fwidth; ++gx) { if (!(row & (1 << (7 - gx)))) continue; // Set pixel - lay down the outline around it for (int dy = -1; dy <= 1; ++dy) { for (int dx = -1; dx <= 1; ++dx) { if (dx == 0 && dy == 0) continue; int px = x + (int)gx + dx; int py = y + (int)gy + dy; if (px < 0 || px >= vis_w || py < 0 || py >= vis_h) continue; /* Only stamp if the canvas there isn't already part of the glyph itself. Test by checking whether the source bitmap has a pixel at this offset - if it does, the inner pass write will cover it. */ int srcx = (int)gx + dx; int srcy = (int)gy + dy; if (srcx >= 0 && srcx < (int)fwidth && srcy >= 0 && srcy < (int)fheight && (bitmap[srcy] & (1 << (7 - srcx)))) continue; canvas_set(px, py, 0x000000ff); } } } } // Second pass: draw the glyph itself in the foreground colour for (unsigned gy = 0; gy < fheight; ++gy) { uint8_t row = bitmap[gy]; for (unsigned gx = 0; gx < fwidth; ++gx) { if (!(row & (1 << (7 - gx)))) continue; int px = x + (int)gx; int py = y + (int)gy; if (px < 0 || px >= vis_w || py < 0 || py >= vis_h) continue; canvas_set(px, py, rgba); } } x += fwidth; } } /* ------------------------------------------------------------------------ */ /* Effect implementations */ /* ------------------------------------------------------------------------ */ /* Tea - Canonical demoscene plasma. At each pixel, sum four sinusoids: one in x, one in y, one rotated diagonal, and one radial from the visible-region centre. Each is animated by a per-frame phase. The sum is mapped into a 256-entry palette of warm tea-amber tones so it reads as swirling tea rather than the usual rainbow. The radial term is computed inline from the visible-region centre so the rings are always centred on screen regardless of the emulated system's output size. A fixed-point scale factor adapts ring density to the current resolution. The diagonal uses x + 2y (atan 1/2, roughly 27 degrees) instead of x + y (45 degrees) to break grid-axis symmetry. A slowly cycling spatial drift offsets the x and y terms so the pattern wanders across the screen. Composited at moderate alpha so the underlying shade still reads. */ // Palette containing 256 entries of 0xRRGGBB (alpha applied at blend) static uint32_t plasma_palette[256]; // Sine LUT, signed 8-bit. Range: [-127, +127] static int8_t plasma_sin[256]; /* Phase accumulators, advanced once per frame. */ static uint8_t plasma_pa = 0; // sin(x) animation static uint8_t plasma_pb = 0; // sin(y) animation static uint8_t plasma_pc = 0; // sin(diagonal) animation static uint8_t plasma_pd = 0; // sin(radial) animation static uint8_t plasma_pe = 0; // spatial drift static void fx_tea_init(void) { for (int i = 0; i < 256; ++i) { double theta = (double)i * (2.0 * 3.14159265358979323846 / 256.0); plasma_sin[i] = (int8_t)(sin(theta) * 127.0); } const uint8_t stops[5][3] = { { 0x24, 0x0c, 0x05 }, // dark warm brown - troughs { 0x4a, 0x1a, 0x07 }, // deep umber { 0x80, 0x36, 0x0d }, // dark brown { 0xb4, 0x4e, 0x12 }, // brewed mahogany { 0xd4, 0x76, 0x20 } // saturated amber - highlight }; const int n_stops = 5; const int seg = 256 / (n_stops - 1); // 64 for (int i = 0; i < 256; ++i) { int s = i / seg; if (s >= n_stops - 1) s = n_stops - 2; int t = i - s * seg; // 0..63 const uint8_t *a = stops[s]; const uint8_t *b = stops[s + 1]; int r = a[0] + ((b[0] - a[0]) * t) / seg; int g = a[1] + ((b[1] - a[1]) * t) / seg; int bl = a[2] + ((b[2] - a[2]) * t) / seg; plasma_palette[i] = ((uint32_t)r << 24) | ((uint32_t)g << 16) | ((uint32_t)bl << 8); } } static void fx_tea_deinit(void) { // Nothing allocated } static void fx_tea_step(int vw, int vh) { if (vw < 1 || vh < 1) return; if (vw > JGRF_OSD_W) vw = JGRF_OSD_W; if (vh > JGRF_OSD_H) vh = JGRF_OSD_H; plasma_pa += 2; plasma_pb += 1; plasma_pc += 3; plasma_pd += 1; plasma_pe += 1; const uint8_t alpha = 0x50; /* Radial term: centred on the visible region with adaptive ring density. The fixed-point multiplier scales squared distance so that ~5 full sine cycles fit across the half-diagonal, keeping ring count roughly constant at any resolution. */ int cx = vw >> 1; int cy = vh >> 1; int half_diag_sq = cx * cx + cy * cy; int rad_fp = (half_diag_sq > 0) ? ((5 * 256) << 16) / half_diag_sq // 16.16 fixed-point : (1 << 16); /* Spatial drift: the x and y terms are offset by a slowly cycling sine pair 90 degrees apart, tracing a gentle circular wander so the whole pattern drifts rather than breathing in place. */ int drift_x = plasma_sin[plasma_pe] >> 2; // +/-31 px int drift_y = plasma_sin[(uint8_t)(plasma_pe + 64)] >> 2; // 90 deg off for (int y = 0; y < vh; ++y) { int dy = y - cy; int dy2 = dy * dy; // Vertical term, hoisted -- depends only on y int sy = plasma_sin[(uint8_t)((y << 1) + plasma_pb + drift_y)]; for (int x = 0; x < vw; ++x) { int dx = x - cx; // Horizontal, with spatial drift int sx = plasma_sin[(uint8_t)((x << 1) + plasma_pa + drift_x)]; // Rotated diagonal (x + 2y) -- ~27 deg, breaks 45 deg symmetry int sxy = plasma_sin[(uint8_t)(x + (y << 1) + plasma_pc)]; // Radial from visible centre, resolution-adaptive density uint8_t rad = (uint8_t)((((dx * dx + dy2) * rad_fp) >> 16)); int sr = plasma_sin[(uint8_t)(rad + plasma_pd)]; /* Sum of four [-127, +127] terms -> [-508, +508]. Shift to [0, 1016] and divide to land in [0, 255]. */ int idx = (sx + sy + sxy + sr + 508) >> 2; if (idx > 255) idx = 255; canvas_blend(x, y, plasma_palette[idx] | alpha); } } } /* Snow - Shamelessly inspired by ZSNES. A flock of small white particles falling at varied speeds with slight horizontal drift. Flakes are anywhere between half opaque and fully opaque. */ #define FX_SNOW_FLAKES 256 typedef struct { float fx; // Fractional X float fy; // Fractional Y float speed; // Sub-pixel Y advance per frame float drift; // Sub-pixel X advance per frame uint8_t alpha; // 0x80..0xff } snow_flake_t; static snow_flake_t snow_flakes[FX_SNOW_FLAKES]; static uint32_t snow_rng = 0x12345678; static void fx_snow_init(void) { for (size_t i = 0; i < FX_SNOW_FLAKES; ++i) { snow_flakes[i].fx = xorshift32(&snow_rng) % (vis_w > 0 ? vis_w : 1); snow_flakes[i].fy = xorshift32(&snow_rng) % (vis_h > 0 ? vis_h : 1); // Speeds in [0.5, 1.75] snow_flakes[i].speed = 0.5f + ((xorshift32(&snow_rng) % 100) / 100.0f) * 1.25f; // Drift in [-0.4, +0.4] snow_flakes[i].drift = (((xorshift32(&snow_rng) % 100) / 100.0f) - 0.5f) * 0.8f; snow_flakes[i].alpha = 0x80 + (xorshift32(&snow_rng) % 0x80); } } static void fx_snow_deinit(void) { // Nothing allocated } static void fx_snow_step(int vw, int vh) { for (size_t i = 0; i < FX_SNOW_FLAKES; ++i) { snow_flake_t *f = &snow_flakes[i]; /* Reseed any flake whose start-of-frame position is outside the visible region into a uniform spot inside it. The init function scatters flakes across the full canvas because it runs before the visible region is set by the renderer; without this reseed those out-of-region flakes get clamped to top/edges by the per-frame wrap logic below and produce visible clusters around the rim of the visible area for the first second or so. */ if (f->fx < 0.0f || f->fx >= (float)vw || f->fy < 0.0f || f->fy >= (float)vh) { f->fx = (float)(xorshift32(&snow_rng) % (vw > 0 ? vw : 1)); f->fy = (float)(xorshift32(&snow_rng) % (vh > 0 ? vh : 1)); } f->fy += f->speed; f->fx += f->drift; int x = f->fx; int y = f->fy; // Wrap around if (y >= vh) { f->fy = 0.0f; y = 0; f->fx = (float)(xorshift32(&snow_rng) % (vw > 0 ? vw : 1)); x = (int)f->fx; } if (x < 0) { f->fx = (float)(vw - 1); x = vw - 1; } if (x >= vw) { f->fx = 0.0f; x = 0; } if (x >= 0 && x < vw && y >= 0 && y < vh) { uint32_t col = 0xffffff00 | f->alpha; canvas_blend(x, y, col); } } } /* Mushrooms - Amanita mushrooms scattered across the visible canvas, each on its own lifecycle: grow vertically from nothing, hold at full size, fade out, respawn elsewhere. Each mushroom is a filled half-ellipse cap speckled with cream spots, atop a filled trapezoidal stem. Lifecycles are staggered so they don't all pop together. */ #define FX_MUSH_COUNT 32 // Number of mushrooms on screen #define FX_MUSH_GROW 120 // Frames spent growing #define FX_MUSH_HOLD 180 // Frames at full size #define FX_MUSH_FADE 90 // Frames spent fading out #define FX_MUSH_LIFE (FX_MUSH_GROW + FX_MUSH_HOLD + FX_MUSH_FADE) #define FX_MUSH_SPOT_MIN 8 // Minimum Amanita spots per cap #define FX_MUSH_SPOT_MAX 18 // Maximum Amanita spots per cap // Colours as 0xRRGGBB with alpha applied per frame from the lifecycle envelope //#define FX_MUSH_CAP_RGB 0xd45500 // JG Orange, A. muscaria var. guessowii #define FX_MUSH_CAP_RGB 0xd62828 // Red, A. muscaria #define FX_MUSH_STEM_RGB 0xf0e8dd #define FX_MUSH_SPOT_RGB FX_MUSH_STEM_RGB /* Spot position in cap-local normalized coords -- nx is in [-1, 1] across the cap horizontal radius, ny is in [-1, 0] from cap apex (-1) to cap base (0). Stored at spawn so spots are stable between frames. */ typedef struct { float nx; float ny; } mush_spot_t; typedef struct { int base_x; // Horizontal center in canvas pixels int base_y; // Y of the bottom of the stem int cap_rx; // Cap horizontal radius at full size int cap_ry; // Cap vertical radius at full size int stem_h; // Stem height at full size int stem_top_w; // Stem top half-width int stem_bot_w; // Stem bottom half-width int age; // Frames since spawn int nspots; // Number of Amanita spots on this cap mush_spot_t spots[FX_MUSH_SPOT_MAX]; } mushroom_t; static mushroom_t mushrooms[FX_MUSH_COUNT]; static uint32_t mush_rng = 0xfeedface; static float mush_randf(void) { return (xorshift32(&mush_rng) & 0xffff) / 65535.0f; } static void mush_spawn(mushroom_t *m, int vw, int vh, int initial_age) { /* Pick a cap width first, then derive everything else. Cap widths in a range that makes mushrooms feel modest at small canvases and still varied at larger ones. */ int cap_rx_min = 12; int cap_rx_max = 20; if (vw < 160) { cap_rx_min = 6; cap_rx_max = 12; } int cap_rx = cap_rx_min + (int)(mush_randf() * (float)(cap_rx_max - cap_rx_min)); // Cap height ~0.4..0.6 of cap width int cap_ry = (int)((float)cap_rx * (0.4f + mush_randf() * 0.2f)); if (cap_ry < 4) cap_ry = 4; // Stem height ~0.8..1.4 of cap width int stem_h = (int)((float)cap_rx * (0.8f + mush_randf() * 0.6f)); if (stem_h < 6) stem_h = 6; /* Stem top half-width ~0.22..0.38 of cap width, bottom slightly wider for a gentle taper. */ int stem_top_w = (int)((float)cap_rx * (0.22f + mush_randf() * 0.16f)); if (stem_top_w < 2) stem_top_w = 2; int stem_bot_w = stem_top_w + 1 + (int)(mush_randf() * 2.0f); m->cap_rx = cap_rx; m->cap_ry = cap_ry; m->stem_h = stem_h; m->stem_top_w = stem_top_w; m->stem_bot_w = stem_bot_w; m->age = initial_age; /* Generate single pixel Amanita spots in cap-local normalized coords. Sample the upper unit half-disk with a small inset so spots don't land on the rim. Always reach FX_MUSH_SPOT_MIN, try for up to a random target between MIN and MAX, but cap effort on the extras. */ int target = FX_MUSH_SPOT_MIN + (int)(mush_randf() * (float)(FX_MUSH_SPOT_MAX - FX_MUSH_SPOT_MIN + 1)); if (target > FX_MUSH_SPOT_MAX) target = FX_MUSH_SPOT_MAX; m->nspots = 0; int attempts = 0; while (m->nspots < target) { ++attempts; if (attempts > 64 && m->nspots >= FX_MUSH_SPOT_MIN) break; float nx = mush_randf() * 2.0f - 1.0f; // -1..1 float ny = -mush_randf(); // -1..0 (upper half) if (nx * nx + ny * ny > 0.78f) continue; m->spots[m->nspots].nx = nx; m->spots[m->nspots].ny = ny; ++m->nspots; } /* Place the mushroom such that its cap top and stem base fit in the visible area with a small margin. */ int margin = 2; int min_x = cap_rx + margin; int max_x = vw - cap_rx - margin; if (max_x <= min_x) max_x = min_x + 1; int total_h = cap_ry + stem_h; int min_y = total_h + margin; int max_y = vh - margin; if (max_y <= min_y) max_y = min_y + 1; m->base_x = min_x + (int)(mush_randf() * (float)(max_x - min_x)); m->base_y = min_y + (int)(mush_randf() * (float)(max_y - min_y)); } static void fx_mush_init(void) { /* Stagger ages so mushrooms pop in/out of phase. */ for (int i = 0; i < FX_MUSH_COUNT; ++i) mush_spawn(&mushrooms[i], vis_w, vis_h, (FX_MUSH_LIFE * i) / FX_MUSH_COUNT); } static void fx_mush_deinit(void) { // Nothing allocated } // Fill a horizontal scanline segment with a colour, blended static void fx_hline(int x0, int x1, int y, int vw, int vh, uint32_t col) { if (y < 0 || y >= vh) return; if (x0 < 0) x0 = 0; if (x1 >= vw) x1 = vw - 1; for (int x = x0; x <= x1; ++x) canvas_blend(x, y, col); } static void fx_mush_step(int vw, int vh) { for (int i = 0; i < FX_MUSH_COUNT; ++i) { mushroom_t *m = &mushrooms[i]; ++m->age; if (m->age >= FX_MUSH_LIFE) { mush_spawn(m, vw, vh, 0); continue; } /* Lifecycle envelopes --- Growth scales the geometry vertically from 0 to 1 during the grow phase and stays at 1 thereafter. env scales alpha from 0 up during grow, holds at 1, then ramps down during fade. */ float growth, env; if (m->age < FX_MUSH_GROW) { growth = (float)m->age / (float)FX_MUSH_GROW; env = growth; } else if (m->age < FX_MUSH_GROW + FX_MUSH_HOLD) { growth = 1.0f; env = 1.0f; } else { growth = 1.0f; env = (float)(FX_MUSH_LIFE - m->age) / (float)FX_MUSH_FADE; if (env < 0.0f) env = 0.0f; } uint8_t alpha = (uint8_t)(env * 0.6f * 255.0f); if (alpha == 0) continue; uint32_t cap_col = (FX_MUSH_CAP_RGB << 8) | alpha; uint32_t stem_col = (FX_MUSH_STEM_RGB << 8) | alpha; uint32_t spot_col = (FX_MUSH_SPOT_RGB << 8) | alpha; /* Current scaled dimensions -- the horizontal cap radius doesn't grow as fast as the vertical so the cap "pops open" near the end of growth - simulating a mushroom emerging. */ float h_growth = (growth < 0.7f) ? (growth / 0.7f) : 1.0f; int stem_h = (int)((float)m->stem_h * growth); int cap_ry = (int)((float)m->cap_ry * growth); int cap_rx = (int)((float)m->cap_rx * h_growth); if (cap_rx < 1) cap_rx = 1; if (cap_ry < 1) cap_ry = 1; /* Geometry coords -- the cap center is positioned so the cap's bottom touches the top of the stem. */ int cx = m->base_x; int stem_bot_y = m->base_y; int stem_top_y = stem_bot_y - stem_h; int cap_cy = stem_top_y; /* Stem -- trapezoid from just below the cap down to stem_bot_y. Starting one row below stem_top_y means the cap fully owns the overlap region. Stem and cap never blend over each other. The bottom row is trimmed by one pixel each side for a softer foot. */ int top_w = m->stem_top_w; int bot_w = m->stem_bot_w; if (stem_h > 0) { for (int y = stem_top_y + 1; y <= stem_bot_y; ++y) { /* t goes 0 just under the cap, 1 at the bottom. */ float t = (stem_h > 0) ? (float)(y - stem_top_y) / (float)stem_h : 0.0f; int hw = top_w + (int)((float)(bot_w - top_w) * t + 0.5f); if (y == stem_bot_y && hw > 1) --hw; fx_hline(cx - hw, cx + hw, y, vw, vh, stem_col); } } /* Cap -- half-ellipse centred at (cx, cap_cy). For each row from cap_cy - cap_ry up to cap_cy, compute the half-width from the ellipse equation: (x/rx)^2 + (y/ry)^2 = 1 => half_width = rx * sqrt(1 - (y/ry)^2). Skip rows with a half-width below 2 so the cap doesn't end in a single pixel nub at the apex. */ for (int dy = -cap_ry; dy <= 0; ++dy) { float ny = (float)dy / (float)cap_ry; float k = 1.0f - ny * ny; int hw = (int)((float)cap_rx * sqrtf(k) + 0.5f); if (hw < 2) continue; int yy = cap_cy + dy; fx_hline(cx - hw, cx + hw, yy, vw, vh, cap_col); } /* Amanita spots -- skip while the cap is still a sliver. Each spot is a single pixel in cap-local normalized coords, projected onto the current scaled ellipse. The spawn-time inset keeps them inside the cap silhouette at all sizes. */ if (cap_rx >= 6 && cap_ry >= 4) { for (int s = 0; s < m->nspots; ++s) { const mush_spot_t *sp = &m->spots[s]; int sx = cx + (int)(sp->nx * (float)cap_rx + 0.5f); int sy = cap_cy + (int)(sp->ny * (float)cap_ry + 0.5f); if (sx < 0 || sx >= vw || sy < 0 || sy >= vh) continue; canvas_blend(sx, sy, spot_col); } } } } /* Fire - Doom-style propagating fire. Per-pixel intensity is stored in a palette-indexed buffer the size of the canvas. The bottom row is held at maximum heat, seeding the flames. Each frame, every cell takes its value from a cell on the row below with a small random horizontal jitter, minus a small random cooling. The result is a chaotic plume that rises, cools and dissipates, with no per-particle bookkeeping. */ #define FX_FIRE_PAL_MAX 36 // Highest palette index (37 entries, 0..36) static uint32_t fx_fire_palette[FX_FIRE_PAL_MAX + 1]; static uint8_t fx_fire_buf[JGRF_OSD_W * JGRF_OSD_H]; static uint32_t fx_fire_rng = 0xc0ffee42; static void fx_fire_init(void) { memset(fx_fire_buf, 0, sizeof(fx_fire_buf)); /* stops[i] = { index, R, G, B }, sorted by index ascending. Linearly interpolate RGB between consecutive stops. */ const unsigned stops[6][4] = { { 0, 0x07, 0x07, 0x07 }, { 4, 0x28, 0x07, 0x07 }, { 12, 0xa8, 0x1f, 0x07 }, { 24, 0xe0, 0x7b, 0x0b }, { 32, 0xf4, 0xbb, 0x4b }, { 36, 0xff, 0xef, 0xc7 } }; for (int s = 0; s < 6 - 1; ++s) { int i0 = stops[s][0], i1 = stops[s + 1][0]; for (int i = i0; i <= i1; ++i) { float t = (i1 == i0) ? 0.0f : (float)(i - i0) / (float)(i1 - i0); unsigned r = stops[s][1] + ((stops[s + 1][1] - stops[s][1]) * t); unsigned g = stops[s][2] + ((stops[s + 1][2] - stops[s][2]) * t); unsigned b = stops[s][3] + ((stops[s + 1][3] - stops[s][3]) * t); fx_fire_palette[i] = (r << 16) | (g << 8) | b; } } } static void fx_fire_deinit(void) { // Nothing allocated } static void fx_fire_step(int vw, int vh) { if (vw < 1 || vh < 1) return; if (vw > JGRF_OSD_W) vw = JGRF_OSD_W; if (vh > JGRF_OSD_H) vh = JGRF_OSD_H; /* Seed the bottom row with maximum heat. Holding this every frame is what keeps the fire alive. */ uint8_t *seed_row = &fx_fire_buf[(vh - 1) * JGRF_OSD_W]; for (int x = 0; x < vw; ++x) seed_row[x] = FX_FIRE_PAL_MAX; /* Propagate upward: each cell on row y writes its value (minus a small cooling) to a horizontally jittered cell on row y-1. Iterating from the bottom up means sources are read for this frame before they get overwritten as destinations from the row below. The same destination can be written by multiple sources -- that overlap is part of what creates the chaotic plume. */ for (int y = vh - 1; y > 0; --y) { const uint8_t *src_row = &fx_fire_buf[y * JGRF_OSD_W]; uint8_t *dst_row = &fx_fire_buf[(y - 1) * JGRF_OSD_W]; for (int x = 0; x < vw; ++x) { uint8_t pixel = src_row[x]; if (pixel == 0) { dst_row[x] = 0; continue; } uint32_t r = xorshift32(&fx_fire_rng); int rand = r & 3; // 0..3 horizontal jitter int cool = ((r >> 2) & 7) < 3 ? 1 : 0; int dx = x + 2 - rand; if (dx < 0) dx = 0; if (dx >= vw) dx = vw - 1; dst_row[dx] = pixel - cool; } } for (int y = 0; y < vh; ++y) { const uint8_t *row = &fx_fire_buf[y * JGRF_OSD_W]; for (int x = 0; x < vw; ++x) { uint8_t idx = row[x]; if (idx == 0) continue; uint32_t rgb = fx_fire_palette[idx]; int a = 0x40 + (idx * (0xff - 0x40)) / FX_FIRE_PAL_MAX; if (a > 0xff) a = 0xff; canvas_blend(x, y, (rgb << 8) | (uint32_t)a); } } } /* Danmaku - Phyllotactic danmaku bullet emitter. An emitter fires bullets, advancing its golden-angle slot by ~137.5078 degrees between shots. Each bullet flies radially outward at constant speed. Because the golden angle is the "most irrational" rotation, no rational approximation packs the disc evenly -- the bullets in flight trace the interlocking spiral pattern of a sunflower seedhead. Bullets vanish when they leave the visible rectangle (age = -1 marks a free slot). Each frame, one free slot is reused for a new bullet at the center. */ #define FX_DMK_BULLETS 1024 #define FX_DMK_GOLDEN_ANGLE 2.39996322972865332f // ~137.5078 deg in radians #define FX_DMK_TWO_PI 6.28318530717958647692f #define FX_DMK_SPEED 1.2f // Pixels per frame, radial #define FX_DMK_RADIUS 1 // Diamond half-width #define FX_DMK_CULL_AGE 2 // Skip drawing while age < this #define FX_DMK_SPAWN_PER_FRAME 8 // Bullets emitted per frame static float fx_dmk_theta[FX_DMK_BULLETS]; // Wrapped angle, valid if age >= 0 static int fx_dmk_age[FX_DMK_BULLETS]; // -1 = free slot static float fx_dmk_next_theta = 0.0f; // Next slot's angle static void fx_dmk_init(void) { for (int i = 0; i < FX_DMK_BULLETS; ++i) fx_dmk_age[i] = -1; fx_dmk_next_theta = 0.0f; } static void fx_dmk_deinit(void) { // Nothing allocated } static void fx_dmk_step(int vw, int vh) { if (vw < 1 || vh < 1) return; float cx = vw * 0.666666f; float cy = vh * 0.333333f; // Spawn up to N bullets per frame into the first free slots int spawned = 0; for (int i = 0; i < FX_DMK_BULLETS && spawned < FX_DMK_SPAWN_PER_FRAME; ++i) { if (fx_dmk_age[i] < 0) { fx_dmk_theta[i] = fx_dmk_next_theta; fx_dmk_age[i] = 0; // Advance to next slot, keeping theta wrapped in [0, 2pi) fx_dmk_next_theta += FX_DMK_GOLDEN_ANGLE; if (fx_dmk_next_theta >= FX_DMK_TWO_PI) fx_dmk_next_theta -= FX_DMK_TWO_PI; ++spawned; } } // Advance and draw all live bullets for (int i = 0; i < FX_DMK_BULLETS; ++i) { if (fx_dmk_age[i] < 0) continue; fx_dmk_age[i] += 1; float r = FX_DMK_SPEED * (float)fx_dmk_age[i]; float theta = fx_dmk_theta[i]; int x = (int)(cx + r * cosf(theta)); int y = (int)(cy + r * sinf(theta)); // Bullet has left the visible rectangle: mark slot free if (x < 0 || x >= vw || y < 0 || y >= vh) { fx_dmk_age[i] = -1; continue; } // Skip drawing while too close to centre to avoid clumping if (fx_dmk_age[i] < FX_DMK_CULL_AGE) continue; /* Stamp a diamond: pixels where |dx| + |dy| <= radius. The visible rectangle check above only confirms the center is in bounds, so each diamond pixel needs its own clip. */ for (int dy = -FX_DMK_RADIUS; dy <= FX_DMK_RADIUS; ++dy) { int span = FX_DMK_RADIUS - (dy < 0 ? -dy : dy); int py = y + dy; if (py < 0 || py >= vh) continue; for (int dx = -span; dx <= span; ++dx) { int px = x + dx; if (px < 0 || px >= vw) continue; canvas_blend(px, py, 0xe48898a0); } } } } /* ------------------------------------------------------------------------ */ /* Public lifecycle and per-frame */ /* ------------------------------------------------------------------------ */ void jgrf_osd_set_visible(int w, int h) { if (w < 1) w = 1; if (h < 1) h = 1; if (w > JGRF_OSD_W) w = JGRF_OSD_W; if (h > JGRF_OSD_H) h = JGRF_OSD_H; vis_w = w; vis_h = h; } void jgrf_osd_rehash(void) { if (!settings) return; switch (settings[MISC_FONT].val) { default: case 0: { // 6x8 font = sixbyeight; fwidth = 6; fheight = 8; break; } case 1: { // 8x8 font = eightbyeight; fwidth = 8; fheight = 8; break; } } jgrf_osd_toggle(osdactive); } void jgrf_osd_toggle(int active) { osdactive = active; if (osdactive) { int bgfx = settings[MISC_BGFX].val; // Don't reset any active effects if (!(bgfx >= 2 && fx_active[bgfx - 2])) jgrf_osd_fx_clear(); // Reset OSD font colour osd_text_rgba = JGRF_OSD_ORANGE; // Set new shade and/or effect switch (bgfx) { case 0: default: { // None jgrf_osd_shade_set(0x00000000); break; } case 1: { // Shade jgrf_osd_shade_set(0xd4550040); // JG break; } case 2: { // Tea Swirl jgrf_osd_fx_enable(JGRF_OSD_FX_TEA); jgrf_osd_shade_set(0x6e260e80); // Oolong break; } case 3: { // Snow jgrf_osd_fx_enable(JGRF_OSD_FX_SNOW); jgrf_osd_shade_set(0x1010a080); // ZSNESish osd_text_rgba = 0xd0d0ffff; // Ice break; } case 4: { // Mushrooms jgrf_osd_fx_enable(JGRF_OSD_FX_MUSH); jgrf_osd_shade_set(0x000000a0); // Dark osd_text_rgba = 0xff2020ff; // Unapologetic red break; } case 5: { // Fire jgrf_osd_fx_enable(JGRF_OSD_FX_FIRE); jgrf_osd_shade_set(0x000000d0); // Very dark break; } case 6: { // Danmaku jgrf_osd_fx_enable(JGRF_OSD_FX_DMK); jgrf_osd_shade_set(0xa0); // Dark osd_text_rgba = 0x666bffff; break; } } } else { jgrf_osd_shade_set(0x00000000); jgrf_osd_fx_clear(); } } void jgrf_osd_init(void) { settings = jgrf_settings_ptr(); canvas_clear(); shade_rgba = 0; for (int i = 0; i < JGRF_OSD_NUM_FX; ++i) fx_active[i] = 0; for (int i = 0; i < JGRF_OSD_NUM_TEXT_SLOTS; ++i) { text_slots[i].frames = 0; text_slots[i].text[0] = '\0'; } jgrf_osd_rehash(); } void jgrf_osd_deinit(void) { jgrf_osd_fx_clear(); } void jgrf_osd_step(void) { // Decrement text frame countdowns, persistent slots are left alone for (size_t i = 0; i < JGRF_OSD_NUM_TEXT_SLOTS; ++i) { if (text_slots[i].frames > 0) --text_slots[i].frames; } } const void *jgrf_osd_composite(int *active, int *dirty) { int has_shade = shade_rgba != 0; int has_fx = fx_any_active(); int has_text = text_active(); if (active) *active = (has_shade || has_fx || has_text); if (dirty) *dirty = (has_shade || has_fx || has_text); if (!has_shade && !has_fx && !has_text) return canvas; // Nothing drawn, early return // Layer 0: Shade or clear if (has_shade) canvas_fill_visible(shade_rgba); else canvas_clear(); // Layer 1: Effects if (has_fx) { for (size_t i = 0; i < JGRF_OSD_NUM_FX; ++i) { if (fx_active[i] && fx_table[i].step) fx_table[i].step(vis_w, vis_h); } } // Layer 2: Text if (has_text) { for (int i = 0; i < JGRF_OSD_NUM_TEXT_SLOTS; ++i) { jgrf_osd_text_slot_t *s = &text_slots[i]; if (s->frames == 0 || s->text[0] == '\0') continue; int tx, ty; resolve_anchor(s, &tx, &ty); jgrf_osd_render_text(tx, ty, s->rgba, s->text); } } return canvas; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/osd.h000066400000000000000000000074201523150047600215250ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef OSD_H #define OSD_H #include /* Fixed canvas dimensions. The OSD always rasterises into a 640x480 RGBA8888 buffer in its own logical pixel space. The renderer composites a portion of this canvas over the post-shader output as an alpha-blended quad, with integer scaling determined by the current render dimensions. */ #define JGRF_OSD_W 640 #define JGRF_OSD_H 480 // Text slots enum { JGRF_OSD_TEXT_FRONTEND = 0, // Frontend messages (bottom left) JGRF_OSD_TEXT_CORE = 1, // Core messages (bottom right) JGRF_OSD_TEXT_MENU = 2, // Menu / Input Config (top left) JGRF_OSD_TEXT_AUX = 3, // Reserved for future use JGRF_OSD_NUM_TEXT_SLOTS }; // Pixel effects enum { JGRF_OSD_FX_TEA = 0, JGRF_OSD_FX_SNOW = 1, JGRF_OSD_FX_MUSH = 2, JGRF_OSD_FX_FIRE = 3, JGRF_OSD_FX_DMK = 4, JGRF_OSD_NUM_FX }; // Text positioning anchor enum { JGRF_OSD_ANCHOR_TL = 0, JGRF_OSD_ANCHOR_TR = 1, JGRF_OSD_ANCHOR_BL = 2, JGRF_OSD_ANCHOR_BR = 3 }; void jgrf_osd_init(void); void jgrf_osd_deinit(void); void jgrf_osd_rehash(void); void jgrf_osd_toggle(int active); void jgrf_osd_set_visible(int vis_w, int vis_h); // Shade layer - set an RGBA value to shade, or 0 disable void jgrf_osd_shade_set(uint32_t rgba); // Effects layer void jgrf_osd_fx_enable(int fx_id); void jgrf_osd_fx_disable(int fx_id); void jgrf_osd_fx_clear(void); /* Text layer: (X, Y) is in OSD canvas pixels, interpreted relative to the anchor corner of the visible region. frames=0 means inactive, frames>0 means countdown, frames=-1 means persistent until cleared. Pass NULL or empty string with frames=0 to clear. */ void jgrf_osd_text(int slot, int frames, int anchor, int x, int y, uint32_t rgba, const char *text); void jgrf_osd_text_clear(int slot); // Per-frame: advance effects, decrement text countdowns, perform redraws void jgrf_osd_step(void); /* Composite all active layers into the canvas and return a pointer to the RGBA8888 pixel data. *active is set to 1 if anything was drawn (renderer should draw the OSD quad), 0 otherwise (renderer should skip). *dirty is set to 1 if the canvas content changed since the last call (renderer should re-upload the texture); 0 otherwise. */ const void *jgrf_osd_composite(int *active, int *dirty); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/paths.c000066400000000000000000000224441523150047600220550ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Programmer beware! Ugly code lives below. #include #include #include #include #include #include "jgrf.h" #include "cli.h" #include "detect.h" #include "paths.h" #if defined(__MINGW32__) || defined(__MINGW64__) #define MKDIRR mkdir(tmp) #else #define MKDIRR mkdir(tmp, S_IRWXU) #endif // Recursive mkdir (similar to mkdir -p) static void mkdirr(const char *dir) { char tmp[256]; char *p = NULL; size_t len = snprintf(tmp, sizeof(tmp), "%s", dir); if (tmp[len - 1] == SEP) tmp[len - 1] = 0; for (p = tmp + 1; *p; ++p) { if (*p == SEP) { *p = 0; MKDIRR; *p = SEP; } } MKDIRR; } // Create user directories void jgrf_paths_mkdirs(void) { jgrf_gdata_t *gdata = jgrf_gdata_ptr(); mkdirr(gdata->configpath); mkdirr(gdata->datapath); mkdirr(gdata->biospath); mkdirr(gdata->userassets); mkdirr(gdata->cheatpath); mkdirr(gdata->savepath); mkdirr(gdata->statepath); mkdirr(gdata->sspath); } void jgrf_paths_set_userdirs(void) { #if defined(JGRF_STATIC) const char *binname = jg_get_coreinfo("")->name; #else const char *binname = "jollygood"; #endif jgrf_gdata_t *gdata = jgrf_gdata_ptr(); // Set up user config path #if defined(JGRF_STATIC) && (defined(_WIN32) \ || defined(__MINGW32__) || defined(__MINGW64__)) (void)binname; snprintf(gdata->configpath, sizeof(gdata->configpath), "%s%c", gdata->binpath, SEP); snprintf(gdata->datapath, sizeof(gdata->datapath), "%s%c", gdata->binpath, SEP); #else if (getenv("XDG_CONFIG_HOME")) snprintf(gdata->configpath, sizeof(gdata->configpath), "%s%c%s%c", getenv("XDG_CONFIG_HOME"), SEP, binname, SEP); else snprintf(gdata->configpath, sizeof(gdata->configpath), "%s%c.config%c%s%c", getenv("HOME"), SEP, SEP, binname, SEP); // Set up user data path if (getenv("XDG_DATA_HOME")) snprintf(gdata->datapath, sizeof(gdata->datapath), "%s%c%s%c", getenv("XDG_DATA_HOME"), SEP, binname, SEP); else snprintf(gdata->datapath, sizeof(gdata->datapath), "%s%c.local%cshare%c%s%c", getenv("HOME"), SEP, SEP, SEP, binname, SEP); #endif // Set up screenshot path snprintf(gdata->sspath, sizeof(gdata->sspath), "%sscreenshots%c", gdata->datapath, SEP); } #ifdef JGRF_STATIC void jgrf_paths_set_corelib(void) { jgrf_gdata_t *gdata = jgrf_gdata_ptr(); snprintf(gdata->corelib, sizeof(gdata->corelib), "%s", "Statically linked"); snprintf(gdata->corename, sizeof(gdata->corename), "%s", jg_get_coreinfo("")->name); } void jgrf_paths_set_coreassets(void) { jgrf_gdata_t *gdata = jgrf_gdata_ptr(); snprintf(gdata->coreassets, sizeof(gdata->coreassets), "%s", gdata->binpath); #if defined(DATADIR) // Check for core assets system-wide char coreassets[256]; snprintf(coreassets, sizeof(coreassets), "%s%cjollygood%c%s", DATADIR, SEP, SEP, gdata->corename); struct stat fbuf; if (stat(coreassets, &fbuf) == 0) snprintf(gdata->coreassets, sizeof(gdata->coreassets), "%s", coreassets); #endif // defined(DATADIR) } #else void jgrf_paths_set_corelib(void) { jgrf_gdata_t *gdata = jgrf_gdata_ptr(); // Detect the default core for the system if (jgrf_cli_core()) { snprintf(gdata->corename, sizeof(gdata->corename), "%s", jgrf_cli_core()); } else if (!jgrf_detect_core()) jgrf_log(JG_LOG_ERR, "Cannot detect usable core, or invalid file. Exiting...\n"); // Set the core path to the local core path snprintf(gdata->corelib, sizeof(gdata->corelib), "%s%ccores%c%s%c%s.%s", gdata->binpath, SEP, SEP, gdata->corename, SEP, gdata->corename, SOEXT); // Check if a core exists at that path struct stat fbuf; int corefound = 0; if (stat(gdata->corelib, &fbuf) == 0) { corefound = 1; } #if defined(LIBDIR) && defined(DATADIR) // Check for the core system-wide else { snprintf(gdata->corelib, sizeof(gdata->corelib), "%s%cjollygood%c%s.%s", LIBDIR, SEP, SEP, gdata->corename, SOEXT); if (stat(gdata->corelib, &fbuf) == 0) corefound = 1; } #endif // defined(LIBDIR) && defined(DATADIR) /* If the core couldn't be loaded, try a backup core for this system until the list is exhausted. */ if (!corefound) { if (jgrf_cli_core()) jgrf_log(JG_LOG_ERR, "Failed to locate core. Exiting...\n"); else jgrf_paths_set_corelib(); } } void jgrf_paths_set_coreassets(void) { jgrf_gdata_t *gdata = jgrf_gdata_ptr(); int assetsfound = 0; struct stat fbuf; snprintf(gdata->coreassets, sizeof(gdata->coreassets), "%s%ccores%c%s", gdata->binpath, SEP, SEP, gdata->corename); if (stat(gdata->coreassets, &fbuf) == 0) { assetsfound = 1; } #if defined(LIBDIR) && defined(DATADIR) // Check for the core system-wide else { snprintf(gdata->coreassets, sizeof(gdata->coreassets), "%s%cjollygood%c%s", DATADIR, SEP, SEP, gdata->corename); if (stat(gdata->coreassets, &fbuf) == 0) assetsfound = 1; } #endif // defined(LIBDIR) && defined(DATADIR) /* Not all cores have assets, so empty the core asset path and assume the core will handle any missing assets accordingly. */ if (!assetsfound) snprintf(gdata->coreassets, sizeof(gdata->coreassets), "%s", ""); } #endif // JGRF_STATIC int jgrf_paths_env_corelib(void) { #ifdef JGRF_STATIC return 0; #endif if (!getenv("JOLLYGOOD_CORE_DIRS")) return 0; jgrf_gdata_t *gdata = jgrf_gdata_ptr(); // Detect the default core for the system if (jgrf_cli_core()) { snprintf(gdata->corename, sizeof(gdata->corename), "%s", jgrf_cli_core()); } else if (!jgrf_detect_core()) jgrf_log(JG_LOG_ERR, "Cannot detect default core, or invalid file. Exiting...\n"); // Allocate enough memory for the entire env var + null terminator size_t len = strlen(getenv("JOLLYGOOD_CORE_DIRS")) + 1; char *dirs = calloc(len, sizeof(char)); snprintf(dirs, len, "%s", getenv("JOLLYGOOD_CORE_DIRS")); char *delim = PATHSEP; char *token = strtok(dirs, delim); char corelib[256]; struct stat fbuf; int corefound = 0; while (token) { // Check for the core in this path snprintf(corelib, sizeof(corelib), "%s%c%s.%s", token, SEP, gdata->corename, SOEXT); token = strtok(NULL, delim); if (stat(corelib, &fbuf) == 0) { snprintf(gdata->corelib, sizeof(gdata->corelib), "%s", corelib); corefound = 1; break; } } // Cleanup free(dirs); return corefound; } int jgrf_paths_env_coreassets(void) { if (!getenv("JOLLYGOOD_ASSET_DIRS")) return 0; jgrf_gdata_t *gdata = jgrf_gdata_ptr(); // Allocate enough memory for the entire env var + null terminator size_t len = strlen(getenv("JOLLYGOOD_ASSET_DIRS")) + 1; char *dirs = calloc(len, sizeof(char)); snprintf(dirs, len, "%s", getenv("JOLLYGOOD_ASSET_DIRS")); char *delim = PATHSEP; char *token = strtok(dirs, delim); struct stat fbuf; int assetsfound = 0; /* Check if the directory exists -- since asset filenames are unknown at this stage, assume the first existing directory in the list contains assets the core is able to use. */ while (token) { snprintf(gdata->coreassets, sizeof(gdata->coreassets), "%s%c%s", token, SEP, gdata->corename); token = strtok(NULL, delim); if (stat(gdata->coreassets, &fbuf) == 0) { assetsfound = 1; break; } } // Cleanup free(dirs); return assetsfound; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/paths.h000066400000000000000000000033471523150047600220630ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef PATHS_H #define PATHS_H void jgrf_paths_mkdirs(void); void jgrf_paths_set_userdirs(void); void jgrf_paths_set_corelib(void); void jgrf_paths_set_coreassets(void); int jgrf_paths_env_corelib(void); int jgrf_paths_env_coreassets(void); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/settings.c000066400000000000000000000332011523150047600225670ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "tconfig.h" #include "jgrf.h" #include "settings.h" static ini_table_s *conf; static jgrf_gdata_t *gdata; static size_t numemusettings = 0; static jg_setting_t *emusettings = NULL; static int *emusettings_default = NULL; static size_t numsettings = 0; static int *settings_default = NULL; static jg_setting_t settings[] = { { "", "Audio: Resampler Quality", "N = Resampler Quality", "", 3, 0, 10, 1 }, { "", "Video: API", #ifdef JGRF_VULKAN "0 = OpenGL Core, 1 = OpenGL ES, 2 = OpenGL Compatibility, 3 = Vulkan", "", 0, 0, 3, 1 #else "0 = OpenGL Core, 1 = OpenGL ES, 2 = OpenGL Compatibility", "", 0, 0, 2, 1 #endif }, { "", "Video: Fullscreen", "0 = Disabled, 1 = Enabled", "", 0, 0, 1, 0 }, { "", "Video: Scale (Initial)", "N = Video Scale Factor", "", 3, 1, 8, 1 }, { "", "Video: Shader", "0 = Nearest Neighbour, 1 = Linear, 2 = Sharp Bilinear, " "3 = Anti-Aliased Nearest Neighbour, 4 = CRT-Yee64, 5 = CRTea, 6 = LCD", "", 2, 0, 6, 0 }, { "", "Video: CRTea Mode", "0 = Scanlines, 1 = Aperture Grille Lite, 2 = Aperture Grille, " "3 = Shadow Mask, 4 = Custom", "", 2, 0, 4, 0 }, { "", "CRTea: Custom Mask Type", "0 = No Mask, 1 = Aperture Grille Lite, 2 = Aperture Grille, " "3 = Shadow Mask", "", 2, 0, 3, 0 }, { "", "CRTea: Mask Strength", "N = CRTea Mask Strength", "", 5, 0, 10, 0 }, { "", "CRTea: Scanline Strength", "N = CRTea Scanline Strength", "", 6, 0, 10, 0 }, { "", "CRTea: Sharpness", "N = CRTea Sharpness", "", 7, 0, 10, 0 }, { "", "CRTea: Curve", "N = CRTea Curvature", "", 2, 0, 10, 0 }, { "", "CRTea: Corner", "N = CRTea Corner", "", 3, 0, 10, 0 }, { "", "CRTea: Trinitron Curve", "N = CRTea Trinitron Curvature", "", 10, 0, 10, 0 }, { "", "Input: Axis Deadzone", "N = Deadzone Size", "", 5, 0, 9, 0 }, { "", "Cheats: Auto-activate at Boot", "0 = Disabled, 1 = Enabled", "", 0, 0, 1, 1 }, { "", "Log Level: Core", "0 = Debug, 1 = Info, 2 = Warning, 3 = Error", "", 1, 0, 3, 0 }, { "", "Log Level: Frontend", "0 = Debug, 1 = Info, 2 = Warning, 3 = Error", "", 1, 0, 3, 0 }, { "", "OSD: Background Effects", "0 = None, 1 = Shade, 2 = Tea, 3 = Snow, " "4 = Mushrooms, 5 = Fire, 6 = Danmaku", "", 2, 0, 6, 0 }, { "", "OSD: Font", "0 = 6x8, 1 = 8x8", "", 0, 0, 1, 0 }, }; // Read a setting with boundary check static void jgrf_setting_rd(const char *s, const char *n, jg_setting_t *t) { // section, name, setting if (ini_table_check_entry(conf, s, n)) { int val_orig = t->val; // Save the default value ini_table_get_entry_as_int(conf, s, n, &(t->val)); // Read the value // Reset to default if out of bounds if ((t->val > t->max) || (t->val < t->min)) t->val = val_orig; } } // Handle reading of settings static void jgrf_settings_handler(void) { // Audio jgrf_setting_rd("audio", "rsqual", &settings[AUDIO_RSQUAL]); // Video jgrf_setting_rd("video", "api", &settings[VIDEO_API]); jgrf_setting_rd("video", "fullscreen", &settings[VIDEO_FULLSCREEN]); jgrf_setting_rd("video", "scale", &settings[VIDEO_SCALE]); jgrf_setting_rd("video", "shader", &settings[VIDEO_SHADER]); jgrf_setting_rd("video", "crtea_mode", &settings[VIDEO_CRTEA_MODE]); jgrf_setting_rd("video", "crtea_masktype", &settings[VIDEO_CRTEA_MASKTYPE]); jgrf_setting_rd("video", "crtea_maskstr", &settings[VIDEO_CRTEA_MASKSTR]); jgrf_setting_rd("video", "crtea_scanstr", &settings[VIDEO_CRTEA_SCANSTR]); jgrf_setting_rd("video", "crtea_sharpness", &settings[VIDEO_CRTEA_SHARPNESS]); jgrf_setting_rd("video", "crtea_curve", &settings[VIDEO_CRTEA_CURVE]); jgrf_setting_rd("video", "crtea_corner", &settings[VIDEO_CRTEA_CORNER]); jgrf_setting_rd("video", "crtea_tcurve", &settings[VIDEO_CRTEA_TCURVE]); // Input jgrf_setting_rd("input", "axis_deadzone", &settings[INPUT_AXIS_DEADZONE]); // Misc jgrf_setting_rd("misc", "cheatauto", &settings[MISC_CHEATAUTO]); jgrf_setting_rd("misc", "corelog", &settings[MISC_CORELOG]); jgrf_setting_rd("misc", "frontendlog", &settings[MISC_FRONTENDLOG]); jgrf_setting_rd("misc", "bgfx", &settings[MISC_BGFX]); jgrf_setting_rd("misc", "font", &settings[MISC_FONT]); } // Read the general settings ini to override defaults static void jgrf_settings_read(void) { char path[192]; snprintf(path, sizeof(path), "%ssettings.ini", gdata->configpath); conf = ini_table_create(); if (!ini_table_read_from_file(conf, path)) jgrf_log(JG_LOG_DBG, "Main configuration file not found: %s\n", path); else jgrf_settings_handler(); // Clean up the config data ini_table_destroy(conf); } // Initialize the settings to defaults and grab global data pointer int jgrf_settings_init(void) { // Grab global data pointer gdata = jgrf_gdata_ptr(); // Store settings defaults to allow restoration later numsettings = sizeof(settings) / sizeof(jg_setting_t); settings_default = (int*)calloc(numsettings, sizeof(int)); for (size_t i = 0; i < numsettings; ++i) settings_default[i] = settings[i].val; // Read settings from config file jgrf_settings_read(); return 1; } void jgrf_settings_deinit(void) { if (settings_default) free(settings_default); if (emusettings_default) free(emusettings_default); } // Read core-specific overrides for frontend settings void jgrf_settings_override(const char *name) { char overridepath[256]; snprintf(overridepath, sizeof(overridepath), "%s%s.ini", gdata->configpath, name); conf = ini_table_create(); if (!ini_table_read_from_file(conf, overridepath)) { jgrf_log(JG_LOG_DBG, "Override configuration file not found: %s\n", overridepath); } else jgrf_settings_handler(); // Clean up the config data ini_table_destroy(conf); } jg_setting_t* jgrf_settings_ptr(void) { return settings; } jg_setting_t* jgrf_settings_emu_ptr(size_t *num) { *num = numemusettings; return emusettings; } // Read core-specific settings - "Emulator Settings" void jgrf_settings_emu(jg_setting_t* (*get_settings)(size_t*)) { // Get number of settings and set local pointer to core settings array emusettings = get_settings(&numemusettings); if (!numemusettings) { jgrf_log(JG_LOG_DBG, "No Emulator Settings\n"); return; } // Create an array of default settings emusettings_default = (int*)calloc(numemusettings, sizeof(int)); // Build the .ini path for emulator-specific settings char path[256]; snprintf(path, sizeof(path), "%s%s.ini", gdata->configpath, gdata->corename); // Create config structure conf = ini_table_create(); if (!ini_table_read_from_file(conf, path)) jgrf_log(JG_LOG_DBG, "Core configuration file not found: %s\n", path); for (size_t i = 0; i < numemusettings; ++i) { if (emusettings[i].max < emusettings[i].min) { jgrf_log(JG_LOG_ERR, "Max < Min for setting: %s\n", emusettings[i].name); } emusettings_default[i] = emusettings[i].val; // Store default setting if (ini_table_check_entry(conf, gdata->corename, emusettings[i].name)) { int val; ini_table_get_entry_as_int(conf, gdata->corename, emusettings[i].name, &val); if (val <= emusettings[i].max && val >= emusettings[i].min) emusettings[i].val = val; else jgrf_log(JG_LOG_WRN, "Setting out of range: %s = %d\n", emusettings[i].name, val); } } // Clean up the config data ini_table_destroy(conf); } static void jgrf_settings_write_frontend(void) { char ibuf[5]; // Buffer to hold integers converted to strings // Audio snprintf(ibuf, sizeof(ibuf), "%d", settings[AUDIO_RSQUAL].val); ini_table_create_entry(conf, "audio", "rsqual", ibuf); // Video snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_API].val); ini_table_create_entry(conf, "video", "api", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_FULLSCREEN].val); ini_table_create_entry(conf, "video", "fullscreen", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_SCALE].val); ini_table_create_entry(conf, "video", "scale", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_SHADER].val); ini_table_create_entry(conf, "video", "shader", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_CRTEA_MODE].val); ini_table_create_entry(conf, "video", "crtea_mode", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_CRTEA_MASKTYPE].val); ini_table_create_entry(conf, "video", "crtea_masktype", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_CRTEA_MASKSTR].val); ini_table_create_entry(conf, "video", "crtea_maskstr", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_CRTEA_SCANSTR].val); ini_table_create_entry(conf, "video", "crtea_scanstr", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_CRTEA_SHARPNESS].val); ini_table_create_entry(conf, "video", "crtea_sharpness", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_CRTEA_CURVE].val); ini_table_create_entry(conf, "video", "crtea_curve", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_CRTEA_CORNER].val); ini_table_create_entry(conf, "video", "crtea_corner", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[VIDEO_CRTEA_TCURVE].val); ini_table_create_entry(conf, "video", "crtea_tcurve", ibuf); // Input snprintf(ibuf, sizeof(ibuf), "%d", settings[INPUT_AXIS_DEADZONE].val); ini_table_create_entry(conf, "input", "axis_deadzone", ibuf); // Misc snprintf(ibuf, sizeof(ibuf), "%d", settings[MISC_CHEATAUTO].val); ini_table_create_entry(conf, "misc", "cheatauto", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[MISC_CORELOG].val); ini_table_create_entry(conf, "misc", "corelog", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[MISC_FRONTENDLOG].val); ini_table_create_entry(conf, "misc", "frontendlog", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[MISC_BGFX].val); ini_table_create_entry(conf, "misc", "bgfx", ibuf); snprintf(ibuf, sizeof(ibuf), "%d", settings[MISC_FONT].val); ini_table_create_entry(conf, "misc", "font", ibuf); } static void jgrf_settings_write_emu(void) { if (!numemusettings) { jgrf_log(JG_LOG_DBG, "No Emulator Settings\n"); return; } char ibuf[5]; // Buffer to hold integers converted to strings for (size_t i = 0; i < numemusettings; ++i) { snprintf(ibuf, sizeof(ibuf), "%d", emusettings[i].val); ini_table_create_entry(conf, gdata->corename, emusettings[i].name, ibuf); } } void jgrf_settings_write(int opts) { // Create data structure conf = ini_table_create(); char path[256]; if (opts & SETTINGS_EMULATOR) { // Emulator Settings snprintf(path, sizeof(path), "%s%s.ini", gdata->configpath, gdata->corename); jgrf_settings_write_emu(); if (opts & SETTINGS_FRONTEND) // Combined Settings jgrf_settings_write_frontend(); } else if (opts & SETTINGS_FRONTEND) { // Frontend Settings snprintf(path, sizeof(path), "%ssettings.ini", gdata->configpath); jgrf_settings_write_frontend(); } ini_table_write_to_file(conf, path); // Clean up the config data ini_table_destroy(conf); } void jgrf_settings_default(int opts) { if (opts & SETTINGS_FRONTEND) { for (size_t i = 0; i < numsettings; ++i) settings[i].val = settings_default[i]; jgrf_rehash_frontend(); } if (opts & SETTINGS_EMULATOR) { for (size_t i = 0; i < numemusettings; ++i) emusettings[i].val = emusettings_default[i]; jgrf_rehash_core(); } } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/settings.h000066400000000000000000000045141523150047600226010ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef SETTINGS_H #define SETTINGS_H #define SETTINGS_FRONTEND 0x01 #define SETTINGS_EMULATOR 0x02 enum _jgrf_settings { AUDIO_RSQUAL, VIDEO_API, VIDEO_FULLSCREEN, VIDEO_SCALE, VIDEO_SHADER, VIDEO_CRTEA_MODE, VIDEO_CRTEA_MASKTYPE, VIDEO_CRTEA_MASKSTR, VIDEO_CRTEA_SCANSTR, VIDEO_CRTEA_SHARPNESS, VIDEO_CRTEA_CURVE, VIDEO_CRTEA_CORNER, VIDEO_CRTEA_TCURVE, INPUT_AXIS_DEADZONE, MISC_CHEATAUTO, MISC_CORELOG, MISC_FRONTENDLOG, MISC_BGFX, MISC_FONT, JGRF_SETTINGS_MAX }; int jgrf_settings_init(void); void jgrf_settings_deinit(void); jg_setting_t* jgrf_settings_ptr(void); jg_setting_t* jgrf_settings_emu_ptr(size_t*); void jgrf_settings_emu(jg_setting_t* (*)(size_t*)); void jgrf_settings_override(const char *); void jgrf_settings_write(int); void jgrf_settings_default(int); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/000077500000000000000000000000001523150047600222155ustar00rootroot00000000000000jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/aann.frag000066400000000000000000000071371523150047600240030ustar00rootroot00000000000000/* MIT License AntiAliased Nearest Neighbor Copyright (c) 2015 nyanpasu64 and wareya 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. */ #version 450 layout(set = 0, binding = 0) uniform sampler2D source; layout(push_constant) uniform Push { vec4 sourceSize; vec4 targetSize; int masktype; float maskstr; float scanstr; float sharpness; float curve; float corner; float tcurve; } pc; layout(location = 0) in vec2 texCoord; layout(location = 0) out vec4 fragColor; #define NOT(fl) (1.0-fl) #define YES(fl) fl vec4 vpow(vec4 n, float e) { return vec4(pow(n.x, e), pow(n.y, e), pow(n.z, e), pow(n.w, e)); } vec4 getLQV(vec3 mine) { return vec4 ( mine.r , mine.g , mine.b , mine.r*0.2989 + mine.g*0.5870 + mine.b*0.1140); } vec3 fromLQV(vec4 mine) { float f = mine.w/(mine.r*0.2989 + mine.g*0.5870 + mine.b*0.1140); return vec3(mine.rgb)*f; } vec3 percent(float ssize, float tsize, float coord) { float minfull = (coord*tsize - 0.5) /tsize*ssize; float maxfull = (coord*tsize + 0.5) /tsize*ssize; float realfull = floor(maxfull); if (minfull > realfull) { return vec3(1, (realfull+0.5)/ssize, (realfull+0.5)/ssize); } return vec3( (maxfull - realfull) / (maxfull - minfull), (realfull-0.5) / ssize, (realfull+0.5) / ssize ); } void main() { float cheapsrgb = 2.1; float gamma = 3.0; vec3 xstuff = percent(pc.sourceSize.x, pc.targetSize.x, texCoord.x); vec3 ystuff = percent(pc.sourceSize.y, pc.targetSize.y, texCoord.y); float xkeep = xstuff[0]; float ykeep = ystuff[0]; // get points to interpolate across, in linear rgb vec4 a = getLQV(vpow(texture(source,vec2(xstuff[1],ystuff[1])), cheapsrgb).rgb); vec4 b = getLQV(vpow(texture(source,vec2(xstuff[2],ystuff[1])), cheapsrgb).rgb); vec4 c = getLQV(vpow(texture(source,vec2(xstuff[1],ystuff[2])), cheapsrgb).rgb); vec4 d = getLQV(vpow(texture(source,vec2(xstuff[2],ystuff[2])), cheapsrgb).rgb); // use perceptual gamma for luminance component a.w = pow(a.w, 1.0/gamma); b.w = pow(b.w, 1.0/gamma); c.w = pow(c.w, 1.0/gamma); d.w = pow(d.w, 1.0/gamma); // interpolate vec4 gammaLQVresult = NOT(xkeep)*NOT(ykeep)*a + YES(xkeep)*NOT(ykeep)*b + NOT(xkeep)*YES(ykeep)*c + YES(xkeep)*YES(ykeep)*d; // change luminance gamma back to linear vec4 LQVresult = gammaLQVresult; LQVresult.w = pow(gammaLQVresult.w, gamma); // convert back to srgb; lqv -> lrgb -> srgb vec4 c1 = vpow(vec4(fromLQV(LQVresult), 1), 1.0/cheapsrgb); fragColor = c1; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/aann.fs000066400000000000000000000065611523150047600234740ustar00rootroot00000000000000/* MIT License AntiAliased Nearest Neighbor Copyright (c) 2015 nyanpasu64 and wareya 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. */ precision highp float; uniform sampler2D source; uniform vec4 sourceSize; uniform vec4 targetSize; in vec2 texCoord; out vec4 fragColor; #define NOT(fl) (1.0-fl) #define YES(fl) fl vec4 vpow(vec4 n, float e) { return vec4(pow(n.x, e), pow(n.y, e), pow(n.z, e), pow(n.w, e)); } vec4 getLQV(vec3 mine) { return vec4 ( mine.r , mine.g , mine.b , mine.r*0.2989 + mine.g*0.5870 + mine.b*0.1140); } vec3 fromLQV(vec4 mine) { float f = mine.w/(mine.r*0.2989 + mine.g*0.5870 + mine.b*0.1140); return vec3(mine.rgb)*f; } vec3 percent(float ssize, float tsize, float coord) { float minfull = (coord*tsize - 0.5) /tsize*ssize; float maxfull = (coord*tsize + 0.5) /tsize*ssize; float realfull = floor(maxfull); if (minfull > realfull) { return vec3(1, (realfull+0.5)/ssize, (realfull+0.5)/ssize); } return vec3( (maxfull - realfull) / (maxfull - minfull), (realfull-0.5) / ssize, (realfull+0.5) / ssize ); } void main() { float cheapsrgb = 2.1; float gamma = 3.0; vec3 xstuff = percent(sourceSize.x, targetSize.x, texCoord.x); vec3 ystuff = percent(sourceSize.y, targetSize.y, texCoord.y); float xkeep = xstuff[0]; float ykeep = ystuff[0]; // get points to interpolate across, in linear rgb vec4 a = getLQV(vpow(texture(source,vec2(xstuff[1],ystuff[1])), cheapsrgb).rgb); vec4 b = getLQV(vpow(texture(source,vec2(xstuff[2],ystuff[1])), cheapsrgb).rgb); vec4 c = getLQV(vpow(texture(source,vec2(xstuff[1],ystuff[2])), cheapsrgb).rgb); vec4 d = getLQV(vpow(texture(source,vec2(xstuff[2],ystuff[2])), cheapsrgb).rgb); // use perceptual gamma for luminance component a.w = pow(a.w, 1.0/gamma); b.w = pow(b.w, 1.0/gamma); c.w = pow(c.w, 1.0/gamma); d.w = pow(d.w, 1.0/gamma); // interpolate vec4 gammaLQVresult = NOT(xkeep)*NOT(ykeep)*a + YES(xkeep)*NOT(ykeep)*b + NOT(xkeep)*YES(ykeep)*c + YES(xkeep)*YES(ykeep)*d; // change luminance gamma back to linear vec4 LQVresult = gammaLQVresult; LQVresult.w = pow(gammaLQVresult.w, gamma); // convert back to srgb; lqv -> lrgb -> srgb vec4 c1 = vpow(vec4(fromLQV(LQVresult), 1), 1.0/cheapsrgb); fragColor = c1; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/crt-yee64.frag000066400000000000000000000126311523150047600246030ustar00rootroot00000000000000/* MIT License CRT-Yee64 Copyright (c) 2017 Lucas Melo 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. */ #version 450 layout(set = 0, binding = 0) uniform sampler2D source; layout(push_constant) uniform Push { vec4 sourceSize; vec4 targetSize; int masktype; float maskstr; float scanstr; float sharpness; float curve; float corner; float tcurve; } pc; layout(location = 0) in vec2 texCoord; layout(location = 0) out vec4 fragColor; void main() { //Declare parameters //pixelSize vec4 c0 = pc.sourceSize.xyyy; //textureSize vec4 c1 = pc.sourceSize; //viewSize vec4 c2 = pc.sourceSize; const vec4 c3 = vec4(-1., 0.5, 1.25, 0.); const vec4 c4 = vec4(0.5, -0.5, 1.5, -3.); const vec4 c5 = vec4(-1., 1., -2., 2.); const vec4 c6 = vec4(-3., -8., 720., 0.166666672); const vec4 c7 = vec4(-0.333000004, -0.666000009, 0.899999976, 1.20000005); const vec4 c8 = vec4(1.5, 0.5, 2.5, 0.899999976); //Declare registers vec4 r0, r1, r2, r3, r4, r5, r6, r7, r8, r9; //Code starts here vec4 v0 = texCoord.xyyy; //dcl_2d s0 r0.z = c3.w; r1.x = 1.0 / c0.x; r1.y = 1.0 / c0.y; r1.xy = (r1 * c1).xy; r1.xy = (r1 * v0).xy; r2.x = 1.0 / c1.x; r2.y = 1.0 / c1.y; r1.zw = (r2.xyxy * c0.xyxy).zw; r1.zw = (r1 * r1.xyxy).zw; r1.xy = (r1 * c2).xy; r2.zw = (r1 * c1.xyxy).zw; r2.zw = fract(r2).zw; r0.xy = (-r2.zwzw).xy; r3.xy = (r1.zwzw * c1 + r0.xzzw).xy; r4.yz = (r1.xzww * c1.xxyw + r0.xzyw).yz; r3.z = r0.y + r3.y; r5 = r3.xzxz + -c4.zyxy; r3 = r3.xzxz + c8.xyzy; r3 = r2.xyxy * r3; r5 = r2.xyxy * r5; r6 = texture(source, r5.zw); r5 = texture(source, r5.xy); r5.xyz = (r5 * c3.zzzz).xyz; r7 = r1.zwzw * c1.xyxy + r0.xyxy; r0.zw = (r1 * c1.xyxy + - r7).zw; r8.x = c3.x; r1.zw = (r1 * c1.xyxy + r8.xxxx).zw; r1.zw = (r0.xyxy + r1).zw; r4.x = r0.x + r4.y; r4 = r4.xzxz + c4.xyxz; r4 = r2.xyxy * r4; r0.xy = (r1.zwzw + c3.yyyy).xy; r0.xy = (r2 * r0).xy; r8 = texture(source, r0.xy); r8.xyz = (r8 * c3.zzzz).xyz; r0.xy = (r0.zwzw + -c3.yyyy).xy; r9 = -r0.xxxx + c5; r9 = r9 * r9; r9 = r9 * c4.wwww; r0.z = pow(2.0, r9.x); r6.xyz = (r6 * r0.zzzz).xyz; r6.xyz = (r6 * c3.zzzz).xyz; r0.w = pow(2.0, r9.z); r5.xyz = (r5 * r0.wwww + r6).xyz; r0.w = r0.z + r0.w; r6 = r7.zwzw + c4.zyxx; r7 = r7 + c4.yzzz; r7 = r2.xyxy * r7; r2 = r2.xyxy * r6; r6 = texture(source, r2.zw); r2 = texture(source, r2.xy); r2.xyz = (r2 * c3.zzzz).xyz; r1.zw = (r0.xyxy * r0.xyxy).zw; r0.xy = (-r0.yyyy + c5).xy; r0.xy = (r0 * r0).xy; r0.xy = (r0 * c6.yyyy).xy; r1.zw = (r1 * c6.xyxy).zw; r1.z = pow(2.0, r1.z); r1.w = pow(2.0, r1.w); r6.xyz = (r6 * r1.zzzz).xyz; r5.xyz = (r6 * c3.zzzz + r5).xyz; r6 = texture(source, r3.xy); r3 = texture(source, r3.zw); r3.xyz = (r3 * c3.zzzz).xyz; r2.w = pow(2.0, r9.y); r3.w = pow(2.0, r9.w); r6.xyz = (r6 * r2.wwww).xyz; r5.xyz = (r6 * c3.zzzz + r5).xyz; r3.xyz = (r3 * r3.wwww + r5).xyz; r0.w = r0.w + r1.z; r0.w = r2.w + r0.w; r0.w = r3.w + r0.w; r0.w = 1.0 / r0.w; r3.xyz = (r0.wwww * r3).xyz; r3.xyz = (r1.wwww * r3).xyz; r5 = texture(source, r4.xy); r4 = texture(source, r4.zw); r4.xyz = (r1.zzzz * r4).xyz; r4.xyz = (r4 * c3.zzzz).xyz; r5.xyz = (r5 * c3.zzzz).xyz; r5.xyz = (r1.zzzz * r5).xyz; r0.w = r0.z + r1.z; r0.w = r2.w + r0.w; r0.w = 1.0 / r0.w; r5.xyz = (r8 * r0.zzzz + r5).xyz; r2.xyz = (r2 * r2.wwww + r5).xyz; r2.xyz = (r0.wwww * r2).xyz; r0.x = pow(2.0, r0.x); r0.y = pow(2.0, r0.y); r2.xyz = (r2 * r0.xxxx + r3).xyz; r3 = texture(source, r7.xy); r5 = texture(source, r7.zw); r5.xyz = (r2.wwww * r5).xyz; r3.xyz = (r0.zzzz * r3).xyz; r3.xyz = (r3 * c3.zzzz + r4).xyz; r3.xyz = (r5 * c3.zzzz + r3).xyz; r0.xzw = (r0.wwww * r3.xyyz).xzw; r0.xyz = (r0.xzww * r0.yyyy + r2).xyz; r1.zw = fract(r1.xyxy).zw; r1.xy = (-r1.zwzw + r1).xy; r1.xy = (r1 + c3.yyyy).xy; r0.w = (r1.y * -c4.w + r1.x); r0.w = r0.w * c6.w; r0.w = fract(r0.w); r1.xy = (r0.wwww + c7).xy; r2.yz = (r1.y >= 0.0 ? c7.xzww : c7.xwzw).yz; r2.x = c8.w; r1.xyz = (r1.x >= 0.0 ? r2 : c7.wzzw).xyz; r1.xyz = (r0 * r1).xyz; r2.z = c6.z; r0.w = r2.z + -c2.y; fragColor.xyz = (r0.w >= 0.0 ? r0 : r1).xyz; fragColor.w = -c3.x; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/crt-yee64.fs000066400000000000000000000122251523150047600242730ustar00rootroot00000000000000/* MIT License CRT-Yee64 Copyright (c) 2017 Lucas Melo 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. */ precision highp float; uniform sampler2D source; uniform vec4 sourceSize; in vec2 texCoord; out vec4 fragColor; void main() { //Declare parameters //pixelSize vec4 c0 = sourceSize.xyyy; //textureSize vec4 c1 = sourceSize; //viewSize vec4 c2 = sourceSize; const vec4 c3 = vec4(-1., 0.5, 1.25, 0.); const vec4 c4 = vec4(0.5, -0.5, 1.5, -3.); const vec4 c5 = vec4(-1., 1., -2., 2.); const vec4 c6 = vec4(-3., -8., 720., 0.166666672); const vec4 c7 = vec4(-0.333000004, -0.666000009, 0.899999976, 1.20000005); const vec4 c8 = vec4(1.5, 0.5, 2.5, 0.899999976); //Declare registers vec4 r0, r1, r2, r3, r4, r5, r6, r7, r8, r9; //Code starts here vec4 v0 = texCoord.xyyy; //dcl_2d s0 r0.z = c3.w; r1.x = 1.0 / c0.x; r1.y = 1.0 / c0.y; r1.xy = (r1 * c1).xy; r1.xy = (r1 * v0).xy; r2.x = 1.0 / c1.x; r2.y = 1.0 / c1.y; r1.zw = (r2.xyxy * c0.xyxy).zw; r1.zw = (r1 * r1.xyxy).zw; r1.xy = (r1 * c2).xy; r2.zw = (r1 * c1.xyxy).zw; r2.zw = fract(r2).zw; r0.xy = (-r2.zwzw).xy; r3.xy = (r1.zwzw * c1 + r0.xzzw).xy; r4.yz = (r1.xzww * c1.xxyw + r0.xzyw).yz; r3.z = r0.y + r3.y; r5 = r3.xzxz + -c4.zyxy; r3 = r3.xzxz + c8.xyzy; r3 = r2.xyxy * r3; r5 = r2.xyxy * r5; r6 = texture(source, r5.zw); r5 = texture(source, r5.xy); r5.xyz = (r5 * c3.zzzz).xyz; r7 = r1.zwzw * c1.xyxy + r0.xyxy; r0.zw = (r1 * c1.xyxy + - r7).zw; r8.x = c3.x; r1.zw = (r1 * c1.xyxy + r8.xxxx).zw; r1.zw = (r0.xyxy + r1).zw; r4.x = r0.x + r4.y; r4 = r4.xzxz + c4.xyxz; r4 = r2.xyxy * r4; r0.xy = (r1.zwzw + c3.yyyy).xy; r0.xy = (r2 * r0).xy; r8 = texture(source, r0.xy); r8.xyz = (r8 * c3.zzzz).xyz; r0.xy = (r0.zwzw + -c3.yyyy).xy; r9 = -r0.xxxx + c5; r9 = r9 * r9; r9 = r9 * c4.wwww; r0.z = pow(2.0, r9.x); r6.xyz = (r6 * r0.zzzz).xyz; r6.xyz = (r6 * c3.zzzz).xyz; r0.w = pow(2.0, r9.z); r5.xyz = (r5 * r0.wwww + r6).xyz; r0.w = r0.z + r0.w; r6 = r7.zwzw + c4.zyxx; r7 = r7 + c4.yzzz; r7 = r2.xyxy * r7; r2 = r2.xyxy * r6; r6 = texture(source, r2.zw); r2 = texture(source, r2.xy); r2.xyz = (r2 * c3.zzzz).xyz; r1.zw = (r0.xyxy * r0.xyxy).zw; r0.xy = (-r0.yyyy + c5).xy; r0.xy = (r0 * r0).xy; r0.xy = (r0 * c6.yyyy).xy; r1.zw = (r1 * c6.xyxy).zw; r1.z = pow(2.0, r1.z); r1.w = pow(2.0, r1.w); r6.xyz = (r6 * r1.zzzz).xyz; r5.xyz = (r6 * c3.zzzz + r5).xyz; r6 = texture(source, r3.xy); r3 = texture(source, r3.zw); r3.xyz = (r3 * c3.zzzz).xyz; r2.w = pow(2.0, r9.y); r3.w = pow(2.0, r9.w); r6.xyz = (r6 * r2.wwww).xyz; r5.xyz = (r6 * c3.zzzz + r5).xyz; r3.xyz = (r3 * r3.wwww + r5).xyz; r0.w = r0.w + r1.z; r0.w = r2.w + r0.w; r0.w = r3.w + r0.w; r0.w = 1.0 / r0.w; r3.xyz = (r0.wwww * r3).xyz; r3.xyz = (r1.wwww * r3).xyz; r5 = texture(source, r4.xy); r4 = texture(source, r4.zw); r4.xyz = (r1.zzzz * r4).xyz; r4.xyz = (r4 * c3.zzzz).xyz; r5.xyz = (r5 * c3.zzzz).xyz; r5.xyz = (r1.zzzz * r5).xyz; r0.w = r0.z + r1.z; r0.w = r2.w + r0.w; r0.w = 1.0 / r0.w; r5.xyz = (r8 * r0.zzzz + r5).xyz; r2.xyz = (r2 * r2.wwww + r5).xyz; r2.xyz = (r0.wwww * r2).xyz; r0.x = pow(2.0, r0.x); r0.y = pow(2.0, r0.y); r2.xyz = (r2 * r0.xxxx + r3).xyz; r3 = texture(source, r7.xy); r5 = texture(source, r7.zw); r5.xyz = (r2.wwww * r5).xyz; r3.xyz = (r0.zzzz * r3).xyz; r3.xyz = (r3 * c3.zzzz + r4).xyz; r3.xyz = (r5 * c3.zzzz + r3).xyz; r0.xzw = (r0.wwww * r3.xyyz).xzw; r0.xyz = (r0.xzww * r0.yyyy + r2).xyz; r1.zw = fract(r1.xyxy).zw; r1.xy = (-r1.zwzw + r1).xy; r1.xy = (r1 + c3.yyyy).xy; r0.w = (r1.y * -c4.w + r1.x); r0.w = r0.w * c6.w; r0.w = fract(r0.w); r1.xy = (r0.wwww + c7).xy; r2.yz = (r1.y >= 0.0 ? c7.xzww : c7.xwzw).yz; r2.x = c8.w; r1.xyz = (r1.x >= 0.0 ? r2 : c7.wzzw).xyz; r1.xyz = (r0 * r1).xyz; r2.z = c6.z; r0.w = r2.z + -c2.y; fragColor.xyz = (r0.w >= 0.0 ? r0 : r1).xyz; fragColor.w = -c3.x; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/crtea.frag000066400000000000000000000237421523150047600241640ustar00rootroot00000000000000/* MIT License CRTea - Configurable CRT Fragment Shader Copyright (c) 2020-2022 Rupert Carmichael 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. */ // Based on Public Domain work by Timothy Lottes #version 450 layout(set = 0, binding = 0) uniform sampler2D source; layout(push_constant) uniform Push { vec4 sourceSize; vec4 targetSize; int masktype; float maskstr; float scanstr; float sharpness; float curve; float corner; float tcurve; } pc; layout(location = 0) in vec2 texCoord; layout(location = 0) out vec4 FragColor; //#define CRTS_DEBUG 1 // Define to see on/off split screen //#define CRTS_2_TAP 1 // Faster very pixely 2-tap filter (off is 8) #define CRTS_WARP 1 // Apply screen warp #define INPUT_MASK (1.0 - pc.maskstr) #define INPUT_SCAN (0.5 + (0.5 * pc.scanstr)) #define INPUT_SHRP (-1.0 * pc.sharpness) #define INPUT_X pc.sourceSize.x #define INPUT_Y pc.sourceSize.y #define CURVATURE pc.curve #define TRINITRON_CURVE pc.tcurve #define CORNER pc.corner #define CRT_GAMMA 2.4 #define CRTS_TONE 1 // Normalize mid-level and process color #define CRTS_CONTRAST 0 // Process color - enable contrast control #define CRTS_SATURATION 0 // Process color - enable saturation control #define CrtsRcpF1(x) (1.0/(x)) #define CrtsSatF1(x) clamp((x),0.0,1.0) // sRGB-Linear Conversions float FromSrgb1(float c) { return (c <= 0.04045) ? c * (1.0 / 12.92) : pow(c * (1.0 / 1.055) + (0.055 / 1.055), CRT_GAMMA); } vec3 FromSrgb(vec3 c) { return vec3(FromSrgb1(c.r), FromSrgb1(c.g), FromSrgb1(c.b)); } float ToSrgb1(float c) { return (c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 0.41666) - 0.055); } vec3 ToSrgb(vec3 c) { return vec3(ToSrgb1(c.r), ToSrgb1(c.g), ToSrgb1(c.b)); } // Fetch the input image colour vec3 CrtsFetch(vec2 uv) { // Scale to get native texels in the image uv *= vec2(INPUT_X, INPUT_Y) / pc.sourceSize.xy; return FromSrgb(texture(source, uv.xy, -16.0).rgb); } // Get the maximum of 3 floats float CrtsMax3F1(float a, float b, float c) { return max(a, max(b, c)); } // Tonal Control vec4 CrtsTone(float contrast, float saturation, float thin, float mask) { if (pc.masktype == 0) mask = 1.0; if (pc.masktype == 1) { // Normal R mask is {1.0,mask,mask} // LITE R mask is {mask,1.0,1.0} mask = 0.5 + mask * 0.5; } vec4 ret; float midOut = 0.18 / ((1.5 - thin) * (0.5 * mask + 0.5)); float pMidIn = pow(0.18, contrast); ret.x = contrast; ret.y = ((-pMidIn) + midOut) / ((1.0 - pMidIn) * midOut); ret.z = ((-pMidIn) * midOut + pMidIn) / (midOut * (-pMidIn) + midOut); ret.w = contrast + saturation; return ret; } // Apply the mask vec3 CrtsMask(vec2 pos, float dark) { if (pc.masktype == 0) { // Scanlines (No Mask) return vec3(1.0, 1.0, 1.0); } if (pc.masktype == 1) { // Aperture Grille Lite vec3 m = vec3(1.0, 1.0, 1.0); float x = fract(pos.x * (1.0 / 3.0)); if (x < (1.0 / 3.0)) m.r = dark; else if (x < (2.0 / 3.0)) m.g = dark; else m.b = dark; return m; } if (pc.masktype == 2) { // Aperture Grille vec3 m = vec3(dark, dark, dark); float x = fract(pos.x * (1.0 / 3.0)); if (x < (1.0 / 3.0)) m.r = 1.0; else if (x < (2.0 / 3.0)) m.g = 1.0; else m.b = 1.0; return m; } if (pc.masktype == 3) { // Shadow Mask pos.x += pos.y * 2.9999; vec3 m = vec3(dark, dark, dark); float x = fract(pos.x * (1.0 / 6.0)); if (x < (1.0 / 3.0)) m.r = 1.0; else if (x < (2.0 / 3.0)) m.g = 1.0; else m.b = 1.0; return m; } } // The filter function itself // SV_POSITION, fragCoord.xy // sourceSize / targetSize (in pixels) // 0.5 * sourceSize (in pixels) // 1.0 / sourceSize (in pixels) // 1.0 / targetSize (in pixels) // 2.0 / targetSize (in pixels) // Warp scanlines but not phosphor mask // Tonal curve parameters generated by CrtsTone() vec3 CrtsFilter(vec2 ipos, vec2 inputSizeDivOutputSize, vec2 halfInputSize, vec2 rcpInputSize, vec2 rcpOutputSize, vec2 twoDivOutputSize, vec2 warp, vec4 tone) { #ifdef CRTS_DEBUG vec2 uv = ipos * rcpOutputSize; // Show second half processed, and first half un-processed if (uv.x < 0.5) { // Force nearest to get squares uv *= 1.0 / rcpInputSize; uv = floor(uv) + vec2(0.5, 0.5); uv *= rcpInputSize; vec3 color = CrtsFetch(uv); return color; } #endif // Optional apply warp vec2 pos; #ifdef CRTS_WARP // Convert to {-1 to 1} range pos = ipos * twoDivOutputSize - vec2(1.0, 1.0); // Distort pushes image outside {-1 to 1} range pos *= vec2(1.0 + (pos.y * pos.y) * warp.x, 1.0 + (pos.x * pos.x) * warp.y); // TODO: Vignette needs optimization float vin = (1.0 - ((1.0 - CrtsSatF1(pos.x * pos.x)) * (1.0 - CrtsSatF1(pos.y * pos.y)))) * (0.998 + (0.001 * CORNER)); vin = CrtsSatF1((-vin) * pc.sourceSize.y + pc.sourceSize.y); // Leave in {0 to inputSize} pos = pos * halfInputSize + halfInputSize; #else pos = ipos * inputSizeDivOutputSize; #endif // Snap to center of first scanline float y0 = floor(pos.y - 0.5) + 0.5; #ifdef CRTS_2_TAP // Using Inigo's "Improved Texture Interpolation" // http://iquilezles.org/www/articles/texture/texture.htm pos.x += 0.5; float xi = floor(pos.x); float xf = pos.x - xi; xf = xf * xf * xf * (xf * (xf * 6.0 - 15.0) + 10.0); float x0 = xi + xf - 0.5; vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y); // Coordinate adjusted bilinear fetch from 2 nearest scanlines vec3 colA = CrtsFetch(p); p.y += rcpInputSize.y; vec3 colB = CrtsFetch(p); #else // Snap to center of one of four pixels float x0 = floor(pos.x - 1.5) + 0.5; // Inital UV position vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y); // Fetch 4 nearest texels from 2 nearest scanlines vec3 colA0 = CrtsFetch(p); p.x += rcpInputSize.x; vec3 colA1 = CrtsFetch(p); p.x += rcpInputSize.x; vec3 colA2 = CrtsFetch(p); p.x += rcpInputSize.x; vec3 colA3 = CrtsFetch(p); p.y += rcpInputSize.y; vec3 colB3 = CrtsFetch(p); p.x -= rcpInputSize.x; vec3 colB2 = CrtsFetch(p); p.x -= rcpInputSize.x; vec3 colB1 = CrtsFetch(p); p.x -= rcpInputSize.x; vec3 colB0 = CrtsFetch(p); #endif // Vertical filter // Scanline intensity is using sine wave // Easy filter window and integral used later in exposure float off = pos.y - y0; float pi2 = 6.28318530717958; float hlf = 0.5; float scanA = cos(min(0.5, off * INPUT_SCAN) * pi2) * hlf + hlf; float scanB = cos(min(0.5, (-off) * INPUT_SCAN + INPUT_SCAN) * pi2) * hlf + hlf; #ifdef CRTS_2_TAP #ifdef CRTS_WARP // Get rid of wrong pixels on edge scanA *= vin; scanB *= vin; #endif // Apply vertical filter vec3 color = (colA * scanA) + (colB * scanB); #else // Horizontal kernel is simple gaussian filter float off0 = pos.x - x0; float off1 = off0 - 1.0; float off2 = off0 - 2.0; float off3 = off0 - 3.0; float pix0 = exp2(INPUT_SHRP * off0 * off0); float pix1 = exp2(INPUT_SHRP * off1 * off1); float pix2 = exp2(INPUT_SHRP * off2 * off2); float pix3 = exp2(INPUT_SHRP * off3 * off3); float pixT = CrtsRcpF1(pix0 + pix1 + pix2 + pix3); #ifdef CRTS_WARP // Get rid of wrong pixels on edge pixT *= vin; #endif scanA *= pixT; scanB *= pixT; // Apply horizontal and vertical filters vec3 color = (colA0 * pix0 + colA1 * pix1 + colA2 * pix2 + colA3 * pix3) * scanA + (colB0 * pix0 + colB1 * pix1 + colB2 * pix2 + colB3 * pix3) * scanB; #endif // Apply phosphor mask color *= CrtsMask(gl_FragCoord.xy, INPUT_MASK); // Optional color processing #ifdef CRTS_TONE // Tonal control, start by protecting from /0 float peak = max(1.0 / (256.0 * 65536.0), CrtsMax3F1(color.r, color.g, color.b)); // Compute the ratios of {R,G,B} vec3 ratio = color * CrtsRcpF1(peak); // Apply tonal curve to peak value #ifdef CRTS_CONTRAST peak = pow(peak, tone.x); #endif peak = peak * CrtsRcpF1(peak * tone.y + tone.z); // Apply saturation #ifdef CRTS_SATURATION ratio = pow(ratio, vec3(tone.w, tone.w, tone.w)); #endif // Reconstruct color return ratio * peak; #else return color; #endif } void main() { vec2 warp_factor; warp_factor.x = CURVATURE; warp_factor.y = (3.0 / 4.0) * warp_factor.x; // assume 4:3 aspect warp_factor.x *= (1.0 - TRINITRON_CURVE); FragColor.rgb = CrtsFilter(texCoord.xy * pc.targetSize.xy, pc.sourceSize.xy * pc.targetSize.zw, pc.sourceSize.xy * vec2(0.5, 0.5), pc.sourceSize.zw, pc.targetSize.zw, 2.0 * pc.targetSize.zw, warp_factor, CrtsTone(1.0, 0.0, INPUT_SCAN, INPUT_MASK)); // Output non-linear color FragColor.rgb = ToSrgb(FragColor.rgb); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/crtea.fs000066400000000000000000000237271523150047600236600ustar00rootroot00000000000000/* MIT License CRTea - Configurable CRT Fragment Shader Copyright (c) 2020-2022 Rupert Carmichael 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. */ // Based on Public Domain work by Timothy Lottes precision highp float; uniform sampler2D source; uniform vec4 sourceSize; uniform vec4 targetSize; uniform int masktype; // Mask Type uniform float maskstr; // Mask Strength uniform float scanstr; // Scanline Strength uniform float sharpness; // Pixel Sharpness uniform float curve; // Screen Curvature uniform float corner; // Screen Curvature uniform float tcurve; // Trinitron Curve in vec2 texCoord; out vec4 FragColor; //#define CRTS_DEBUG 1 // Define to see on/off split screen //#define CRTS_2_TAP 1 // Faster very pixely 2-tap filter (off is 8) #define CRTS_WARP 1 // Apply screen warp #define INPUT_MASK (1.0 - maskstr) #define INPUT_SCAN (0.5 + (0.5 * scanstr)) #define INPUT_SHRP (-1.0 * sharpness) #define INPUT_X sourceSize.x #define INPUT_Y sourceSize.y #define CURVATURE curve #define TRINITRON_CURVE tcurve #define CORNER corner #define CRT_GAMMA 2.4 #define CRTS_TONE 1 // Normalize mid-level and process color #define CRTS_CONTRAST 0 // Process color - enable contrast control #define CRTS_SATURATION 0 // Process color - enable saturation control #define CrtsRcpF1(x) (1.0/(x)) #define CrtsSatF1(x) clamp((x),0.0,1.0) // sRGB-Linear Conversions float FromSrgb1(float c) { return (c <= 0.04045) ? c * (1.0 / 12.92) : pow(c * (1.0 / 1.055) + (0.055 / 1.055), CRT_GAMMA); } vec3 FromSrgb(vec3 c) { return vec3(FromSrgb1(c.r), FromSrgb1(c.g), FromSrgb1(c.b)); } float ToSrgb1(float c) { return (c < 0.0031308 ? c * 12.92 : 1.055 * pow(c, 0.41666) - 0.055); } vec3 ToSrgb(vec3 c) { return vec3(ToSrgb1(c.r), ToSrgb1(c.g), ToSrgb1(c.b)); } // Fetch the input image colour vec3 CrtsFetch(vec2 uv) { // Scale to get native texels in the image uv *= vec2(INPUT_X, INPUT_Y) / sourceSize.xy; return FromSrgb(texture(source, uv.xy, -16.0).rgb); } // Get the maximum of 3 floats float CrtsMax3F1(float a, float b, float c) { return max(a, max(b, c)); } // Tonal Control vec4 CrtsTone(float contrast, float saturation, float thin, float mask) { if (masktype == 0) mask = 1.0; if (masktype == 1) { // Normal R mask is {1.0,mask,mask} // LITE R mask is {mask,1.0,1.0} mask = 0.5 + mask * 0.5; } vec4 ret; float midOut = 0.18 / ((1.5 - thin) * (0.5 * mask + 0.5)); float pMidIn = pow(0.18, contrast); ret.x = contrast; ret.y = ((-pMidIn) + midOut) / ((1.0 - pMidIn) * midOut); ret.z = ((-pMidIn) * midOut + pMidIn) / (midOut * (-pMidIn) + midOut); ret.w = contrast + saturation; return ret; } // Apply the mask vec3 CrtsMask(vec2 pos, float dark) { if (masktype == 0) { // Scanlines (No Mask) return vec3(1.0, 1.0, 1.0); } if (masktype == 1) { // Aperture Grille Lite vec3 m = vec3(1.0, 1.0, 1.0); float x = fract(pos.x * (1.0 / 3.0)); if (x < (1.0 / 3.0)) m.r = dark; else if (x < (2.0 / 3.0)) m.g = dark; else m.b = dark; return m; } if (masktype == 2) { // Aperture Grille vec3 m = vec3(dark, dark, dark); float x = fract(pos.x * (1.0 / 3.0)); if (x < (1.0 / 3.0)) m.r = 1.0; else if (x < (2.0 / 3.0)) m.g = 1.0; else m.b = 1.0; return m; } if (masktype == 3) { // Shadow Mask pos.x += pos.y * 2.9999; vec3 m = vec3(dark, dark, dark); float x = fract(pos.x * (1.0 / 6.0)); if (x < (1.0 / 3.0)) m.r = 1.0; else if (x < (2.0 / 3.0)) m.g = 1.0; else m.b = 1.0; return m; } } // The filter function itself // SV_POSITION, fragCoord.xy // sourceSize / targetSize (in pixels) // 0.5 * sourceSize (in pixels) // 1.0 / sourceSize (in pixels) // 1.0 / targetSize (in pixels) // 2.0 / targetSize (in pixels) // Warp scanlines but not phosphor mask // Tonal curve parameters generated by CrtsTone() vec3 CrtsFilter(vec2 ipos, vec2 inputSizeDivOutputSize, vec2 halfInputSize, vec2 rcpInputSize, vec2 rcpOutputSize, vec2 twoDivOutputSize, vec2 warp, vec4 tone) { #ifdef CRTS_DEBUG vec2 uv = ipos * rcpOutputSize; // Show second half processed, and first half un-processed if (uv.x < 0.5) { // Force nearest to get squares uv *= 1.0 / rcpInputSize; uv = floor(uv) + vec2(0.5, 0.5); uv *= rcpInputSize; vec3 color = CrtsFetch(uv); return color; } #endif // Optional apply warp vec2 pos; #ifdef CRTS_WARP // Convert to {-1 to 1} range pos = ipos * twoDivOutputSize - vec2(1.0, 1.0); // Distort pushes image outside {-1 to 1} range pos *= vec2(1.0 + (pos.y * pos.y) * warp.x, 1.0 + (pos.x * pos.x) * warp.y); // TODO: Vignette needs optimization float vin = (1.0 - ((1.0 - CrtsSatF1(pos.x * pos.x)) * (1.0 - CrtsSatF1(pos.y * pos.y)))) * (0.998 + (0.001 * CORNER)); vin = CrtsSatF1((-vin) * sourceSize.y + sourceSize.y); // Leave in {0 to inputSize} pos = pos * halfInputSize + halfInputSize; #else pos = ipos * inputSizeDivOutputSize; #endif // Snap to center of first scanline float y0 = floor(pos.y - 0.5) + 0.5; #ifdef CRTS_2_TAP // Using Inigo's "Improved Texture Interpolation" // http://iquilezles.org/www/articles/texture/texture.htm pos.x += 0.5; float xi = floor(pos.x); float xf = pos.x - xi; xf = xf * xf * xf * (xf * (xf * 6.0 - 15.0) + 10.0); float x0 = xi + xf - 0.5; vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y); // Coordinate adjusted bilinear fetch from 2 nearest scanlines vec3 colA = CrtsFetch(p); p.y += rcpInputSize.y; vec3 colB = CrtsFetch(p); #else // Snap to center of one of four pixels float x0 = floor(pos.x - 1.5) + 0.5; // Inital UV position vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y); // Fetch 4 nearest texels from 2 nearest scanlines vec3 colA0 = CrtsFetch(p); p.x += rcpInputSize.x; vec3 colA1 = CrtsFetch(p); p.x += rcpInputSize.x; vec3 colA2 = CrtsFetch(p); p.x += rcpInputSize.x; vec3 colA3 = CrtsFetch(p); p.y += rcpInputSize.y; vec3 colB3 = CrtsFetch(p); p.x -= rcpInputSize.x; vec3 colB2 = CrtsFetch(p); p.x -= rcpInputSize.x; vec3 colB1 = CrtsFetch(p); p.x -= rcpInputSize.x; vec3 colB0 = CrtsFetch(p); #endif // Vertical filter // Scanline intensity is using sine wave // Easy filter window and integral used later in exposure float off = pos.y - y0; float pi2 = 6.28318530717958; float hlf = 0.5; float scanA = cos(min(0.5, off * INPUT_SCAN) * pi2) * hlf + hlf; float scanB = cos(min(0.5, (-off) * INPUT_SCAN + INPUT_SCAN) * pi2) * hlf + hlf; #ifdef CRTS_2_TAP #ifdef CRTS_WARP // Get rid of wrong pixels on edge scanA *= vin; scanB *= vin; #endif // Apply vertical filter vec3 color = (colA * scanA) + (colB * scanB); #else // Horizontal kernel is simple gaussian filter float off0 = pos.x - x0; float off1 = off0 - 1.0; float off2 = off0 - 2.0; float off3 = off0 - 3.0; float pix0 = exp2(INPUT_SHRP * off0 * off0); float pix1 = exp2(INPUT_SHRP * off1 * off1); float pix2 = exp2(INPUT_SHRP * off2 * off2); float pix3 = exp2(INPUT_SHRP * off3 * off3); float pixT = CrtsRcpF1(pix0 + pix1 + pix2 + pix3); #ifdef CRTS_WARP // Get rid of wrong pixels on edge pixT *= vin; #endif scanA *= pixT; scanB *= pixT; // Apply horizontal and vertical filters vec3 color = (colA0 * pix0 + colA1 * pix1 + colA2 * pix2 + colA3 * pix3) * scanA + (colB0 * pix0 + colB1 * pix1 + colB2 * pix2 + colB3 * pix3) * scanB; #endif // Apply phosphor mask color *= CrtsMask(gl_FragCoord.xy, INPUT_MASK); // Optional color processing #ifdef CRTS_TONE // Tonal control, start by protecting from /0 float peak = max(1.0 / (256.0 * 65536.0), CrtsMax3F1(color.r, color.g, color.b)); // Compute the ratios of {R,G,B} vec3 ratio = color * CrtsRcpF1(peak); // Apply tonal curve to peak value #ifdef CRTS_CONTRAST peak = pow(peak, tone.x); #endif peak = peak * CrtsRcpF1(peak * tone.y + tone.z); // Apply saturation #ifdef CRTS_SATURATION ratio = pow(ratio, vec3(tone.w, tone.w, tone.w)); #endif // Reconstruct color return ratio * peak; #else return color; #endif } void main() { vec2 warp_factor; warp_factor.x = CURVATURE; warp_factor.y = (3.0 / 4.0) * warp_factor.x; // assume 4:3 aspect warp_factor.x *= (1.0 - TRINITRON_CURVE); FragColor.rgb = CrtsFilter(texCoord.xy * targetSize.xy, sourceSize.xy * targetSize.zw, sourceSize.xy * vec2(0.5, 0.5), sourceSize.zw, targetSize.zw, 2.0 * targetSize.zw, warp_factor, CrtsTone(1.0, 0.0, INPUT_SCAN, INPUT_MASK)); // Output non-linear color FragColor.rgb = ToSrgb(FragColor.rgb); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/default.frag000066400000000000000000000003411523150047600245000ustar00rootroot00000000000000#version 450 layout(set = 0, binding = 0) uniform sampler2D source; layout(location = 0) in vec2 texCoord; layout(location = 0) out vec4 fragColor; void main() { fragColor = vec4(texture(source, texCoord).rgb, 1.0); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/default.fs000066400000000000000000000023431523150047600241750ustar00rootroot00000000000000/* MIT License Copyright (c) 2020-2022 Rupert Carmichael 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. */ precision highp float; in vec2 texCoord; out vec4 fragColor; uniform sampler2D source; void main() { fragColor = vec4(texture(source, texCoord).rgb, 1.0); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/default.vert000066400000000000000000000003331523150047600245420ustar00rootroot00000000000000#version 450 layout(location = 0) in vec2 position; layout(location = 1) in vec2 texcoord; layout(location = 0) out vec2 texCoord; void main() { texCoord = texcoord; gl_Position = vec4(position, 0.0, 1.0); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/default.vs000066400000000000000000000023141523150047600242130ustar00rootroot00000000000000/* MIT License Copyright (c) 2020-2022 Rupert Carmichael 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. */ in vec2 position; in vec2 vtxCoord; out vec2 texCoord; void main() { texCoord = vtxCoord; gl_Position = vec4(position, 0.0, 1.0); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/lcd.frag000066400000000000000000000041741523150047600236260ustar00rootroot00000000000000/* MIT License Copyright (c) 2020-2022 Rupert Carmichael 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. */ #version 450 layout(set = 0, binding = 0) uniform sampler2D source; layout(push_constant) uniform Push { vec4 sourceSize; vec4 targetSize; int masktype; float maskstr; float scanstr; float sharpness; float curve; float corner; float tcurve; } pc; layout(location = 0) in vec2 texCoord; layout(location = 0) out vec4 fragColor; #define maskstr 0.5 void main() { // Find out where in the LCD screen we are vec2 lcdCoord = texCoord * pc.sourceSize.xy * 3.0; // Find out which column of the pixel we're in float col = floor(mod(lcdCoord.x, 3.0)); // Shade all pixels vec4 shade = vec4(maskstr, maskstr, maskstr, 1.0); // Shade stronger in red, green, or blue for each column if (col == 1.0) shade.g = 1.0; else if (col == 2.0) shade.b = 1.0; else shade.r = 1.0; // Draw a horizontal scanline at the bottom of each pixel if (floor(mod(lcdCoord.y, 3.0)) == 0.0) shade.rgba = vec4(maskstr, maskstr, maskstr, 1.0); fragColor = texture(source, texCoord) * shade; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/lcd.fs000066400000000000000000000035751523150047600233230ustar00rootroot00000000000000/* MIT License Copyright (c) 2020-2022 Rupert Carmichael 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. */ precision highp float; uniform sampler2D source; uniform vec4 sourceSize; in vec2 texCoord; out vec4 fragColor; #define maskstr 0.5 void main() { // Find out where in the LCD screen we are vec2 lcdCoord = texCoord * sourceSize.xy * 3.0; // Find out which column of the pixel we're in float col = floor(mod(lcdCoord.x, 3.0)); // Shade all pixels vec4 shade = vec4(maskstr, maskstr, maskstr, 1.0); // Shade stronger in red, green, or blue for each column if (col == 1.0) shade.g = 1.0; else if (col == 2.0) shade.b = 1.0; else shade.r = 1.0; // Draw a horizontal scanline at the bottom of each pixel if (floor(mod(lcdCoord.y, 3.0)) == 0.0) shade.rgba = vec4(maskstr, maskstr, maskstr, 1.0); fragColor = texture(source, texCoord) * shade; } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/osd.frag000066400000000000000000000003221523150047600236400ustar00rootroot00000000000000#version 450 layout(set = 0, binding = 0) uniform sampler2D source; layout(location = 0) in vec2 texCoord; layout(location = 0) out vec4 fragColor; void main() { fragColor = texture(source, texCoord); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/sharp-bilinear.frag000066400000000000000000000047651523150047600257720ustar00rootroot00000000000000/* MIT License Sharp Bilinear (Modified) Copyright (c) 2020-2022 Rupert Carmichael 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. */ #version 450 // Auto-translated from the GL ES fragment shader of the same name. The sampler // moves to a descriptor (set 0, binding 0) and every former uniform (sourceSize, // targetSize, and the CRTea parameters) is delivered through a single // push-constant block whose layout matches vk_pushconst_t in video_vk.c. layout(set = 0, binding = 0) uniform sampler2D source; layout(push_constant) uniform Push { vec4 sourceSize; vec4 targetSize; int masktype; float maskstr; float scanstr; float sharpness; float curve; float corner; float tcurve; } pc; layout(location = 0) in vec2 texCoord; layout(location = 0) out vec4 fragColor; // Based on Public Domain work by Themaister void main() { float prescale = floor(pc.targetSize.y / pc.sourceSize.y + 0.01); if (prescale == 0.0) prescale = 1.0; vec2 texel = texCoord.xy * pc.sourceSize.xy; vec2 texel_floored = floor(texel); vec2 s = fract(texel); float region_range = 0.5 - 0.5 / prescale; /* Figure out where in the texel to sample to get correct pre-scaled bilinear. Uses the hardware bilinear interpolator to avoid having to sample 4 times manually. */ vec2 center_dist = s - 0.5; vec2 f = (center_dist - clamp(center_dist, -region_range, region_range)) * prescale + 0.5; vec2 mod_texel = texel_floored + f; fragColor = texture(source, mod_texel / pc.sourceSize.xy); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/shaders/sharp-bilinear.fs000066400000000000000000000037161523150047600254560ustar00rootroot00000000000000/* MIT License Sharp Bilinear (Modified) Copyright (c) 2020-2022 Rupert Carmichael 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. */ // Based on Public Domain work by Themaister precision highp float; uniform sampler2D source; uniform vec4 sourceSize; uniform vec4 targetSize; in vec2 texCoord; out vec4 fragColor; void main() { float prescale = floor(targetSize.y / sourceSize.y + 0.01); if (prescale == 0.0) prescale = 1.0; vec2 texel = texCoord.xy * sourceSize.xy; vec2 texel_floored = floor(texel); vec2 s = fract(texel); float region_range = 0.5 - 0.5 / prescale; /* Figure out where in the texel to sample to get correct pre-scaled bilinear. Uses the hardware bilinear interpolator to avoid having to sample 4 times manually. */ vec2 center_dist = s - 0.5; vec2 f = (center_dist - clamp(center_dist, -region_range, region_range)) * prescale + 0.5; vec2 mod_texel = texel_floored + f; fragColor = texture(source, mod_texel / sourceSize.xy); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/video.c000066400000000000000000000203501523150047600220360ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "lodepng.h" #include "jgrf.h" #include "video.h" #include "video_gl.h" #ifdef JGRF_VULKAN #include "video_vk.h" #endif #include "settings.h" void (*jgrf_video_create)(void); int (*jgrf_video_init)(void); void (*jgrf_video_deinit)(void); void (*jgrf_video_fullscreen)(void); void (*jgrf_video_render)(int); void (*jgrf_video_resize)(void); void (*jgrf_video_get_scale_params)(float*, float*, float*, float*); void (*jgrf_video_set_cursor)(int); jg_videoinfo_t* (*jgrf_video_get_info)(void); void (*jgrf_video_set_info)(jg_videoinfo_t*); void (*jgrf_video_swapbuffers)(void); void (*jgrf_video_text)(int, int, const char*); void (*jgrf_video_rehash)(void); void *(*jgrf_video_get_pixels)(int*, int*); static jgrf_gdata_t *gdata; // Set function pointers for video - Use to select a video API when more exist void jgrf_video_setfuncs(void) { gdata = jgrf_gdata_ptr(); jg_setting_t *settings = jgrf_settings_ptr(); switch (settings[VIDEO_API].val) { case 0: case 1: // OpenGL - Core Profile and OpenGL ES jgrf_video_create = &jgrf_video_gl_create; jgrf_video_init = &jgrf_video_gl_init; jgrf_video_deinit = &jgrf_video_gl_deinit; jgrf_video_fullscreen = &jgrf_video_gl_fullscreen; jgrf_video_render = &jgrf_video_gl_render; jgrf_video_resize = &jgrf_video_gl_resize; jgrf_video_get_scale_params = &jgrf_video_gl_get_scale_params; jgrf_video_set_cursor = &jgrf_video_gl_set_cursor; jgrf_video_get_info = &jgrf_video_gl_get_info; jgrf_video_set_info = &jgrf_video_gl_set_info; jgrf_video_swapbuffers = &jgrf_video_gl_swapbuffers; jgrf_video_text = &jgrf_video_gl_text; jgrf_video_rehash = &jgrf_video_gl_rehash; jgrf_video_get_pixels = &jgrf_video_gl_get_pixels; break; case 2: // OpenGL - Compatibility Profile jgrf_video_create = &jgrf_video_gl_create; jgrf_video_init = &jgrf_video_gl_init; jgrf_video_deinit = &jgrf_video_gl_deinit; jgrf_video_fullscreen = &jgrf_video_gl_fullscreen; jgrf_video_render = &jgrf_video_gl_render_compat; jgrf_video_resize = &jgrf_video_gl_resize; jgrf_video_get_scale_params = &jgrf_video_gl_get_scale_params; jgrf_video_set_cursor = &jgrf_video_gl_set_cursor; jgrf_video_get_info = &jgrf_video_gl_get_info; jgrf_video_set_info = &jgrf_video_gl_set_info; jgrf_video_swapbuffers = &jgrf_video_gl_swapbuffers; jgrf_video_text = &jgrf_video_gl_text; jgrf_video_rehash = &jgrf_video_gl_rehash; jgrf_video_get_pixels = &jgrf_video_gl_get_pixels; break; #ifdef JGRF_VULKAN case 3: // Vulkan jgrf_video_create = &jgrf_video_vk_create; jgrf_video_init = &jgrf_video_vk_init; jgrf_video_deinit = &jgrf_video_vk_deinit; jgrf_video_fullscreen = &jgrf_video_vk_fullscreen; jgrf_video_render = &jgrf_video_vk_render; jgrf_video_resize = &jgrf_video_vk_resize; jgrf_video_get_scale_params = &jgrf_video_vk_get_scale_params; jgrf_video_set_cursor = &jgrf_video_vk_set_cursor; jgrf_video_get_info = &jgrf_video_vk_get_info; jgrf_video_set_info = &jgrf_video_vk_set_info; jgrf_video_swapbuffers = &jgrf_video_vk_swapbuffers; jgrf_video_text = &jgrf_video_vk_text; jgrf_video_rehash = &jgrf_video_vk_rehash; jgrf_video_get_pixels = &jgrf_video_vk_get_pixels; break; #endif default: jgrf_log(JG_LOG_ERR, "Invalid Video API: %d\n", settings[VIDEO_API].val); break; } } // Load an application icon void jgrf_video_icon_load(SDL_Window *window) { char iconpath[192]; #ifdef __APPLE__ int iconsize = 1024; #else int iconsize = 96; #endif #ifdef JGRF_STATIC snprintf(iconpath, sizeof(iconpath), "%s%cicons%c%s%d.png", jgrf_gdata_ptr()->binpath, SEP, SEP, jg_get_coreinfo("")->name, iconsize); #if defined(DATADIR) struct stat fbuf; // Make sure the icon actually exists at this path if (stat(iconpath, &fbuf) != 0) { // Not found locally, use system-wide path snprintf(iconpath, sizeof(iconpath), "%s%cjollygood%c%s%c%s%d.png", DATADIR, SEP, SEP, jg_get_coreinfo("")->name, SEP, jg_get_coreinfo("")->name, iconsize); } #endif #else snprintf(iconpath, sizeof(iconpath), "%s%cicons%cjollygood%d.png", jgrf_gdata_ptr()->binpath, SEP, SEP, iconsize); #if defined(DATADIR) struct stat fbuf; // Make sure the icon actually exists at this path if (stat(iconpath, &fbuf) != 0) { // Not found locally, use system-wide path snprintf(iconpath, sizeof(iconpath), "%s%cjollygood%cjgrf%cjollygood%d.png", DATADIR, SEP, SEP, SEP, iconsize); } #endif #endif uint32_t x, y; uint8_t *png_icon = 0; uint8_t error = lodepng_decode32_file(&png_icon, &x, &y, iconpath); if (error) jgrf_log(JG_LOG_WRN, "lodepng code %u: %s\n", error, lodepng_error_text(error)); SDL_Surface *icon; icon = SDL_CreateSurfaceFrom(x, y, SDL_PIXELFORMAT_RGBA32, png_icon, x * sizeof(uint32_t)); SDL_SetWindowIcon(window, icon); SDL_DestroySurface(icon); free(png_icon); // Seed the RNG srand((unsigned)time(NULL)); } // Write the currently displayed video frame to a .png file void jgrf_video_screenshot(void) { char ssname[256]; snprintf(ssname, sizeof(ssname), "%s%d-%03x.png", gdata->sspath, (unsigned)time(NULL), rand() % 0xfff); // Rendered pixels after post-processing int rw, rh; void *ssdata = jgrf_video_get_pixels(&rw, &rh); // Set up the LodePNGState to ensure 100% opacity on the alpha channel LodePNGState state; lodepng_state_init(&state); state.info_raw.colortype = LCT_RGBA; state.info_raw.bitdepth = 8; state.info_png.color.colortype = LCT_RGB; state.info_png.color.bitdepth = 8; state.encoder.auto_convert = 0; // Dump the raw data into PNG format unsigned char *png = NULL; size_t pngsize = 0; unsigned error = lodepng_encode(&png, &pngsize, ssdata, rw, rh, &state); // Save the screenshot to the filesystem if encoding was successful if (!error) error = lodepng_save_file(png, pngsize, ssname); if (error) jgrf_log(JG_LOG_WRN, "lodepng code %u: %s\n", error, lodepng_error_text(error)); else jgrf_log(JG_LOG_SCR, "Screenshot saved"); lodepng_state_cleanup(&state); free(png); free(ssdata); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/video.h000066400000000000000000000044161523150047600220500ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef VIDEO_H #define VIDEO_H extern void (*jgrf_video_create)(void); extern int (*jgrf_video_init)(void); extern void (*jgrf_video_deinit)(void); extern void (*jgrf_video_fullscreen)(void); extern void (*jgrf_video_render)(int); extern void (*jgrf_video_resize)(void); extern void (*jgrf_video_get_scale_params)(float*, float*, float*, float*); extern void (*jgrf_video_set_cursor)(int); extern jg_videoinfo_t* (*jgrf_video_get_info)(void); extern void (*jgrf_video_set_info)(jg_videoinfo_t*); extern void (*jgrf_video_swapbuffers)(void); extern void (*jgrf_video_text)(int, int, const char*); extern void (*jgrf_video_rehash)(void); extern void *(*jgrf_video_get_pixels)(int*, int*); void jgrf_video_setfuncs(void); void jgrf_video_icon_load(SDL_Window*); void jgrf_video_screenshot(void); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/video_gl.c000066400000000000000000001270441523150047600225300ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #define NUMPASSES 2 #define SIZE_GLSLVER 20 #include "jgrf.h" #include "video.h" #include "video_gl.h" #include "settings.h" #include "osd.h" static jgrf_gdata_t *gdata = NULL; static jg_setting_t *settings = NULL; // SDL Window management static SDL_Window *window; static SDL_Cursor *cursor; static SDL_GLContext glcontext; // Pointer to the video buffer when allocated by the frontend static void *videobuf = NULL; // Pointer to the core's video information static jg_videoinfo_t *vidinfo = NULL; // OpenGL related variables static GLuint vao[NUMPASSES]; static GLuint vbo[NUMPASSES]; static GLuint shaderprog[NUMPASSES]; static GLuint tex[NUMPASSES]; static GLuint texfilter[NUMPASSES]; static GLuint framebuf; // Framebuffer for rendering offscreen static int glapi = 0; // Triangle and Texture vertices static GLfloat vertices[] = { -1.0, -1.0, // Vertex 1 (X, Y) Left Bottom -1.0, 1.0, // Vertex 2 (X, Y) Left Top 1.0, -1.0, // Vertex 3 (X, Y) Right Bottom 1.0, 1.0, // Vertex 4 (X, Y) Right Top 0.0, 0.0, // Texture 2 (X, Y) Left Top 0.0, 1.0, // Texture 1 (X, Y) Left Bottom 1.0, 0.0, // Texture 4 (X, Y) Right Top 1.0, 1.0, // Texture 3 (X, Y) Right Bottom }; // OSD static GLuint osd_tex = 0; static GLuint osd_vao = 0; static GLuint osd_vbo = 0; static GLuint osd_shader = 0; static int osd_initialized = 0; // OSD shader source static const char *osd_vshader_src = "in vec2 vertex;\n" "in vec2 vert_tex_coord;\n" "out vec2 tex_coord;\n" "void main() {\n" " gl_Position = vec4(vertex, 0.0, 1.0);\n" " tex_coord = vert_tex_coord;\n" "}\n"; static const char *osd_fshader_src = "in vec2 tex_coord;\n" "uniform sampler2D osd_texture;\n" "out vec4 frag_colour;\n" "void main() {\n" " frag_colour = texture(osd_texture, tex_coord);\n" "}\n"; // Pixel Format static unsigned pixfmt_active = JG_PIXFMT_XRGB8888; static struct _pixfmt { GLuint format; GLuint format_internal; GLuint type; size_t size; } pixfmt; // Dimensions static struct _dimensions { int ww; int wh; float rw; float rh; float xo; float yo; float dpiscale; } dimensions; // Last seen aspect ratio for change detection static double aspect_prev = 0.0f; // Create the SDL OpenGL Window void jgrf_video_gl_create(void) { // Set the GL version switch (settings[VIDEO_API].val) { default: case 0: { // OpenGL - Core Profile SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); break; } case 1: { // OpenGL ES SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); break; } case 2: { // OpenGL - Compatibility Profile SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); break; } } // Disable the alpha channel so that the framebuffer is 100% opaque SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); // Set window flags SDL_WindowFlags windowflags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY; // Set up the window char title[128]; gdata = jgrf_gdata_ptr(); snprintf(title, sizeof(title), "%s", gdata->gamename); jgrf_log(JG_LOG_DBG, "SDL Video driver: %s\n", SDL_GetCurrentVideoDriver()); // Set the window dimensions if (!strcmp(SDL_GetCurrentVideoDriver(), "kmsdrm")) { SDL_DisplayID disp_id = SDL_GetPrimaryDisplay(); const SDL_DisplayMode *dm = SDL_GetDesktopDisplayMode(disp_id); dimensions.ww = dm->w; dimensions.wh = dm->h; } else { dimensions.ww = (vidinfo->aspect * vidinfo->h * settings[VIDEO_SCALE].val) + 0.5; dimensions.wh = (vidinfo->h * settings[VIDEO_SCALE].val) + 0.5; } dimensions.rw = dimensions.ww; dimensions.rh = dimensions.wh; jgrf_log(JG_LOG_DBG, "Creating window with dimensions: %d x %d\n", dimensions.ww, dimensions.wh); // Bring up the window window = SDL_CreateWindow(title, dimensions.ww, dimensions.wh, windowflags); if (!window) jgrf_log(JG_LOG_ERR, "Failed to create window: %s\n", SDL_GetError()); // Store the DPI scale int x, y; SDL_GetWindowSizeInPixels(window, &x, &y); dimensions.dpiscale = (float)x/dimensions.ww; jgrf_video_icon_load(window); // Set the GL context glcontext = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, glcontext); SDL_GL_SetSwapInterval(!bmark); // Vsync off in Benchmark mode if (!glcontext) jgrf_log(JG_LOG_WRN, "Failed to create glcontext: %s\n", SDL_GetError()); // Do post window creation OpenGL setup if (settings[VIDEO_API].val > 1) jgrf_video_gl_setup_compat(); else jgrf_video_gl_setup(); jgrf_log(JG_LOG_INF, "Video: OpenGL %s\n", glGetString(GL_VERSION)); SDL_HideCursor(); // Set fullscreen if required if (settings[VIDEO_FULLSCREEN].val) SDL_SetWindowFullscreen(window, true); } // Initialize video buffer int jgrf_video_gl_init(void) { // Grab settings and global data pointers settings = jgrf_settings_ptr(); gdata = jgrf_gdata_ptr(); if (!(gdata->hints & JG_HINT_VIDEO_INTERNAL)) { // Address of allocated memory owned by frontend but passed to core videobuf = (void*)calloc(vidinfo->wmax * vidinfo->hmax, pixfmt.size); vidinfo->buf = videobuf; } if (gdata->hints & JG_HINT_VIDEO_PRESCALED) settings[VIDEO_SCALE].val = 1; glapi = settings[VIDEO_API].val; jgrf_osd_init(); return 1; } // Flip the image so that it can be written out with proper orientation static inline void jgrf_video_gl_ssflip(uint8_t *pixels, int width, int height, int bytes) { // Flip the pixels size_t rowsize = width * bytes; uint8_t *row = (uint8_t*)calloc(rowsize, sizeof(uint8_t)); uint8_t *low = pixels; uint8_t *high = &pixels[(height - 1) * rowsize]; for (; low < high; low += rowsize, high -= rowsize) { memcpy(row, low, rowsize); memcpy(low, high, rowsize); memcpy(high, row, rowsize); } free(row); } // Dump the pixels rendered to the default framebuffer void *jgrf_video_gl_get_pixels(int *rw, int *rh) { uint8_t *pixels = (uint8_t*)calloc(dimensions.rw * dimensions.rh, sizeof(uint32_t)); // Clear any OSD content before grabbing the framebuffer for (int i = 0; i < JGRF_OSD_NUM_TEXT_SLOTS; ++i) jgrf_osd_text_clear(i); jgrf_osd_shade_set(0); jgrf_osd_fx_clear(); if (glapi > 1) jgrf_video_gl_render_compat(0); else jgrf_video_gl_render(0); // Read the pixels and flip them vertically glReadPixels(dimensions.xo, dimensions.yo, dimensions.rw, dimensions.rh, GL_RGBA, GL_UNSIGNED_BYTE, pixels); jgrf_video_gl_ssflip(pixels, dimensions.rw, dimensions.rh, sizeof(uint32_t)); *rw = dimensions.rw; *rh = dimensions.rh; return pixels; } // Deinitialize OpenGL Video void jgrf_video_gl_deinit(void) { if (osd_tex) glDeleteTextures(1, &osd_tex); if (osd_vbo) glDeleteBuffers(1, &osd_vbo); if (osd_vao) glDeleteVertexArrays(1, &osd_vao); if (osd_shader) glDeleteProgram(osd_shader); osd_tex = osd_vbo = osd_vao = osd_shader = 0; osd_initialized = 0; jgrf_osd_deinit(); if (framebuf) glDeleteFramebuffers(1, &framebuf); for (int i = 0; i < NUMPASSES; ++i) { if (shaderprog[i]) glDeleteProgram(shaderprog[i]); if (tex[i]) glDeleteTextures(1, &tex[i]); if (vao[i]) glDeleteVertexArrays(1, &vao[i]); if (vbo[i]) glDeleteBuffers(1, &vbo[i]); } if (!(gdata->hints & JG_HINT_VIDEO_INTERNAL)) if (videobuf) free(videobuf); if (cursor) SDL_DestroyCursor(cursor); if (glcontext) SDL_GL_DestroyContext(glcontext); SDL_DestroyWindow(window); } // Toggle between fullscreen and windowed void jgrf_video_gl_fullscreen(void) { settings[VIDEO_FULLSCREEN].val ^= 1; SDL_SetWindowFullscreen(window, settings[VIDEO_FULLSCREEN].val ? true : false); // Fullscreen toggle does not trigger SDL_WINDOWEVENT_RESIZED jgrf_video_gl_resize(); } // Set the GL Pixel Format static void jgrf_video_gl_refresh_pixfmt(void) { pixfmt_active = vidinfo->pixfmt; switch (vidinfo->pixfmt) { case JG_PIXFMT_XRGB8888: { pixfmt.format = GL_BGRA; pixfmt.format_internal = GL_RGBA; pixfmt.type = GL_UNSIGNED_BYTE; pixfmt.size = sizeof(uint32_t); jgrf_log(JG_LOG_DBG, "Pixel format: GL_UNSIGNED_BYTE\n"); break; } case JG_PIXFMT_XBGR8888: { pixfmt.format = GL_RGBA; pixfmt.format_internal = GL_RGBA; pixfmt.type = GL_UNSIGNED_BYTE; pixfmt.size = sizeof(uint32_t); jgrf_log(JG_LOG_DBG, "Pixel format: GL_UNSIGNED_BYTE\n"); break; } case JG_PIXFMT_RGBX5551: { pixfmt.format = GL_RGBA; pixfmt.format_internal = GL_RGBA; pixfmt.type = GL_UNSIGNED_SHORT_5_5_5_1; pixfmt.size = sizeof(uint16_t); jgrf_log(JG_LOG_DBG, "Pixel format: GL_UNSIGNED_SHORT_5_5_5_1\n"); break; } case JG_PIXFMT_RGB565: { pixfmt.format = GL_RGB; pixfmt.format_internal = GL_RGB; pixfmt.type = GL_UNSIGNED_SHORT_5_6_5; pixfmt.size = sizeof(uint16_t); jgrf_log(JG_LOG_DBG, "Pixel format: GL_UNSIGNED_SHORT_5_6_5\n"); break; } default: { jgrf_log(JG_LOG_ERR, "Unknown pixel format, exiting...\n"); break; } } } // Refresh any video settings that may have changed static void jgrf_video_gl_refresh(void) { // Recalculate viewport if the aspect ratio changed if (vidinfo->aspect != aspect_prev) { aspect_prev = vidinfo->aspect; jgrf_video_gl_resize(); } float top = (float)vidinfo->y / vidinfo->hmax; float bottom = 1.0 + top - ((vidinfo->hmax - (float)vidinfo->h) / vidinfo->hmax); float left = (float)vidinfo->x / vidinfo->wmax; float right = 1.0 + left - ((vidinfo->wmax -(float)vidinfo->w) / vidinfo->wmax); if (pixfmt_active != vidinfo->pixfmt) jgrf_video_gl_refresh_pixfmt(); // Check if any vertices have changed since last time if (vertices[9] != top || vertices[11] != bottom || vertices[8] != left || vertices[12] != right) { vertices[9] = vertices[13] = top; vertices[11] = vertices[15] = bottom; vertices[8] = vertices[10] = left; vertices[12] = vertices[14] = right; } else { // If nothing changed, return return; } // Bind the VAO/VBO for the offscreen texture, update with new vertex data glBindVertexArray(vao[0]); glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // Resize the offscreen texture glBindTexture(GL_TEXTURE_2D, tex[0]); glTexImage2D(GL_TEXTURE_2D, 0, pixfmt.format_internal, vidinfo->wmax, vidinfo->hmax, 0, pixfmt.format, pixfmt.type, NULL); // Set row length glUseProgram(shaderprog[0]); glPixelStorei(GL_UNPACK_ROW_LENGTH, vidinfo->p); // Resize the output texture glUseProgram(shaderprog[1]); glBindTexture(GL_TEXTURE_2D, tex[1]); glTexImage2D(GL_TEXTURE_2D, 0, pixfmt.format_internal, vidinfo->w, vidinfo->h, 0, pixfmt.format, pixfmt.type, NULL); // Update uniforms for post-processing glUniform4f(glGetUniformLocation(shaderprog[1], "sourceSize"), (float)vidinfo->w, (float)vidinfo->h, 1.0/(float)vidinfo->w, 1.0/(float)vidinfo->h); glUniform4f(glGetUniformLocation(shaderprog[1], "targetSize"), dimensions.rw, dimensions.rh, 1.0/dimensions.rw, 1.0/dimensions.rh); } static void jgrf_video_gl_osd_setup(void) { /* Lazy initialisation of OSD GL resources. Deferred until the first frame because shader compilation needs the GL context fully ready. */ if (osd_initialized) return; /* Allocate the OSD texture at the canvas's fixed size. Filtering is GL_NEAREST so integer scaling produces crisp bitmap text. */ glGenTextures(1, &osd_tex); glBindTexture(GL_TEXTURE_2D, osd_tex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, JGRF_OSD_W, JGRF_OSD_H, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); /* For Core Profile/GLES, set up a small VAO/VBO/shader. For Compatibility Profile, use immediate mode in the render function and skip these. */ if (glapi < 2) { const char *ver = (glapi == 1) ? "#version 300 es\nprecision highp float;\n" : "#version 140\n"; char vsrc[512], fsrc[512]; snprintf(vsrc, sizeof(vsrc), "%s%s", ver, osd_vshader_src); snprintf(fsrc, sizeof(fsrc), "%s%s", ver, osd_fshader_src); GLuint vs = glCreateShader(GL_VERTEX_SHADER); const char *vp = vsrc; glShaderSource(vs, 1, &vp, NULL); glCompileShader(vs); GLint ok = 0; glGetShaderiv(vs, GL_COMPILE_STATUS, &ok); if (!ok) { char log[1024]; GLsizei len = 0; glGetShaderInfoLog(vs, sizeof(log), &len, log); jgrf_log(JG_LOG_WRN, "OSD vshader compile: %.*s\n", (int)len, log); } GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); const char *fp = fsrc; glShaderSource(fs, 1, &fp, NULL); glCompileShader(fs); glGetShaderiv(fs, GL_COMPILE_STATUS, &ok); if (!ok) { char log[1024]; GLsizei len = 0; glGetShaderInfoLog(fs, sizeof(log), &len, log); jgrf_log(JG_LOG_WRN, "OSD fshader compile: %.*s\n", (int)len, log); } osd_shader = glCreateProgram(); glAttachShader(osd_shader, vs); glAttachShader(osd_shader, fs); glBindAttribLocation(osd_shader, 0, "vertex"); glBindAttribLocation(osd_shader, 1, "vert_tex_coord"); glLinkProgram(osd_shader); glGetProgramiv(osd_shader, GL_LINK_STATUS, &ok); if (!ok) { char log[1024]; GLsizei len = 0; glGetProgramInfoLog(osd_shader, sizeof(log), &len, log); jgrf_log(JG_LOG_WRN, "OSD shader link: %.*s\n", (int)len, log); } glDeleteShader(vs); glDeleteShader(fs); /* Bind the sampler uniform to texture unit 0 explicitly. Most drivers default to 0, but some don't. */ glUseProgram(osd_shader); GLint sloc = glGetUniformLocation(osd_shader, "osd_texture"); if (sloc >= 0) glUniform1i(sloc, 0); glGenVertexArrays(1, &osd_vao); glBindVertexArray(osd_vao); glGenBuffers(1, &osd_vbo); glBindBuffer(GL_ARRAY_BUFFER, osd_vbo); // 4 vertices, each (x, y, u, v). Filled in per-frame. glBufferData(GL_ARRAY_BUFFER, 4 * 4 * sizeof(GLfloat), NULL, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat))); } osd_initialized = 1; } // Compute the OSD's destination rectangle (in window pixels, post-shader) static void jgrf_video_gl_osd_compute_rects(float *u0, float *v0, float *u1, float *v1, int *vis_w_out, int *vis_h_out) { float rw = dimensions.rw; float rh = dimensions.rh; /* OSD scale ticks up every time the render area crosses another 256x224 boundary on both axes, rounded to the nearest step rather than floored - so a window just shy of the next boundary still moves up. */ int scale_x = ((int)rw + 128) / 256; int scale_y = ((int)rh + 112) / 224; int scale = (scale_x < scale_y) ? scale_x : scale_y; if (scale < 1) scale = 1; /* Visible region in canvas pixels: how much of the canvas the OSD module fills, and what UV span the OSD quad samples. The render area is the viewport itself (set by the caller), so the quad always fills it exactly - we just need enough canvas to cover. */ int vw = ((int)rw + scale - 1) / scale; if (vw > JGRF_OSD_W) vw = JGRF_OSD_W; int vh = ((int)rh + scale - 1) / scale; if (vh > JGRF_OSD_H) vh = JGRF_OSD_H; *u0 = 0.0f; *v0 = 0.0f; *u1 = rw / ((float)scale * (float)JGRF_OSD_W); *v1 = rh / ((float)scale * (float)JGRF_OSD_H); *vis_w_out = vw; *vis_h_out = vh; } static void jgrf_video_gl_osd_draw_modern(void) { int rw = (int)dimensions.rw; int rh = (int)dimensions.rh; if (rw < 1 || rh < 1) return; int vw, vh; float u0, v0, u1, v1; jgrf_video_gl_osd_compute_rects(&u0, &v0, &u1, &v1, &vw, &vh); jgrf_osd_set_visible(vw, vh); jgrf_osd_step(); int active = 0, dirty = 0; const void *pixels = jgrf_osd_composite(&active, &dirty); if (!active) return; jgrf_video_gl_osd_setup(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, osd_tex); if (dirty) { glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, JGRF_OSD_W, JGRF_OSD_H, GL_RGBA, GL_UNSIGNED_BYTE, pixels); } glViewport(dimensions.xo, dimensions.yo, dimensions.rw, dimensions.rh); GLfloat verts[] = { -1.0f, -1.0f, u0, v1, -1.0f, 1.0f, u0, v0, 1.0f, -1.0f, u1, v1, 1.0f, 1.0f, u1, v0 }; glUseProgram(osd_shader); glBindVertexArray(osd_vao); glBindBuffer(GL_ARRAY_BUFFER, osd_vbo); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisable(GL_BLEND); } static void jgrf_video_gl_osd_draw_compat(void) { int rw = (int)dimensions.rw; int rh = (int)dimensions.rh; if (rw < 1 || rh < 1) return; int vw, vh; float u0, v0, u1, v1; jgrf_video_gl_osd_compute_rects(&u0, &v0, &u1, &v1, &vw, &vh); jgrf_osd_set_visible(vw, vh); jgrf_osd_step(); int active = 0, dirty = 0; const void *pixels = jgrf_osd_composite(&active, &dirty); if (!active) return; jgrf_video_gl_osd_setup(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, osd_tex); if (dirty) { glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); /* tightly packed */ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, JGRF_OSD_W, JGRF_OSD_H, GL_RGBA, GL_UNSIGNED_BYTE, pixels); } glViewport(dimensions.xo, dimensions.yo, dimensions.rw, dimensions.rh); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); /* The compat setup forces texture alpha to a constant 1.0 via a GL_COMBINE env to keep the game framebuffer fully opaque. That breaks the OSD: every pixel of the OSD quad would become opaque, blacking out the screen wherever the OSD is empty. Switch to plain GL_REPLACE for the OSD draw so the texture's actual RGBA reaches the blender, then restore the COMBINE setup before returning. */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBegin(GL_QUADS); glTexCoord2f(u0, v1); glVertex2f(-1.0f, -1.0f); glTexCoord2f(u1, v1); glVertex2f( 1.0f, -1.0f); glTexCoord2f(u1, v0); glVertex2f( 1.0f, 1.0f); glTexCoord2f(u0, v0); glVertex2f(-1.0f, 1.0f); glEnd(); glDisable(GL_BLEND); /* Restore the alpha-forcing combine setup so the next game-quad draw still produces an opaque framebuffer. Mirrors the original setup in jgrf_video_gl_setup_compat. */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } // Render the scene void jgrf_video_gl_render(int render) { jgrf_video_gl_refresh(); // Check for changes // Viewport set to size of the input pixel array glViewport(0, 0, vidinfo->w, vidinfo->h); // Make sure first pass shader program is active glUseProgram(shaderprog[0]); // Bind user-created framebuffer and draw scene onto it glBindFramebuffer(GL_FRAMEBUFFER, framebuf); glBindVertexArray(vao[0]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex[0]); // Render if there is new pixel data, do Black Frame Insertion otherwise if (render) { glPixelStorei(GL_UNPACK_ROW_LENGTH, vidinfo->p); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, // xoffset 0, // yoffset vidinfo->w + vidinfo->x, // width vidinfo->h + vidinfo->y, // height pixfmt.format, // format pixfmt.type, // type vidinfo->buf); } // Clear the screen to black glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Draw a rectangle from the 2 triangles glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // Now deal with the actual image to be output // Viewport adjusted for output glViewport(dimensions.xo, dimensions.yo, dimensions.rw, dimensions.rh); // Make sure second pass shader program is active glUseProgram(shaderprog[1]); // Bind default framebuffer and draw contents of user framebuffer glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindVertexArray(vao[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex[1]); // Clear the screen to black again glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Draw framebuffer contents glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // OSD pass: composite layers, upload texture, draw alpha-blended quad jgrf_video_gl_osd_draw_modern(); } // Render the scene void jgrf_video_gl_render_compat(int render) { jgrf_video_gl_refresh(); // Check for changes // Viewport set to size of the output glViewport(dimensions.xo, dimensions.yo, dimensions.rw, dimensions.rh); // Clear the screen to black glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex[0]); // Render if there is new pixel data, do Black Frame Insertion otherwise if (render) { glPixelStorei(GL_UNPACK_ROW_LENGTH, vidinfo->p); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, // xoffset 0, // yoffset vidinfo->w + vidinfo->x, // width vidinfo->h + vidinfo->y, // height pixfmt.format, // format pixfmt.type, // type vidinfo->buf); } glEnable(GL_TEXTURE_2D); glBegin(GL_QUADS); glTexCoord2f(vertices[10], vertices[11]); glVertex2f(vertices[0], vertices[1]); // Bottom Left glTexCoord2f(vertices[8], vertices[9]); glVertex2f(vertices[2], vertices[3]); // Top Left glTexCoord2f(vertices[12], vertices[13]); glVertex2f(vertices[6], vertices[7]); // Top Right glTexCoord2f(vertices[14], vertices[15]); glVertex2f(vertices[4], vertices[5]); // Bottom Right glEnd(); // OSD pass: draw composited canvas as alpha-blended quad over the game jgrf_video_gl_osd_draw_compat(); } // Handle viewport resizing void jgrf_video_gl_resize(void) { SDL_GetWindowSizeInPixels(window, &dimensions.ww, &dimensions.wh); dimensions.rw = dimensions.ww; dimensions.rh = dimensions.wh; // Check which dimension to optimize if (dimensions.rh * vidinfo->aspect > dimensions.rw) dimensions.rh = dimensions.rw / vidinfo->aspect + 0.5; else if (dimensions.rw / vidinfo->aspect > dimensions.rh) dimensions.rw = dimensions.rh * vidinfo->aspect + 0.5; // Store X and Y offsets dimensions.xo = (dimensions.ww - dimensions.rw) / 2; dimensions.yo = (dimensions.wh - dimensions.rh) / 2; // Update the targetSize uniform glUniform4f(glGetUniformLocation(shaderprog[1], "targetSize"), dimensions.rw, dimensions.rh, 1.0/dimensions.rw, 1.0/dimensions.rh); // Get current display mode SDL_DisplayID displayid = SDL_GetDisplayForWindow(window); const SDL_DisplayMode *dm = SDL_GetCurrentDisplayMode(displayid); // Set the base fps for use in the main loop if (dm) jgrf_set_screenfps((int)(dm->refresh_rate + 0.5)); } // Retrieve scale parameters for pointing device input void jgrf_video_gl_get_scale_params(float *xscale, float *yscale, float *xo, float *yo) { *xscale = dimensions.rw / (vidinfo->aspect * vidinfo->h) / dimensions.dpiscale; *yscale = dimensions.rh / vidinfo->h / dimensions.dpiscale; *xo = dimensions.xo / dimensions.dpiscale; *yo = dimensions.yo / dimensions.dpiscale; } // Retrieve video information jg_videoinfo_t* jgrf_video_gl_get_info(void) { return vidinfo; } // Set the cursor void jgrf_video_gl_set_cursor(int ctype) { cursor = SDL_CreateSystemCursor(ctype); SDL_ShowCursor(); SDL_SetCursor(cursor); } // Pass a pointer to the video info held in the core into the frontend void jgrf_video_gl_set_info(jg_videoinfo_t *ptr) { vidinfo = ptr; // Also set the GL pixel format at this time jgrf_video_gl_refresh_pixfmt(); } // Load a shader source file into memory static const GLchar* jgrf_video_gl_shader_load(const char *filename) { FILE *file = fopen(filename, "rb"); if (!file) jgrf_log(JG_LOG_ERR, "Could not open shader file, exiting...\n"); // Get the size of the shader source file fseek(file, 0, SEEK_END); size_t size = ftell(file); rewind(file); // Allocate memory to store the shader source including version string GLchar *src = (GLchar*)calloc(size + SIZE_GLSLVER, sizeof(GLchar)); if (!src) jgrf_log(JG_LOG_ERR, "Could not allocate memory, exiting...\n"); // Allocate memory for the shader source without version string GLchar *shader = (GLchar*)calloc(size + 1, sizeof(GLchar)); // Write version string into the buffer for the full shader source snprintf(src, SIZE_GLSLVER, "%s", settings[VIDEO_API].val ? "#version 300 es\n" : "#version 140\n"); if (!shader || !fread(shader, size, sizeof(GLchar), file)) { free(src); fclose(file); jgrf_log(JG_LOG_ERR, "Could not open shader file, exiting...\n"); return NULL; } // Close file handle after reading fclose(file); // Append shader source to version string src = strncat(src, shader, size + SIZE_GLSLVER); // Free the shader source without version string free(shader); return src; } // Create a shader program from a vertex shader and a fragment shader static GLuint jgrf_video_gl_prog_create(const char *vs, const char *fs) { char vspath[192]; char fspath[192]; struct stat fbuf; // First find what path to use. Check local first. snprintf(vspath, sizeof(vspath), "%s%cshaders%cdefault.vs", gdata->binpath, SEP, SEP); if (stat(vspath, &fbuf) == 0) { // Found it locally snprintf(vspath, sizeof(vspath), "%s%cshaders%c%s", gdata->binpath, SEP, SEP, vs); snprintf(fspath, sizeof(fspath), "%s%cshaders%c%s", gdata->binpath, SEP, SEP, fs); } #ifdef JGRF_STATIC #if defined(DATADIR) else { // Use the system-wide path snprintf(vspath, sizeof(vspath), "%s%cjollygood%c%s%cshaders%c%s", DATADIR, SEP, SEP, jg_get_coreinfo("")->name, SEP, SEP, vs); snprintf(fspath, sizeof(fspath), "%s%cjollygood%c%s%cshaders%c%s", DATADIR, SEP, SEP, jg_get_coreinfo("")->name, SEP, SEP, fs); } #endif #else #if defined(DATADIR) else { // Use the system-wide path snprintf(vspath, sizeof(vspath), "%s%cjollygood%cjgrf%cshaders%c%s", DATADIR, SEP, SEP, SEP, SEP, vs); snprintf(fspath, sizeof(fspath), "%s%cjollygood%cjgrf%cshaders%c%s", DATADIR, SEP, SEP, SEP, SEP, fs); } #endif #endif const GLchar *vsrc = jgrf_video_gl_shader_load(vspath); const GLchar *fsrc = jgrf_video_gl_shader_load(fspath); GLint err; // Create and compile the vertex shader GLuint vshader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vshader, 1, &vsrc, NULL); glCompileShader(vshader); // Test if the shader compiled glGetShaderiv(vshader, GL_COMPILE_STATUS, &err); if (err == GL_FALSE) { char shaderlog[1024]; glGetShaderInfoLog(vshader, 1024, NULL, shaderlog); jgrf_log(JG_LOG_WRN, "Vertex shader: %s", shaderlog); } // Create and compile the fragment shader GLuint fshader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fshader, 1, &fsrc, NULL); glCompileShader(fshader); // Test if the fragment shader compiled glGetShaderiv(fshader, GL_COMPILE_STATUS, &err); if (err == GL_FALSE) { char shaderlog[1024]; glGetShaderInfoLog(fshader, 1024, NULL, shaderlog); jgrf_log(JG_LOG_WRN, "Fragment shader: %s", shaderlog); } // Free the allocated memory for shader sources free((GLchar*)vsrc); free((GLchar*)fsrc); // Create the shader program GLuint prog = glCreateProgram(); glAttachShader(prog, vshader); glAttachShader(prog, fshader); glLinkProgram(prog); // Clean up fragment and vertex shaders glDeleteShader(vshader); glDeleteShader(fshader); // Return the successfully linked shader program return prog; } static void jgrf_video_gl_shader_setup(void) { for (int i = 0; i < NUMPASSES; ++i) { if (shaderprog[i]) glDeleteProgram(shaderprog[i]); texfilter[i] = GL_NEAREST; } /* Bind vao[0] so the attribute pointer setup below applies to it. At init time vao[0] happens to be bound by the surrounding setup code, but rehash() can be called from anywhere - including immediately after an OSD draw, when osd_vao is the bound VAO. Without this bind, the OSD VAO ends up pointing at the game's vertex buffer and the OSD silently breaks. */ glBindVertexArray(vao[0]); glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // Create the shader program for the first pass (clipping) shaderprog[0] = jgrf_video_gl_prog_create("default.vs", "default.fs"); GLint posattrib = glGetAttribLocation(shaderprog[0], "position"); glEnableVertexAttribArray(posattrib); glVertexAttribPointer(posattrib, 2, GL_FLOAT, GL_FALSE, 0, 0); GLint texattrib = glGetAttribLocation(shaderprog[0], "vtxCoord"); glEnableVertexAttribArray(texattrib); glVertexAttribPointer(texattrib, 2, GL_FLOAT, GL_FALSE, 0, (void*)(8 * sizeof(GLfloat))); // Set up uniform for input texture glUseProgram(shaderprog[0]); glUniform1i(glGetUniformLocation(shaderprog[0], "source"), 0); switch (settings[VIDEO_SHADER].val) { default: case 0: { // Nearest Neighbour shaderprog[1] = jgrf_video_gl_prog_create("default.vs", "default.fs"); break; } case 1: { // Linear shaderprog[1] = jgrf_video_gl_prog_create("default.vs", "default.fs"); texfilter[1] = GL_LINEAR; break; } case 2: { // Sharp Bilinear shaderprog[1] = jgrf_video_gl_prog_create("default.vs", "sharp-bilinear.fs"); texfilter[0] = GL_LINEAR; texfilter[1] = GL_LINEAR; break; } case 3: { // AANN shaderprog[1] = jgrf_video_gl_prog_create("default.vs", "aann.fs"); break; } case 4: { // CRT-Yee64 shaderprog[1] = jgrf_video_gl_prog_create("default.vs", "crt-yee64.fs"); break; } case 5: { // CRTea shaderprog[1] = jgrf_video_gl_prog_create("default.vs", "crtea.fs"); break; } case 6: { // LCD shaderprog[1] = jgrf_video_gl_prog_create("default.vs", "lcd.fs"); break; } } // Set texture parameters for input texture glBindTexture(GL_TEXTURE_2D, tex[0]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texfilter[0]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texfilter[0]); // Bind vertex array and specify layout for second pass glBindVertexArray(vao[1]); glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); GLint posattrib_out = glGetAttribLocation(shaderprog[1], "position"); glEnableVertexAttribArray(posattrib_out); glVertexAttribPointer(posattrib_out, 2, GL_FLOAT, GL_FALSE, 0, 0); GLint texattrib_out = glGetAttribLocation(shaderprog[1], "vtxCoord"); glEnableVertexAttribArray(texattrib_out); glVertexAttribPointer(texattrib_out, 2, GL_FLOAT, GL_FALSE, 0, (void*)(8 * sizeof(GLfloat))); // Set up uniforms for post-processing texture glUseProgram(shaderprog[1]); glUniform1i(glGetUniformLocation(shaderprog[1], "source"), 0); glUniform4f(glGetUniformLocation(shaderprog[1], "sourceSize"), (float)vidinfo->w, (float)vidinfo->h, 1.0/(float)vidinfo->w, 1.0/(float)vidinfo->h); glUniform4f(glGetUniformLocation(shaderprog[1], "targetSize"), dimensions.rw, dimensions.rh, 1.0/dimensions.rw, 1.0/dimensions.rh); // Settings for CRTea int masktype = 0, maskstr = 0, scanstr = 0, sharpness = 0, curve = settings[VIDEO_CRTEA_CURVE].val, corner = settings[VIDEO_CRTEA_CORNER].val, tcurve = settings[VIDEO_CRTEA_TCURVE].val; switch (settings[VIDEO_CRTEA_MODE].val) { default: case 0: { // Scanlines masktype = 0; maskstr = 0; scanstr = 6; sharpness = 3; break; } case 1: { // Aperture Grille Lite masktype = 1; maskstr = 5; scanstr = 6; sharpness = 7; break; } case 2: { // Aperture Grille masktype = 2; maskstr = 5; scanstr = 6; sharpness = 7; break; } case 3: { // Shadow Mask masktype = 3; maskstr = 4; scanstr = 6; sharpness = 3; break; } case 4: { // Custom masktype = settings[VIDEO_CRTEA_MASKTYPE].val; maskstr = settings[VIDEO_CRTEA_MASKSTR].val; scanstr = settings[VIDEO_CRTEA_SCANSTR].val; sharpness = settings[VIDEO_CRTEA_SHARPNESS].val; break; } } glUniform1i(glGetUniformLocation(shaderprog[1], "masktype"), masktype); glUniform1f(glGetUniformLocation(shaderprog[1], "maskstr"), maskstr / 10.0); glUniform1f(glGetUniformLocation(shaderprog[1], "scanstr"), scanstr / 10.0); glUniform1f(glGetUniformLocation(shaderprog[1], "sharpness"), (float)sharpness); glUniform1f(glGetUniformLocation(shaderprog[1], "curve"), curve / 100.0); glUniform1f(glGetUniformLocation(shaderprog[1], "corner"), corner ? (float)corner : -3.0); glUniform1f(glGetUniformLocation(shaderprog[1], "tcurve"), tcurve / 10.0); // Set parameters for output texture glBindTexture(GL_TEXTURE_2D, tex[1]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texfilter[1]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texfilter[1]); } // Set up OpenGL void jgrf_video_gl_setup(void) { // Create Vertex Array Objects glGenVertexArrays(1, &vao[0]); glGenVertexArrays(1, &vao[1]); // Create Vertex Buffer Objects glGenBuffers(1, &vbo[0]); glGenBuffers(1, &vbo[1]); // Bind buffers for vertex buffer objects glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); GLfloat vertices_out[] = { -1.0, -1.0, // Vertex 1 (X, Y) Left Bottom -1.0, 1.0, // Vertex 2 (X, Y) Left Top 1.0, -1.0, // Vertex 3 (X, Y) Right Bottom 1.0, 1.0, // Vertex 4 (X, Y) Right Top 0.0, 1.0, // Texture 1 (X, Y) Left Bottom 0.0, 0.0, // Texture 2 (X, Y) Left Top 1.0, 1.0, // Texture 3 (X, Y) Right Bottom 1.0, 0.0, // Texture 4 (X, Y) Right Top }; glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_out), vertices_out, GL_STATIC_DRAW); // Bind vertex array and specify layout for first pass glBindVertexArray(vao[0]); glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // Generate texture for raw game output glGenTextures(1, &tex[0]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex[0]); // The full sized source image before any clipping if (!(gdata->hints & JG_HINT_VIDEO_INTERNAL)) { glTexImage2D(GL_TEXTURE_2D, 0, pixfmt.format_internal, vidinfo->wmax, vidinfo->hmax, 0, pixfmt.format, pixfmt.type, vidinfo->buf); } // Create framebuffer glGenFramebuffers(1, &framebuf); glBindFramebuffer(GL_FRAMEBUFFER, framebuf); // Create texture to hold colour buffer glGenTextures(1, &tex[1]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex[1]); // The framebuffer texture that is being rendered to offscreen, after clip glTexImage2D(GL_TEXTURE_2D, 0, pixfmt.format_internal, vidinfo->w, vidinfo->h, 0, pixfmt.format, pixfmt.type, NULL); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex[1], 0); jgrf_video_gl_shader_setup(); jgrf_video_gl_resize(); jgrf_video_gl_refresh(); } // Set up OpenGL - Compatibility Profile void jgrf_video_gl_setup_compat(void) { switch (settings[VIDEO_SHADER].val) { case 0: { // Nearest Neighbour texfilter[0] = GL_NEAREST; break; } case 1: { // Linear texfilter[0] = GL_LINEAR; break; } default: { texfilter[0] = GL_LINEAR; break; } } // Generate texture for raw game output glEnable(GL_TEXTURE_2D); glGenTextures(1, &tex[0]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex[0]); // The full sized source image before any clipping if (!(gdata->hints & JG_HINT_VIDEO_INTERNAL)) { glTexImage2D(GL_TEXTURE_2D, 0, pixfmt.format_internal, vidinfo->wmax, vidinfo->hmax, 0, pixfmt.format, pixfmt.type, vidinfo->buf); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texfilter[0]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texfilter[0]); // Force the alpha channel to be 100% opaque glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT); GLfloat constcolor[] = {0.0f, 0.0f, 0.0f, 1.0f}; glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, constcolor); jgrf_video_gl_resize(); jgrf_video_gl_refresh(); jgrf_log(JG_LOG_WRN, "OpenGL Compatibility Profile supports basic functionality only - no " "post-processing shaders.\n" ); } // Swap Buffers void jgrf_video_gl_swapbuffers(void) { SDL_GL_SwapWindow(window); } // Set a text message to be output for a number of frames void jgrf_video_gl_text(int index, int frames, const char *msg) { int anchor; int x = 0, y = 0; int f = frames; switch (index) { case JGRF_OSD_TEXT_FRONTEND: anchor = JGRF_OSD_ANCHOR_BL; x = 0; y = 0; break; case JGRF_OSD_TEXT_CORE: anchor = JGRF_OSD_ANCHOR_BR; x = 0; y = 0; break; case JGRF_OSD_TEXT_MENU: anchor = JGRF_OSD_ANCHOR_TL; x = 8; y = 8; /* Menu slot is sticky: any positive frames count means "keep showing until cleared". 0 clears it. */ f = (frames > 0) ? -1 : 0; break; case JGRF_OSD_TEXT_AUX: default: anchor = JGRF_OSD_ANCHOR_TR; x = 0; y = 0; break; } if (f == 0 || !msg || msg[0] == '\0') { jgrf_osd_text_clear(index); return; } jgrf_osd_text(index, f, anchor, x, y, 0, msg); } void jgrf_video_gl_rehash(void) { // Avoid applying shaders under Compatibility Profile if (glapi < 2) jgrf_video_gl_shader_setup(); SDL_SetWindowFullscreen(window, settings[VIDEO_FULLSCREEN].val ? true : false); jgrf_video_gl_resize(); jgrf_osd_rehash(); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/video_gl.h000066400000000000000000000042651523150047600225340ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef VIDEO_GL_H #define VIDEO_GL_H void jgrf_video_gl_create(void); int jgrf_video_gl_init(void); void jgrf_video_gl_deinit(void); void jgrf_video_gl_fullscreen(void); void jgrf_video_gl_render(int); void jgrf_video_gl_render_compat(int); void jgrf_video_gl_resize(void); void jgrf_video_gl_get_scale_params(float*, float*, float*, float*); void jgrf_video_gl_set_cursor(int); jg_videoinfo_t* jgrf_video_gl_get_info(void); void jgrf_video_gl_set_info(jg_videoinfo_t*); void jgrf_video_gl_setup(void); void jgrf_video_gl_setup_compat(void); void jgrf_video_gl_swapbuffers(void); void jgrf_video_gl_text(int, int, const char*); void *jgrf_video_gl_get_pixels(int*, int*); void jgrf_video_gl_rehash(void); #endif jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/video_vk.c000066400000000000000000002022371523150047600225440ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Vulkan backend, structurally translated from video_gl.c. Render graph: Pass 0 (clip): source (wmax x hmax) -> offscreen (w x h) Pass 1 (post): offscreen -> swapchain, letterboxed viewport OSD pass: alpha-blended over the swapchain in the same render pass Shaders are SPIR-V loaded from ".spv"; to switch to runtime GLSL compilation (libshaderc), only vk_shader_module() needs to change. Post-processing parameters are a single push-constant block (< 128 bytes). One frame in flight; a fence serializes CPU uploads and GPU reads. */ #include #include #include #include #include #include #include #include #include #define NUMPASSES 2 #define VK_SWAP_MAX 8 // Upper bound on swapchain images tracked // The offscreen (pass 0) target format, referenced by the render pass and image #define VK_FMT_OFFSCREEN VK_FORMAT_R8G8B8A8_UNORM #include "jgrf.h" #include "video.h" #include "video_vk.h" #include "settings.h" #include "osd.h" static jgrf_gdata_t *gdata = NULL; static jg_setting_t *settings = NULL; // SDL Window management static SDL_Window *window; static SDL_Cursor *cursor; // Pointer to the video buffer when allocated by the frontend static void *videobuf = NULL; // Pointer to the core's video information static jg_videoinfo_t *vidinfo = NULL; // Core Vulkan objects static VkInstance instance = VK_NULL_HANDLE; static VkSurfaceKHR surface = VK_NULL_HANDLE; static VkPhysicalDevice physdev = VK_NULL_HANDLE; static VkDevice device = VK_NULL_HANDLE; static uint32_t qf_graphics = 0; static uint32_t qf_present = 0; static VkQueue q_graphics = VK_NULL_HANDLE; static VkQueue q_present = VK_NULL_HANDLE; static VkPhysicalDeviceMemoryProperties memprops; // Swapchain static VkSwapchainKHR swapchain = VK_NULL_HANDLE; static VkFormat swap_format = VK_FORMAT_B8G8R8A8_UNORM; static VkExtent2D swap_extent; static uint32_t swap_count = 0; static VkImage swap_images[VK_SWAP_MAX]; static VkImageView swap_views[VK_SWAP_MAX]; static VkFramebuffer swap_fbos[VK_SWAP_MAX]; static uint32_t swap_index = 0; // image acquired this frame // Render passes static VkRenderPass rp_offscreen = VK_NULL_HANDLE; static VkRenderPass rp_screen = VK_NULL_HANDLE; // Command + sync (single frame in flight) static VkCommandPool cmdpool = VK_NULL_HANDLE; static VkCommandBuffer cmdbuf = VK_NULL_HANDLE; static VkSemaphore sem_acquire = VK_NULL_HANDLE; static VkSemaphore sem_render = VK_NULL_HANDLE; static VkFence fence_frame = VK_NULL_HANDLE; // Managed image: image + backing memory + view typedef struct _vktex { VkImage image; VkDeviceMemory memory; VkImageView view; VkFormat format; uint32_t w, h; VkImageLayout layout; } vktex_t; static vktex_t tex_source; // raw game output (wmax x hmax) static vktex_t tex_offscreen; // clipped target sampled by pass 1 (w x h) static vktex_t tex_osd; // OSD canvas (JGRF_OSD_W x JGRF_OSD_H) static VkFramebuffer fbo_offscreen = VK_NULL_HANDLE; // Samplers: nearest and linear, picked per pass like texfilter[] in GL static VkSampler smp_nearest = VK_NULL_HANDLE; static VkSampler smp_linear = VK_NULL_HANDLE; // Staging buffers for per-frame uploads (source pixels, OSD canvas) typedef struct _vkbuf { VkBuffer buffer; VkDeviceMemory memory; void *mapped; VkDeviceSize size; } vkbuf_t; static vkbuf_t stage_source; static vkbuf_t stage_osd; static vkbuf_t readback; // for screenshots // Vertex buffers (per vertex: vec2 position, vec2 texcoord) static vkbuf_t vbo_pass0; // clip quad, texcoords updated on clip change static vkbuf_t vbo_pass1; // full output quad, static static vkbuf_t vbo_osd; // OSD quad, texcoords updated per frame // Descriptor set layout, pool, and per-image sets static VkDescriptorSetLayout dsl = VK_NULL_HANDLE; static VkDescriptorPool dpool = VK_NULL_HANDLE; static VkDescriptorSet ds_source = VK_NULL_HANDLE; // samples tex_source static VkDescriptorSet ds_offscreen = VK_NULL_HANDLE; // samples tex_offscreen static VkDescriptorSet ds_osd = VK_NULL_HANDLE; // samples tex_osd static VkPipelineLayout playout = VK_NULL_HANDLE; // Pipelines: pass0 (clip), pass1 (selected post-process), osd static VkPipeline pipe_pass0 = VK_NULL_HANDLE; static VkPipeline pipe_pass1 = VK_NULL_HANDLE; static VkPipeline pipe_osd = VK_NULL_HANDLE; // Push-constant block for the post-process fragment shader (std430 layout) typedef struct _vk_pushconst { float sourceSize[4]; float targetSize[4]; int32_t masktype; float maskstr; float scanstr; float sharpness; float curve; float corner; float tcurve; } vk_pushconst_t; static vk_pushconst_t pushconst; // Vertex data: triangle strip, per-vertex (x, y, u, v) // Pass 0 texcoords (clip rectangle) are patched in refresh() static float verts_pass0[] = { -1.0f, -1.0f, 0.0f, 0.0f, // Left Bottom -1.0f, 1.0f, 0.0f, 1.0f, // Left Top 1.0f, -1.0f, 1.0f, 0.0f, // Right Bottom 1.0f, 1.0f, 1.0f, 1.0f, // Right Top }; static const float verts_pass1[] = { -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; // Pixel Format static unsigned pixfmt_active = JG_PIXFMT_XRGB8888; static struct _pixfmt { VkFormat format; size_t size; } pixfmt; // Dimensions static struct _dimensions { int ww; int wh; float rw; float rh; float xo; float yo; float dpiscale; } dimensions; // Last window pixel size the swapchain was built for (zero = freshly created) static int win_pw = 0; static int win_ph = 0; // Last seen aspect ratio for change detection static double aspect_prev = 0.0; // Currently selected post-process shader, to detect rehash changes static int shader_active = -1; // Forward declarations for internal helpers static void jgrf_video_vk_refresh(void); static void jgrf_video_vk_setup(void); static void jgrf_video_vk_recreate_swapchain(void); static void vk_pipelines_create(void); static void vk_pipelines_destroy(void); // Log a fatal Vulkan error static void vk_check(VkResult r, const char *what) { if (r != VK_SUCCESS) jgrf_log(JG_LOG_ERR, "Vulkan: %s failed (%d)\n", what, (int)r); } // Find a memory type index matching a type filter and required properties static uint32_t vk_memtype(uint32_t filter, VkMemoryPropertyFlags props) { for (uint32_t i = 0; i < memprops.memoryTypeCount; ++i) { if ((filter & (1u << i)) && (memprops.memoryTypes[i].propertyFlags & props) == props) return i; } jgrf_log(JG_LOG_ERR, "Vulkan: no suitable memory type\n"); return 0; } // Allocate a buffer with backing memory, mapped if host visible static void vk_buffer_create(vkbuf_t *b, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags props) { b->size = size; b->mapped = NULL; VkBufferCreateInfo bi = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bi.size = size; bi.usage = usage; bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE; vk_check(vkCreateBuffer(device, &bi, NULL, &b->buffer), "vkCreateBuffer"); VkMemoryRequirements req; vkGetBufferMemoryRequirements(device, b->buffer, &req); VkMemoryAllocateInfo ai = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; ai.allocationSize = req.size; ai.memoryTypeIndex = vk_memtype(req.memoryTypeBits, props); vk_check(vkAllocateMemory(device, &ai, NULL, &b->memory), "vkAllocateMemory(buffer)"); vkBindBufferMemory(device, b->buffer, b->memory, 0); if (props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) vkMapMemory(device, b->memory, 0, size, 0, &b->mapped); } static void vk_buffer_destroy(vkbuf_t *b) { if (b->mapped) { vkUnmapMemory(device, b->memory); b->mapped = NULL; } if (b->buffer) { vkDestroyBuffer(device, b->buffer, NULL); b->buffer = VK_NULL_HANDLE; } if (b->memory) { vkFreeMemory(device, b->memory, NULL); b->memory = VK_NULL_HANDLE; } } // Begin a one-shot command buffer for setup/transfer work static VkCommandBuffer vk_cmd_begin(void) { VkCommandBufferAllocateInfo ai = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; ai.commandPool = cmdpool; ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; ai.commandBufferCount = 1; VkCommandBuffer cb; vkAllocateCommandBuffers(device, &ai, &cb); VkCommandBufferBeginInfo bi = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(cb, &bi); return cb; } static void vk_cmd_end(VkCommandBuffer cb) { vkEndCommandBuffer(cb); VkSubmitInfo si = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO }; si.commandBufferCount = 1; si.pCommandBuffers = &cb; vkQueueSubmit(q_graphics, 1, &si, VK_NULL_HANDLE); vkQueueWaitIdle(q_graphics); vkFreeCommandBuffers(device, cmdpool, 1, &cb); } // Record an image layout transition static void vk_transition(VkCommandBuffer cb, vktex_t *t, VkImageLayout newlayout, VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, VkAccessFlags src_access, VkAccessFlags dst_access) { VkImageMemoryBarrier b = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; b.oldLayout = t->layout; b.newLayout = newlayout; b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; b.image = t->image; b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; b.subresourceRange.levelCount = 1; b.subresourceRange.layerCount = 1; b.srcAccessMask = src_access; b.dstAccessMask = dst_access; vkCmdPipelineBarrier(cb, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &b); t->layout = newlayout; } // Create a managed image (sampled, optionally a colour attachment) static void vk_tex_create(vktex_t *t, uint32_t w, uint32_t h, VkFormat fmt, int attachment) { t->w = w; t->h = h; t->format = fmt; t->layout = VK_IMAGE_LAYOUT_UNDEFINED; VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; if (attachment) usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; VkImageCreateInfo ii = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; ii.imageType = VK_IMAGE_TYPE_2D; ii.format = fmt; ii.extent.width = w; ii.extent.height = h; ii.extent.depth = 1; ii.mipLevels = 1; ii.arrayLayers = 1; ii.samples = VK_SAMPLE_COUNT_1_BIT; ii.tiling = VK_IMAGE_TILING_OPTIMAL; ii.usage = usage; ii.sharingMode = VK_SHARING_MODE_EXCLUSIVE; ii.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; vk_check(vkCreateImage(device, &ii, NULL, &t->image), "vkCreateImage"); VkMemoryRequirements req; vkGetImageMemoryRequirements(device, t->image, &req); VkMemoryAllocateInfo ai = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; ai.allocationSize = req.size; ai.memoryTypeIndex = vk_memtype(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); vk_check(vkAllocateMemory(device, &ai, NULL, &t->memory), "vkAllocateMemory(image)"); vkBindImageMemory(device, t->image, t->memory, 0); VkImageViewCreateInfo vi = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; vi.image = t->image; vi.viewType = VK_IMAGE_VIEW_TYPE_2D; vi.format = fmt; vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; vi.subresourceRange.levelCount = 1; vi.subresourceRange.layerCount = 1; vk_check(vkCreateImageView(device, &vi, NULL, &t->view), "vkCreateImageView"); } static void vk_tex_destroy(vktex_t *t) { if (t->view) { vkDestroyImageView(device, t->view, NULL); t->view = VK_NULL_HANDLE; } if (t->image) { vkDestroyImage(device, t->image, NULL); t->image = VK_NULL_HANDLE; } if (t->memory) { vkFreeMemory(device, t->memory, NULL); t->memory = VK_NULL_HANDLE; } } // Update a descriptor set to sample a given image view with a sampler static void vk_ds_write(VkDescriptorSet set, VkImageView view, VkSampler sampler) { VkDescriptorImageInfo info = { 0 }; info.sampler = sampler; info.imageView = view; info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkWriteDescriptorSet w = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; w.dstSet = set; w.dstBinding = 0; w.descriptorCount = 1; w.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; w.pImageInfo = &info; vkUpdateDescriptorSets(device, 1, &w, 0, NULL); } // Load a SPIR-V module from ".spv", searching binpath then DATADIR static VkShaderModule vk_shader_module(const char *name) { char path[256]; struct stat fbuf; snprintf(path, sizeof(path), "%s%cshaders%c%s.spv", gdata->binpath, SEP, SEP, name); if (stat(path, &fbuf) != 0) { #ifdef JGRF_STATIC #if defined(DATADIR) snprintf(path, sizeof(path), "%s%cjollygood%c%s%cshaders%c%s.spv", DATADIR, SEP, SEP, jg_get_coreinfo("")->name, SEP, SEP, name); #endif #else #if defined(DATADIR) snprintf(path, sizeof(path), "%s%cjollygood%cjgrf%cshaders%c%s.spv", DATADIR, SEP, SEP, SEP, SEP, name); #endif #endif } FILE *file = fopen(path, "rb"); if (!file) { jgrf_log(JG_LOG_WRN, "Could not open SPIR-V module: %s\n", path); return VK_NULL_HANDLE; } fseek(file, 0, SEEK_END); long size = ftell(file); rewind(file); // SPIR-V is a stream of 32-bit words; size must be a multiple of four if (size <= 0 || (size & 3)) { jgrf_log(JG_LOG_WRN, "Bad SPIR-V size: %s\n", path); fclose(file); return VK_NULL_HANDLE; } uint32_t *code = (uint32_t*)malloc((size_t)size); if (!code || fread(code, 1, (size_t)size, file) != (size_t)size) { jgrf_log(JG_LOG_WRN, "Could not read SPIR-V module: %s\n", path); free(code); fclose(file); return VK_NULL_HANDLE; } fclose(file); VkShaderModuleCreateInfo ci = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; ci.codeSize = (size_t)size; ci.pCode = code; VkShaderModule mod = VK_NULL_HANDLE; if (vkCreateShaderModule(device, &ci, NULL, &mod) != VK_SUCCESS) jgrf_log(JG_LOG_WRN, "vkCreateShaderModule failed: %s\n", path); free(code); return mod; } // Map the selected shader index to a fragment SPIR-V name and per-pass filters static const char *vk_shader_select(int *filter_src, int *filter_dst) { *filter_src = 0; // source texture (GL texfilter[0]) *filter_dst = 0; // offscreen texture (GL texfilter[1]) switch (settings[VIDEO_SHADER].val) { default: case 0: return "default.frag"; // Nearest Neighbour case 1: *filter_dst = 1; return "default.frag"; // Linear case 2: *filter_src = 1; *filter_dst = 1; return "sharp-bilinear.frag"; case 3: return "aann.frag"; case 4: return "crt-yee64.frag"; case 5: return "crtea.frag"; case 6: return "lcd.frag"; } } // Build a graphics pipeline from a vertex + fragment SPIR-V pair static VkPipeline vk_pipeline_build(const char *vname, const char *fname, VkRenderPass rp, int blend) { VkShaderModule vs = vk_shader_module(vname); VkShaderModule fs = vk_shader_module(fname); if (vs == VK_NULL_HANDLE || fs == VK_NULL_HANDLE) { // Fall back to the default fragment so the screen is never black if (fs == VK_NULL_HANDLE) { jgrf_log(JG_LOG_WRN, "Falling back to default.frag for missing %s\n", fname); fs = vk_shader_module("default.frag"); } if (vs == VK_NULL_HANDLE) vs = vk_shader_module("default.vert"); if (vs == VK_NULL_HANDLE || fs == VK_NULL_HANDLE) { if (vs) vkDestroyShaderModule(device, vs, NULL); if (fs) vkDestroyShaderModule(device, fs, NULL); return VK_NULL_HANDLE; } } VkPipelineShaderStageCreateInfo stages[2] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }, }; stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; stages[0].module = vs; stages[0].pName = "main"; stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; stages[1].module = fs; stages[1].pName = "main"; // Vertex input: one binding, vec2 position + vec2 texcoord VkVertexInputBindingDescription bind = { 0 }; bind.binding = 0; bind.stride = 4 * sizeof(float); bind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; VkVertexInputAttributeDescription attr[2] = { { 0 }, { 0 } }; attr[0].location = 0; attr[0].binding = 0; attr[0].format = VK_FORMAT_R32G32_SFLOAT; attr[0].offset = 0; attr[1].location = 1; attr[1].binding = 0; attr[1].format = VK_FORMAT_R32G32_SFLOAT; attr[1].offset = 2 * sizeof(float); VkPipelineVertexInputStateCreateInfo vi = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; vi.vertexBindingDescriptionCount = 1; vi.pVertexBindingDescriptions = &bind; vi.vertexAttributeDescriptionCount = 2; vi.pVertexAttributeDescriptions = attr; VkPipelineInputAssemblyStateCreateInfo ia = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; // Viewport and scissor are dynamic, set per draw VkPipelineViewportStateCreateInfo vp = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; vp.viewportCount = 1; vp.scissorCount = 1; VkPipelineRasterizationStateCreateInfo rs = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; rs.polygonMode = VK_POLYGON_MODE_FILL; rs.cullMode = VK_CULL_MODE_NONE; rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; rs.lineWidth = 1.0f; VkPipelineMultisampleStateCreateInfo ms = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineColorBlendAttachmentState cba = { 0 }; cba.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; if (blend) { cba.blendEnable = VK_TRUE; cba.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; cba.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; cba.colorBlendOp = VK_BLEND_OP_ADD; cba.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; cba.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; cba.alphaBlendOp = VK_BLEND_OP_ADD; } VkPipelineColorBlendStateCreateInfo cb = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; cb.attachmentCount = 1; cb.pAttachments = &cba; VkDynamicState dyn[2] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo ds = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; ds.dynamicStateCount = 2; ds.pDynamicStates = dyn; VkGraphicsPipelineCreateInfo pi = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; pi.stageCount = 2; pi.pStages = stages; pi.pVertexInputState = &vi; pi.pInputAssemblyState = &ia; pi.pViewportState = &vp; pi.pRasterizationState = &rs; pi.pMultisampleState = &ms; pi.pColorBlendState = &cb; pi.pDynamicState = &ds; pi.layout = playout; pi.renderPass = rp; pi.subpass = 0; VkPipeline pipe = VK_NULL_HANDLE; vk_check(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pi, NULL, &pipe), "vkCreateGraphicsPipelines"); vkDestroyShaderModule(device, vs, NULL); vkDestroyShaderModule(device, fs, NULL); return pipe; } // Set the Vulkan pixel format for the source image static void jgrf_video_vk_refresh_pixfmt(void) { pixfmt_active = vidinfo->pixfmt; switch (vidinfo->pixfmt) { case JG_PIXFMT_XRGB8888: pixfmt.format = VK_FORMAT_B8G8R8A8_UNORM; pixfmt.size = sizeof(uint32_t); jgrf_log(JG_LOG_DBG, "Pixel format: B8G8R8A8_UNORM\n"); break; case JG_PIXFMT_XBGR8888: pixfmt.format = VK_FORMAT_R8G8B8A8_UNORM; pixfmt.size = sizeof(uint32_t); jgrf_log(JG_LOG_DBG, "Pixel format: R8G8B8A8_UNORM\n"); break; case JG_PIXFMT_RGBX5551: pixfmt.format = VK_FORMAT_R5G5B5A1_UNORM_PACK16; pixfmt.size = sizeof(uint16_t); jgrf_log(JG_LOG_DBG, "Pixel format: R5G5B5A1_UNORM_PACK16\n"); break; case JG_PIXFMT_RGB565: pixfmt.format = VK_FORMAT_R5G6B5_UNORM_PACK16; pixfmt.size = sizeof(uint16_t); jgrf_log(JG_LOG_DBG, "Pixel format: R5G6B5_UNORM_PACK16\n"); break; default: jgrf_log(JG_LOG_ERR, "Unknown pixel format, exiting...\n"); break; } } // Create the SDL Vulkan window void jgrf_video_vk_create(void) { // Vulkan has no API sub-modes; the setting just selects this backend SDL_WindowFlags windowflags = SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY; char title[128]; gdata = jgrf_gdata_ptr(); snprintf(title, sizeof(title), "%s", gdata->gamename); jgrf_log(JG_LOG_DBG, "SDL Video driver: %s\n", SDL_GetCurrentVideoDriver()); if (!strcmp(SDL_GetCurrentVideoDriver(), "kmsdrm")) { SDL_DisplayID disp_id = SDL_GetPrimaryDisplay(); const SDL_DisplayMode *dm = SDL_GetDesktopDisplayMode(disp_id); dimensions.ww = dm->w; dimensions.wh = dm->h; } else { dimensions.ww = (vidinfo->aspect * vidinfo->h * settings[VIDEO_SCALE].val) + 0.5; dimensions.wh = (vidinfo->h * settings[VIDEO_SCALE].val) + 0.5; } dimensions.rw = dimensions.ww; dimensions.rh = dimensions.wh; jgrf_log(JG_LOG_DBG, "Creating window with dimensions: %d x %d\n", dimensions.ww, dimensions.wh); window = SDL_CreateWindow(title, dimensions.ww, dimensions.wh, windowflags); if (!window) jgrf_log(JG_LOG_ERR, "Failed to create window: %s\n", SDL_GetError()); int x, y; SDL_GetWindowSizeInPixels(window, &x, &y); dimensions.dpiscale = (float)x / dimensions.ww; jgrf_video_icon_load(window); // Do post window creation Vulkan setup jgrf_video_vk_setup(); SDL_HideCursor(); if (settings[VIDEO_FULLSCREEN].val) SDL_SetWindowFullscreen(window, true); } // Create the Vulkan instance, asking SDL for the required surface extensions static void vk_instance_create(void) { Uint32 next = 0; const char* const *exts_sdl = SDL_Vulkan_GetInstanceExtensions(&next); // Make room for a portability extension on macOS const char *exts[16]; Uint32 n = 0; for (Uint32 i = 0; i < next && n < 15; ++i) exts[n++] = exts_sdl[i]; VkInstanceCreateFlags flags = 0; #ifdef __APPLE__ exts[n++] = "VK_KHR_portability_enumeration"; flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; #endif VkApplicationInfo app = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO }; app.pApplicationName = "jgrf"; app.applicationVersion = VK_MAKE_VERSION(1, 0, 0); app.pEngineName = "jgrf"; app.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo ci = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; ci.flags = flags; ci.pApplicationInfo = &app; ci.enabledExtensionCount = n; ci.ppEnabledExtensionNames = exts; vk_check(vkCreateInstance(&ci, NULL, &instance), "vkCreateInstance"); } // Pick a physical device and the graphics/present queue families static void vk_device_create(void) { uint32_t count = 0; vkEnumeratePhysicalDevices(instance, &count, NULL); if (!count) jgrf_log(JG_LOG_ERR, "Vulkan: no physical devices\n"); VkPhysicalDevice *devs = (VkPhysicalDevice*)calloc(count, sizeof(VkPhysicalDevice)); vkEnumeratePhysicalDevices(instance, &count, devs); // Prefer a discrete GPU, else take the first that has the queues we need int chosen = -1; for (uint32_t i = 0; i < count; ++i) { VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(devs[i], &props); if (chosen < 0) chosen = (int)i; if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { chosen = (int)i; break; } } physdev = devs[chosen]; free(devs); VkPhysicalDeviceProperties devprops; vkGetPhysicalDeviceProperties(physdev, &devprops); jgrf_log(JG_LOG_INF, "Video: Vulkan %u.%u.%u - %s\n", VK_VERSION_MAJOR(devprops.apiVersion), VK_VERSION_MINOR(devprops.apiVersion), VK_VERSION_PATCH(devprops.apiVersion), devprops.deviceName); vkGetPhysicalDeviceMemoryProperties(physdev, &memprops); uint32_t qcount = 0; vkGetPhysicalDeviceQueueFamilyProperties(physdev, &qcount, NULL); VkQueueFamilyProperties *qfp = (VkQueueFamilyProperties*)calloc(qcount, sizeof(*qfp)); vkGetPhysicalDeviceQueueFamilyProperties(physdev, &qcount, qfp); int have_g = 0, have_p = 0; for (uint32_t i = 0; i < qcount; ++i) { if (!have_g && (qfp[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) { qf_graphics = i; have_g = 1; } VkBool32 present = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(physdev, i, surface, &present); if (!have_p && present) { qf_present = i; have_p = 1; } } free(qfp); if (!have_g || !have_p) jgrf_log(JG_LOG_ERR, "Vulkan: no graphics/present queue\n"); float prio = 1.0f; VkDeviceQueueCreateInfo qci[2]; uint32_t qn = 0; VkDeviceQueueCreateInfo base = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; base.queueCount = 1; base.pQueuePriorities = &prio; base.queueFamilyIndex = qf_graphics; qci[qn++] = base; if (qf_present != qf_graphics) { base.queueFamilyIndex = qf_present; qci[qn++] = base; } const char *dexts[4]; uint32_t dn = 0; dexts[dn++] = VK_KHR_SWAPCHAIN_EXTENSION_NAME; #ifdef __APPLE__ dexts[dn++] = "VK_KHR_portability_subset"; #endif VkDeviceCreateInfo dci = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; dci.queueCreateInfoCount = qn; dci.pQueueCreateInfos = qci; dci.enabledExtensionCount = dn; dci.ppEnabledExtensionNames = dexts; vk_check(vkCreateDevice(physdev, &dci, NULL, &device), "vkCreateDevice"); vkGetDeviceQueue(device, qf_graphics, 0, &q_graphics); vkGetDeviceQueue(device, qf_present, 0, &q_present); } // Choose a surface format, preferring B8G8R8A8 UNORM static void vk_choose_surface_format(void) { uint32_t n = 0; vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, surface, &n, NULL); VkSurfaceFormatKHR *fmts = (VkSurfaceFormatKHR*)calloc(n, sizeof(*fmts)); vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, surface, &n, fmts); swap_format = fmts[0].format; for (uint32_t i = 0; i < n; ++i) { if (fmts[i].format == VK_FORMAT_B8G8R8A8_UNORM && fmts[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { swap_format = fmts[i].format; break; } } free(fmts); } // Create or recreate the swapchain and its framebuffers static void vk_swapchain_create(void) { VkSurfaceCapabilitiesKHR caps; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physdev, surface, &caps); int pw, ph; SDL_GetWindowSizeInPixels(window, &pw, &ph); swap_extent = caps.currentExtent; if (caps.currentExtent.width == 0xFFFFFFFFu) { swap_extent.width = (uint32_t)pw; swap_extent.height = (uint32_t)ph; } if (swap_extent.width < caps.minImageExtent.width) swap_extent.width = caps.minImageExtent.width; if (swap_extent.height < caps.minImageExtent.height) swap_extent.height = caps.minImageExtent.height; if (swap_extent.width > caps.maxImageExtent.width) swap_extent.width = caps.maxImageExtent.width; if (swap_extent.height > caps.maxImageExtent.height) swap_extent.height = caps.maxImageExtent.height; uint32_t want = caps.minImageCount + 1; if (caps.maxImageCount && want > caps.maxImageCount) want = caps.maxImageCount; // FIFO (vsync on) by default; IMMEDIATE for benchmark mode if available VkPresentModeKHR present = VK_PRESENT_MODE_FIFO_KHR; if (bmark) { uint32_t pn = 0; vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, surface, &pn, NULL); VkPresentModeKHR *pm = (VkPresentModeKHR*)calloc(pn, sizeof(*pm)); vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, surface, &pn, pm); for (uint32_t i = 0; i < pn; ++i) if (pm[i] == VK_PRESENT_MODE_IMMEDIATE_KHR) present = VK_PRESENT_MODE_IMMEDIATE_KHR; free(pm); } VkSwapchainCreateInfoKHR ci = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; ci.surface = surface; ci.minImageCount = want; ci.imageFormat = swap_format; ci.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; ci.imageExtent = swap_extent; ci.imageArrayLayers = 1; ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; uint32_t qfs[2] = { qf_graphics, qf_present }; if (qf_graphics != qf_present) { ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT; ci.queueFamilyIndexCount = 2; ci.pQueueFamilyIndices = qfs; } else { ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } ci.preTransform = caps.currentTransform; ci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; ci.presentMode = present; ci.clipped = VK_TRUE; vk_check(vkCreateSwapchainKHR(device, &ci, NULL, &swapchain), "vkCreateSwapchainKHR"); vkGetSwapchainImagesKHR(device, swapchain, &swap_count, NULL); if (swap_count > VK_SWAP_MAX) swap_count = VK_SWAP_MAX; vkGetSwapchainImagesKHR(device, swapchain, &swap_count, swap_images); for (uint32_t i = 0; i < swap_count; ++i) { VkImageViewCreateInfo vi = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; vi.image = swap_images[i]; vi.viewType = VK_IMAGE_VIEW_TYPE_2D; vi.format = swap_format; vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; vi.subresourceRange.levelCount = 1; vi.subresourceRange.layerCount = 1; vk_check(vkCreateImageView(device, &vi, NULL, &swap_views[i]), "vkCreateImageView(swap)"); VkFramebufferCreateInfo fi = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; fi.renderPass = rp_screen; fi.attachmentCount = 1; fi.pAttachments = &swap_views[i]; fi.width = swap_extent.width; fi.height = swap_extent.height; fi.layers = 1; vk_check(vkCreateFramebuffer(device, &fi, NULL, &swap_fbos[i]), "vkCreateFramebuffer(swap)"); } } static void vk_swapchain_destroy(void) { for (uint32_t i = 0; i < swap_count; ++i) { if (swap_fbos[i]) vkDestroyFramebuffer(device, swap_fbos[i], NULL); if (swap_views[i]) vkDestroyImageView(device, swap_views[i], NULL); swap_fbos[i] = VK_NULL_HANDLE; swap_views[i] = VK_NULL_HANDLE; } if (swapchain) vkDestroySwapchainKHR(device, swapchain, NULL); swapchain = VK_NULL_HANDLE; swap_count = 0; } // Create the offscreen and onscreen render passes static void vk_renderpasses_create(void) { // Offscreen: clear, render, leave it ready to be sampled by pass 1 { VkAttachmentDescription a = { 0 }; a.format = VK_FMT_OFFSCREEN; a.samples = VK_SAMPLE_COUNT_1_BIT; a.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; a.storeOp = VK_ATTACHMENT_STORE_OP_STORE; a.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; a.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; a.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; a.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkAttachmentReference ref = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; VkSubpassDescription sp = { 0 }; sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; sp.colorAttachmentCount = 1; sp.pColorAttachments = &ref; VkSubpassDependency dep = { 0 }; dep.srcSubpass = VK_SUBPASS_EXTERNAL; dep.dstSubpass = 0; dep.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dep.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo ci = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; ci.attachmentCount = 1; ci.pAttachments = &a; ci.subpassCount = 1; ci.pSubpasses = &sp; ci.dependencyCount = 1; ci.pDependencies = &dep; vk_check(vkCreateRenderPass(device, &ci, NULL, &rp_offscreen), "vkCreateRenderPass(offscreen)"); } // Onscreen: clear the swapchain image, draw post-process then OSD, present { VkAttachmentDescription a = { 0 }; a.format = swap_format; a.samples = VK_SAMPLE_COUNT_1_BIT; a.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; a.storeOp = VK_ATTACHMENT_STORE_OP_STORE; a.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; a.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; a.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; a.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference ref = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; VkSubpassDescription sp = { 0 }; sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; sp.colorAttachmentCount = 1; sp.pColorAttachments = &ref; VkSubpassDependency dep = { 0 }; dep.srcSubpass = VK_SUBPASS_EXTERNAL; dep.dstSubpass = 0; dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dep.srcAccessMask = 0; dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo ci = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; ci.attachmentCount = 1; ci.pAttachments = &a; ci.subpassCount = 1; ci.pSubpasses = &sp; ci.dependencyCount = 1; ci.pDependencies = &dep; vk_check(vkCreateRenderPass(device, &ci, NULL, &rp_screen), "vkCreateRenderPass(screen)"); } } // Create or recreate the offscreen target and framebuffer at w x h static void vk_offscreen_create(void) { if (fbo_offscreen) { vkDestroyFramebuffer(device, fbo_offscreen, NULL); fbo_offscreen = VK_NULL_HANDLE; } if (tex_offscreen.image) vk_tex_destroy(&tex_offscreen); vk_tex_create(&tex_offscreen, vidinfo->w, vidinfo->h, VK_FMT_OFFSCREEN, 1); VkFramebufferCreateInfo fi = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; fi.renderPass = rp_offscreen; fi.attachmentCount = 1; fi.pAttachments = &tex_offscreen.view; fi.width = vidinfo->w; fi.height = vidinfo->h; fi.layers = 1; vk_check(vkCreateFramebuffer(device, &fi, NULL, &fbo_offscreen), "vkCreateFramebuffer(offscreen)"); if (ds_offscreen) { int fs, fd; (void)vk_shader_select(&fs, &fd); vk_ds_write(ds_offscreen, tex_offscreen.view, fd ? smp_linear : smp_nearest); } } // Create or recreate the source image at wmax x hmax static void vk_source_create(void) { if (tex_source.image) vk_tex_destroy(&tex_source); if (stage_source.buffer) vk_buffer_destroy(&stage_source); vk_tex_create(&tex_source, vidinfo->wmax, vidinfo->hmax, pixfmt.format, 0); vk_buffer_create(&stage_source, (VkDeviceSize)vidinfo->wmax * vidinfo->hmax * pixfmt.size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); if (ds_source) { int fs, fd; (void)vk_shader_select(&fs, &fd); vk_ds_write(ds_source, tex_source.view, fs ? smp_linear : smp_nearest); } // Transition to shader-read so the first frame's sampler is valid VkCommandBuffer cb = vk_cmd_begin(); vk_transition(cb, &tex_source, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, VK_ACCESS_SHADER_READ_BIT); vk_cmd_end(cb); } // Create samplers, descriptor layout/pool/sets, and pipeline layout static void vk_descriptors_create(void) { VkSamplerCreateInfo si = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; si.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; si.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; si.minFilter = si.magFilter = VK_FILTER_NEAREST; vk_check(vkCreateSampler(device, &si, NULL, &smp_nearest), "vkCreateSampler(nearest)"); si.minFilter = si.magFilter = VK_FILTER_LINEAR; vk_check(vkCreateSampler(device, &si, NULL, &smp_linear), "vkCreateSampler(linear)"); VkDescriptorSetLayoutBinding b = { 0 }; b.binding = 0; b.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; b.descriptorCount = 1; b.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutCreateInfo li = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; li.bindingCount = 1; li.pBindings = &b; vk_check(vkCreateDescriptorSetLayout(device, &li, NULL, &dsl), "vkCreateDescriptorSetLayout"); VkDescriptorPoolSize ps = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3 }; VkDescriptorPoolCreateInfo pci = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; pci.maxSets = 3; pci.poolSizeCount = 1; pci.pPoolSizes = &ps; vk_check(vkCreateDescriptorPool(device, &pci, NULL, &dpool), "vkCreateDescriptorPool"); VkDescriptorSetLayout layouts[3] = { dsl, dsl, dsl }; VkDescriptorSet sets[3]; VkDescriptorSetAllocateInfo dai = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; dai.descriptorPool = dpool; dai.descriptorSetCount = 3; dai.pSetLayouts = layouts; vk_check(vkAllocateDescriptorSets(device, &dai, sets), "vkAllocateDescriptorSets"); ds_source = sets[0]; ds_offscreen = sets[1]; ds_osd = sets[2]; VkPushConstantRange pcr = { 0 }; pcr.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; pcr.offset = 0; pcr.size = sizeof(vk_pushconst_t); VkPipelineLayoutCreateInfo pli = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; pli.setLayoutCount = 1; pli.pSetLayouts = &dsl; pli.pushConstantRangeCount = 1; pli.pPushConstantRanges = &pcr; vk_check(vkCreatePipelineLayout(device, &pli, NULL, &playout), "vkCreatePipelineLayout"); } // Build the three pipelines (pass0, pass1, OSD) static void vk_pipelines_create(void) { int filter_src, filter_dst; const char *frag = vk_shader_select(&filter_src, &filter_dst); shader_active = settings[VIDEO_SHADER].val; // Update per-pass input filters vk_ds_write(ds_source, tex_source.view, filter_src ? smp_linear : smp_nearest); vk_ds_write(ds_offscreen, tex_offscreen.view, filter_dst ? smp_linear : smp_nearest); pipe_pass0 = vk_pipeline_build("default.vert", "default.frag", rp_offscreen, 0); pipe_pass1 = vk_pipeline_build("default.vert", frag, rp_screen, 0); pipe_osd = vk_pipeline_build("default.vert", "osd.frag", rp_screen, 1); } static void vk_pipelines_destroy(void) { if (pipe_pass0) { vkDestroyPipeline(device, pipe_pass0, NULL); pipe_pass0 = VK_NULL_HANDLE; } if (pipe_pass1) { vkDestroyPipeline(device, pipe_pass1, NULL); pipe_pass1 = VK_NULL_HANDLE; } if (pipe_osd) { vkDestroyPipeline(device, pipe_osd, NULL); pipe_osd = VK_NULL_HANDLE; } } // Set up Vulkan static void jgrf_video_vk_setup(void) { vk_instance_create(); if (!SDL_Vulkan_CreateSurface(window, instance, NULL, &surface)) jgrf_log(JG_LOG_ERR, "SDL_Vulkan_CreateSurface: %s\n", SDL_GetError()); vk_device_create(); vk_choose_surface_format(); // Command pool + the single reused frame command buffer VkCommandPoolCreateInfo cpi = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; cpi.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; cpi.queueFamilyIndex = qf_graphics; vk_check(vkCreateCommandPool(device, &cpi, NULL, &cmdpool), "vkCreateCommandPool"); VkCommandBufferAllocateInfo cbi = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; cbi.commandPool = cmdpool; cbi.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbi.commandBufferCount = 1; vkAllocateCommandBuffers(device, &cbi, &cmdbuf); VkSemaphoreCreateInfo semi = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; vkCreateSemaphore(device, &semi, NULL, &sem_acquire); vkCreateSemaphore(device, &semi, NULL, &sem_render); VkFenceCreateInfo fi = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; fi.flags = VK_FENCE_CREATE_SIGNALED_BIT; vkCreateFence(device, &fi, NULL, &fence_frame); vk_descriptors_create(); // OSD canvas image, staging buffer, and descriptor vk_tex_create(&tex_osd, JGRF_OSD_W, JGRF_OSD_H, VK_FORMAT_R8G8B8A8_UNORM, 0); vk_buffer_create(&stage_osd, (VkDeviceSize)JGRF_OSD_W * JGRF_OSD_H * 4, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); vk_ds_write(ds_osd, tex_osd.view, smp_nearest); { VkCommandBuffer cb = vk_cmd_begin(); vk_transition(cb, &tex_osd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, VK_ACCESS_SHADER_READ_BIT); vk_cmd_end(cb); } // Vertex buffers (host visible so refresh()/OSD can patch texcoords) vk_buffer_create(&vbo_pass0, sizeof(verts_pass0), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); memcpy(vbo_pass0.mapped, verts_pass0, sizeof(verts_pass0)); vk_buffer_create(&vbo_pass1, sizeof(verts_pass1), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); memcpy(vbo_pass1.mapped, verts_pass1, sizeof(verts_pass1)); vk_buffer_create(&vbo_osd, 4 * 4 * sizeof(float), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); // Create the source and offscreen images, render passes, and pipelines vk_source_create(); vk_renderpasses_create(); vk_offscreen_create(); vk_swapchain_create(); vk_pipelines_create(); jgrf_video_vk_resize(); jgrf_video_vk_refresh(); } // Initialize video buffer int jgrf_video_vk_init(void) { settings = jgrf_settings_ptr(); gdata = jgrf_gdata_ptr(); if (!vidinfo) jgrf_log(JG_LOG_ERR, "Vulkan: video info unset\n"); if (!(gdata->hints & JG_HINT_VIDEO_INTERNAL)) { videobuf = (void*)calloc(vidinfo->wmax * vidinfo->hmax, pixfmt.size); vidinfo->buf = videobuf; } if (gdata->hints & JG_HINT_VIDEO_PRESCALED) settings[VIDEO_SCALE].val = 1; jgrf_osd_init(); return 1; } // Deinitialize Vulkan video void jgrf_video_vk_deinit(void) { if (device) vkDeviceWaitIdle(device); jgrf_osd_deinit(); vk_pipelines_destroy(); vk_buffer_destroy(&vbo_pass0); vk_buffer_destroy(&vbo_pass1); vk_buffer_destroy(&vbo_osd); vk_buffer_destroy(&stage_source); vk_buffer_destroy(&stage_osd); if (readback.buffer) vk_buffer_destroy(&readback); if (fbo_offscreen) vkDestroyFramebuffer(device, fbo_offscreen, NULL); vk_tex_destroy(&tex_offscreen); vk_tex_destroy(&tex_source); vk_tex_destroy(&tex_osd); vk_swapchain_destroy(); if (rp_offscreen) vkDestroyRenderPass(device, rp_offscreen, NULL); if (rp_screen) vkDestroyRenderPass(device, rp_screen, NULL); if (playout) vkDestroyPipelineLayout(device, playout, NULL); if (dpool) vkDestroyDescriptorPool(device, dpool, NULL); if (dsl) vkDestroyDescriptorSetLayout(device, dsl, NULL); if (smp_nearest) vkDestroySampler(device, smp_nearest, NULL); if (smp_linear) vkDestroySampler(device, smp_linear, NULL); if (sem_acquire) vkDestroySemaphore(device, sem_acquire, NULL); if (sem_render) vkDestroySemaphore(device, sem_render, NULL); if (fence_frame) vkDestroyFence(device, fence_frame, NULL); if (cmdpool) vkDestroyCommandPool(device, cmdpool, NULL); if (device) vkDestroyDevice(device, NULL); if (surface) vkDestroySurfaceKHR(instance, surface, NULL); if (instance) vkDestroyInstance(instance, NULL); if (!(gdata->hints & JG_HINT_VIDEO_INTERNAL)) { if (videobuf) free(videobuf); } if (cursor) SDL_DestroyCursor(cursor); SDL_DestroyWindow(window); } // Toggle between fullscreen and windowed void jgrf_video_vk_fullscreen(void) { settings[VIDEO_FULLSCREEN].val ^= 1; SDL_SetWindowFullscreen(window, settings[VIDEO_FULLSCREEN].val ? true : false); jgrf_video_vk_resize(); } // Refresh any video settings that may have changed static void jgrf_video_vk_refresh(void) { if (vidinfo->aspect != aspect_prev) { aspect_prev = vidinfo->aspect; jgrf_video_vk_resize(); } float top = (float)vidinfo->y / vidinfo->hmax; float bottom = 1.0f + top - ((vidinfo->hmax - (float)vidinfo->h) / vidinfo->hmax); float left = (float)vidinfo->x / vidinfo->wmax; float right = 1.0f + left - ((vidinfo->wmax - (float)vidinfo->w) / vidinfo->wmax); int refmt = (pixfmt_active != vidinfo->pixfmt); if (refmt) jgrf_video_vk_refresh_pixfmt(); // Rebuild source/offscreen images if the resolution or pixel format changed // verts_pass0 layout per vertex: x, y, u, v. v0=LB v1=LT v2=RB v3=RT. int changed = (verts_pass0[3] != top || verts_pass0[7] != bottom || verts_pass0[2] != left || verts_pass0[10] != right); int resized = ((uint32_t)vidinfo->wmax != tex_source.w || (uint32_t)vidinfo->hmax != tex_source.h || (uint32_t)vidinfo->w != tex_offscreen.w || (uint32_t)vidinfo->h != tex_offscreen.h); if (changed || refmt || resized) { verts_pass0[2] = left; verts_pass0[3] = top; // LB verts_pass0[6] = left; verts_pass0[7] = bottom; // LT verts_pass0[10] = right; verts_pass0[11] = top; // RB verts_pass0[14] = right; verts_pass0[15] = bottom; // RT memcpy(vbo_pass0.mapped, verts_pass0, sizeof(verts_pass0)); if (refmt || resized) { vkDeviceWaitIdle(device); vk_source_create(); vk_offscreen_create(); } } // Update push-constant sizes used by the post-process pass pushconst.sourceSize[0] = (float)vidinfo->w; pushconst.sourceSize[1] = (float)vidinfo->h; pushconst.sourceSize[2] = 1.0f / (float)vidinfo->w; pushconst.sourceSize[3] = 1.0f / (float)vidinfo->h; pushconst.targetSize[0] = dimensions.rw; pushconst.targetSize[1] = dimensions.rh; pushconst.targetSize[2] = 1.0f / dimensions.rw; pushconst.targetSize[3] = 1.0f / dimensions.rh; // Settings for CRTea int masktype = 0, maskstr = 0, scanstr = 0, sharpness = 0, curve = settings[VIDEO_CRTEA_CURVE].val, corner = settings[VIDEO_CRTEA_CORNER].val, tcurve = settings[VIDEO_CRTEA_TCURVE].val; switch (settings[VIDEO_CRTEA_MODE].val) { default: case 0: masktype = 0; maskstr = 0; scanstr = 6; sharpness = 3; break; case 1: masktype = 1; maskstr = 5; scanstr = 6; sharpness = 7; break; case 2: masktype = 2; maskstr = 5; scanstr = 6; sharpness = 7; break; case 3: masktype = 3; maskstr = 4; scanstr = 6; sharpness = 3; break; case 4: masktype = settings[VIDEO_CRTEA_MASKTYPE].val; maskstr = settings[VIDEO_CRTEA_MASKSTR].val; scanstr = settings[VIDEO_CRTEA_SCANSTR].val; sharpness = settings[VIDEO_CRTEA_SHARPNESS].val; break; } pushconst.masktype = masktype; pushconst.maskstr = maskstr / 10.0f; pushconst.scanstr = scanstr / 10.0f; pushconst.sharpness = (float)sharpness; pushconst.curve = curve / 100.0f; pushconst.corner = corner ? (float)corner : -3.0f; pushconst.tcurve = tcurve / 10.0f; } // Upload the visible region of the game framebuffer into the source image static void vk_upload_source(VkCommandBuffer cb) { uint32_t rw = (uint32_t)(vidinfo->w + vidinfo->x); uint32_t rh = (uint32_t)(vidinfo->h + vidinfo->y); size_t dstpitch = (size_t)rw * pixfmt.size; size_t srcpitch = (size_t)vidinfo->p * pixfmt.size; uint8_t *src = (uint8_t*)vidinfo->buf; uint8_t *dst = (uint8_t*)stage_source.mapped; for (uint32_t r = 0; r < rh; ++r) memcpy(dst + r * dstpitch, src + r * srcpitch, dstpitch); vk_transition(cb, &tex_source, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); VkBufferImageCopy region = { 0 }; region.bufferRowLength = rw; // tightly packed region.bufferImageHeight = rh; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.layerCount = 1; region.imageExtent.width = rw; region.imageExtent.height = rh; region.imageExtent.depth = 1; vkCmdCopyBufferToImage(cb, stage_source.buffer, tex_source.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); vk_transition(cb, &tex_source, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); } // Compute the OSD destination UVs and scale static void vk_osd_compute_rects(float *u0, float *v0, float *u1, float *v1, int *vis_w_out, int *vis_h_out) { float rw = dimensions.rw; float rh = dimensions.rh; int scale_x = ((int)rw + 128) / 256; int scale_y = ((int)rh + 112) / 224; int scale = (scale_x < scale_y) ? scale_x : scale_y; if (scale < 1) scale = 1; int vw = ((int)rw + scale - 1) / scale; if (vw > JGRF_OSD_W) vw = JGRF_OSD_W; int vh = ((int)rh + scale - 1) / scale; if (vh > JGRF_OSD_H) vh = JGRF_OSD_H; *u0 = 0.0f; *v0 = 0.0f; *u1 = rw / ((float)scale * (float)JGRF_OSD_W); *v1 = rh / ((float)scale * (float)JGRF_OSD_H); *vis_w_out = vw; *vis_h_out = vh; } // Prepare the OSD for drawing; upload canvas if dirty. Returns 1 if active. static int vk_osd_prepare(VkCommandBuffer cb) { int rw = (int)dimensions.rw; int rh = (int)dimensions.rh; if (rw < 1 || rh < 1) return 0; int vw, vh; float u0, v0, u1, v1; vk_osd_compute_rects(&u0, &v0, &u1, &v1, &vw, &vh); jgrf_osd_set_visible(vw, vh); jgrf_osd_step(); int active = 0, dirty = 0; const void *pixels = jgrf_osd_composite(&active, &dirty); if (!active) return 0; if (dirty && pixels) { memcpy(stage_osd.mapped, pixels, (size_t)JGRF_OSD_W * JGRF_OSD_H * 4); vk_transition(cb, &tex_osd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); VkBufferImageCopy region = { 0 }; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.layerCount = 1; region.imageExtent.width = JGRF_OSD_W; region.imageExtent.height = JGRF_OSD_H; region.imageExtent.depth = 1; vkCmdCopyBufferToImage(cb, stage_osd.buffer, tex_osd.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); vk_transition(cb, &tex_osd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); } // OSD quad: fills letterboxed area, UVs select the canvas (top-origin) float verts[] = { -1.0f, -1.0f, u0, v0, -1.0f, 1.0f, u0, v1, 1.0f, -1.0f, u1, v0, 1.0f, 1.0f, u1, v1, }; memcpy(vbo_osd.mapped, verts, sizeof(verts)); return 1; } // Set a dynamic viewport + scissor on the command buffer static void vk_set_viewport(VkCommandBuffer cb, float x, float y, float w, float h) { VkViewport vp = { x, y, w, h, 0.0f, 1.0f }; VkRect2D sc = { { (int32_t)x, (int32_t)y }, { (uint32_t)w, (uint32_t)h } }; vkCmdSetViewport(cb, 0, 1, &vp); vkCmdSetScissor(cb, 0, 1, &sc); } // Render the scene void jgrf_video_vk_render(int render) { vkWaitForFences(device, 1, &fence_frame, VK_TRUE, UINT64_MAX); VkResult acq = vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, sem_acquire, VK_NULL_HANDLE, &swap_index); if (acq == VK_ERROR_OUT_OF_DATE_KHR) { jgrf_video_vk_recreate_swapchain(); return; } vkResetFences(device, 1, &fence_frame); jgrf_video_vk_refresh(); VkCommandBuffer cb = cmdbuf; vkResetCommandBuffer(cb, 0); VkCommandBufferBeginInfo bi = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; vkBeginCommandBuffer(cb, &bi); // Per-frame uploads happen outside any render pass if (render) vk_upload_source(cb); int osd_active = vk_osd_prepare(cb); VkDeviceSize voff = 0; VkClearValue clear = { { { 0.0f, 0.0f, 0.0f, 1.0f } } }; // Pass 0: source -> offscreen (clip), viewport is the full offscreen area VkRenderPassBeginInfo rp0 = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; rp0.renderPass = rp_offscreen; rp0.framebuffer = fbo_offscreen; rp0.renderArea.extent.width = vidinfo->w; rp0.renderArea.extent.height = vidinfo->h; rp0.clearValueCount = 1; rp0.pClearValues = &clear; vkCmdBeginRenderPass(cb, &rp0, VK_SUBPASS_CONTENTS_INLINE); vk_set_viewport(cb, 0, 0, (float)vidinfo->w, (float)vidinfo->h); vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe_pass0); vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, playout, 0, 1, &ds_source, 0, NULL); vkCmdBindVertexBuffers(cb, 0, 1, &vbo_pass0.buffer, &voff); vkCmdDraw(cb, 4, 1, 0, 0); vkCmdEndRenderPass(cb); // Pass 1 + OSD: offscreen -> swapchain, viewport is the letterboxed area VkRenderPassBeginInfo rp1 = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; rp1.renderPass = rp_screen; rp1.framebuffer = swap_fbos[swap_index]; rp1.renderArea.extent = swap_extent; rp1.clearValueCount = 1; rp1.pClearValues = &clear; vkCmdBeginRenderPass(cb, &rp1, VK_SUBPASS_CONTENTS_INLINE); vk_set_viewport(cb, dimensions.xo, dimensions.yo, dimensions.rw, dimensions.rh); vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe_pass1); vkCmdPushConstants(cb, playout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pushconst), &pushconst); vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, playout, 0, 1, &ds_offscreen, 0, NULL); vkCmdBindVertexBuffers(cb, 0, 1, &vbo_pass1.buffer, &voff); vkCmdDraw(cb, 4, 1, 0, 0); if (osd_active) { vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe_osd); vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, playout, 0, 1, &ds_osd, 0, NULL); vkCmdBindVertexBuffers(cb, 0, 1, &vbo_osd.buffer, &voff); vkCmdDraw(cb, 4, 1, 0, 0); } vkCmdEndRenderPass(cb); vkEndCommandBuffer(cb); VkPipelineStageFlags wait = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkSubmitInfo si = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO }; si.waitSemaphoreCount = 1; si.pWaitSemaphores = &sem_acquire; si.pWaitDstStageMask = &wait; si.commandBufferCount = 1; si.pCommandBuffers = &cb; si.signalSemaphoreCount = 1; si.pSignalSemaphores = &sem_render; vk_check(vkQueueSubmit(q_graphics, 1, &si, fence_frame), "vkQueueSubmit"); } // Present the rendered image void jgrf_video_vk_swapbuffers(void) { VkPresentInfoKHR pi = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; pi.waitSemaphoreCount = 1; pi.pWaitSemaphores = &sem_render; pi.swapchainCount = 1; pi.pSwapchains = &swapchain; pi.pImageIndices = &swap_index; VkResult r = vkQueuePresentKHR(q_present, &pi); if (r == VK_ERROR_OUT_OF_DATE_KHR || r == VK_SUBOPTIMAL_KHR) jgrf_video_vk_recreate_swapchain(); } // Recreate the swapchain and its framebuffers after a resize / out-of-date static void jgrf_video_vk_recreate_swapchain(void) { vkDeviceWaitIdle(device); vk_swapchain_destroy(); vk_swapchain_create(); SDL_GetWindowSizeInPixels(window, &win_pw, &win_ph); jgrf_video_vk_resize(); } // Handle viewport resizing void jgrf_video_vk_resize(void) { // Recreate the swapchain when the window pixel size changes int pw, ph; SDL_GetWindowSizeInPixels(window, &pw, &ph); if (swapchain != VK_NULL_HANDLE && win_pw && (pw != win_pw || ph != win_ph)) { vkDeviceWaitIdle(device); vk_swapchain_destroy(); vk_swapchain_create(); // refreshes swap_extent and swap_fbos } win_pw = pw; win_ph = ph; // Letterbox to the swapchain extent dimensions.ww = (int)swap_extent.width; dimensions.wh = (int)swap_extent.height; dimensions.rw = dimensions.ww; dimensions.rh = dimensions.wh; if (dimensions.rh * vidinfo->aspect > dimensions.rw) dimensions.rh = dimensions.rw / vidinfo->aspect + 0.5; else if (dimensions.rw / vidinfo->aspect > dimensions.rh) dimensions.rw = dimensions.rh * vidinfo->aspect + 0.5; dimensions.xo = (dimensions.ww - dimensions.rw) / 2; dimensions.yo = (dimensions.wh - dimensions.rh) / 2; pushconst.targetSize[0] = dimensions.rw; pushconst.targetSize[1] = dimensions.rh; pushconst.targetSize[2] = 1.0f / dimensions.rw; pushconst.targetSize[3] = 1.0f / dimensions.rh; SDL_DisplayID displayid = SDL_GetDisplayForWindow(window); const SDL_DisplayMode *dm = SDL_GetCurrentDisplayMode(displayid); if (dm) jgrf_set_screenfps((int)(dm->refresh_rate + 0.5)); } // Retrieve scale parameters for pointing device input void jgrf_video_vk_get_scale_params(float *xscale, float *yscale, float *xo, float *yo) { *xscale = dimensions.rw / (vidinfo->aspect * vidinfo->h) / dimensions.dpiscale; *yscale = dimensions.rh / vidinfo->h / dimensions.dpiscale; *xo = dimensions.xo / dimensions.dpiscale; *yo = dimensions.yo / dimensions.dpiscale; } // Retrieve video information jg_videoinfo_t* jgrf_video_vk_get_info(void) { return vidinfo; } // Set the cursor void jgrf_video_vk_set_cursor(int ctype) { cursor = SDL_CreateSystemCursor(ctype); SDL_ShowCursor(); SDL_SetCursor(cursor); } // Pass the core's video info pointer into the frontend void jgrf_video_vk_set_info(jg_videoinfo_t *ptr) { vidinfo = ptr; jgrf_video_vk_refresh_pixfmt(); } // Dump the pixels rendered to the swapchain for a screenshot void *jgrf_video_vk_get_pixels(int *rw, int *rh) { // Clear OSD content before grabbing for (int i = 0; i < JGRF_OSD_NUM_TEXT_SLOTS; ++i) jgrf_osd_text_clear(i); jgrf_osd_shade_set(0); jgrf_osd_fx_clear(); // Render one clean frame and present it jgrf_video_vk_render(0); jgrf_video_vk_swapbuffers(); vkWaitForFences(device, 1, &fence_frame, VK_TRUE, UINT64_MAX); int w = (int)dimensions.rw; int h = (int)dimensions.rh; int xo = (int)dimensions.xo; int yo = (int)dimensions.yo; VkDeviceSize rbsize = (VkDeviceSize)w * h * 4; if (readback.buffer && readback.size < rbsize) vk_buffer_destroy(&readback); if (!readback.buffer) vk_buffer_create(&readback, rbsize, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); VkImage img = swap_images[swap_index]; VkCommandBuffer cb = vk_cmd_begin(); VkImageMemoryBarrier b = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; b.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; b.image = img; b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; b.subresourceRange.levelCount = 1; b.subresourceRange.layerCount = 1; b.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; b.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &b); VkBufferImageCopy region = { 0 }; region.bufferRowLength = w; region.bufferImageHeight = h; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.layerCount = 1; region.imageOffset.x = xo; region.imageOffset.y = yo; region.imageExtent.width = w; region.imageExtent.height = h; region.imageExtent.depth = 1; vkCmdCopyImageToBuffer(cb, img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback.buffer, 1, ®ion); // Restore present layout b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; b.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; b.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &b); vk_cmd_end(cb); uint8_t *pixels = (uint8_t*)calloc((size_t)w * h, sizeof(uint32_t)); memcpy(pixels, readback.mapped, rbsize); // Swap BGRA to RGBA if needed for the screenshot encoder if (swap_format == VK_FORMAT_B8G8R8A8_UNORM || swap_format == VK_FORMAT_B8G8R8A8_SRGB) { for (size_t i = 0; i < (size_t)w * h; ++i) { uint8_t t = pixels[i * 4 + 0]; pixels[i * 4 + 0] = pixels[i * 4 + 2]; pixels[i * 4 + 2] = t; } } *rw = w; *rh = h; return pixels; } // Set a text message to be output for a number of frames void jgrf_video_vk_text(int index, int frames, const char *msg) { int anchor; int x = 0, y = 0; int f = frames; switch (index) { case JGRF_OSD_TEXT_FRONTEND: anchor = JGRF_OSD_ANCHOR_BL; x = 0; y = 0; break; case JGRF_OSD_TEXT_CORE: anchor = JGRF_OSD_ANCHOR_BR; x = 0; y = 0; break; case JGRF_OSD_TEXT_MENU: anchor = JGRF_OSD_ANCHOR_TL; x = 8; y = 8; f = (frames > 0) ? -1 : 0; break; case JGRF_OSD_TEXT_AUX: default: anchor = JGRF_OSD_ANCHOR_TR; x = 0; y = 0; break; } if (f == 0 || !msg || msg[0] == '\0') { jgrf_osd_text_clear(index); return; } jgrf_osd_text(index, f, anchor, x, y, 0, msg); } // Rebuild pipelines and re-letterbox after a settings change void jgrf_video_vk_rehash(void) { vkDeviceWaitIdle(device); // Rebuild pipelines in case the post-process shader changed vk_pipelines_destroy(); vk_pipelines_create(); SDL_SetWindowFullscreen(window, settings[VIDEO_FULLSCREEN].val ? true : false); jgrf_video_vk_resize(); jgrf_osd_rehash(); } jgrf-2.0.1-c080341ab2bc72c0574ffb5dc36203096b5465cb/src/video_vk.h000066400000000000000000000041071523150047600225450ustar00rootroot00000000000000/* Copyright (c) 2020-2026 Rupert Carmichael All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef VIDEO_VK_H #define VIDEO_VK_H void jgrf_video_vk_create(void); int jgrf_video_vk_init(void); void jgrf_video_vk_deinit(void); void jgrf_video_vk_fullscreen(void); void jgrf_video_vk_render(int); void jgrf_video_vk_resize(void); void jgrf_video_vk_get_scale_params(float*, float*, float*, float*); void jgrf_video_vk_set_cursor(int); jg_videoinfo_t* jgrf_video_vk_get_info(void); void jgrf_video_vk_set_info(jg_videoinfo_t*); void jgrf_video_vk_swapbuffers(void); void jgrf_video_vk_text(int, int, const char*); void *jgrf_video_vk_get_pixels(int*, int*); void jgrf_video_vk_rehash(void); #endif