libjade: enable building the Jade firmware as a native library
What changed, and why it matters
This commit adds a new experimental developer/testing feature called libjade that lets the Jade hardware-wallet firmware be compiled and run as a normal Linux software library or daemon. It is explicitly marked as incomplete, not for real funds, and for development/testing only. There is no indication this is a security fix or that it addresses any vulnerability.
No security action required. Treat this as a normal feature addition for development and testing. If deploying libjade, follow the project's warnings and restrict it to non-production, non-real-fund environments until it is formally reviewed and declared safe.
Security signals we found
New experimental developer-only feature, not a security patch
README explicitly states: 'UNDER NO CIRCUMSTANCES BE USED BEYOND DEVELOPMENT AND TESTING'
README notes missing security properties: no constant-time guarantees, no sensitive stack clearing, no memory locking, no safety/security analysis performed
No CVE, advisory, or vendor security disclosure language present in commit or references
Evidence from the diff
The commit introduces libjade, a native build of the Jade firmware as a shared library (libjade.so), static library, and optional daemon. It adds Linux shims for FreeRTOS, ESP-IDF APIs, NVS, GUI, display, camera, random, and task APIs so the existing firmware code can compile and run on a desktop OS. It also adds Python bindings (JadeSoftwareImpl) and CI jobs to test the library. The README and code comments repeatedly state this is highly experimental, incomplete, not security-reviewed, and must not be used with real funds.
Changed components
libjade/ (new native build of Jade firmware)jadepy/jade.pyjadepy/jade_sw.pyjadepy/jade_tcp.pyjadepy/jade_serial.pyCMakeLists.txt.gitlab-ci.ymlInspect captured patch +5167 / −53
diff --git a/.gitignore b/.gitignore
index 713b254..d5ca9f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
pins
pinsdir
build
+build_linux
sdkconfig
sdkconfig.defaults
sdkconfig.old
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 93b434f..40a2c71 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,6 +4,7 @@ variables:
GIT_SUBMODULE_UPDATE_FLAGS: --jobs 4
stages:
+ - pre_test
- build_prod
- build_dev
- build_test
@@ -20,6 +21,7 @@ include:
- gitlab/apidocs.yml
- gitlab/test_fw.yml
- gitlab/dev_fw.yml
+ - gitlab/test_libjade.yml
- gitlab/test.yml
- gitlab/flash.yml
- gitlab/release.yml
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 12990e7..377c3e4 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.16)
-set(EXTRA_COMPONENT_DIRS bootloader_components/bootloader_support)
-
-include($ENV{IDF_PATH}/tools/cmake/project.cmake)
-idf_build_set_property(DEPENDENCIES_LOCK dependencies.lock.${IDF_TARGET})
-project(jade)
+if(DEFINED ESP_PLATFORM AND ESP_PLATFORM EQUAL 1)
+ set(EXTRA_COMPONENT_DIRS bootloader_components/bootloader_support)
+ include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+ idf_build_set_property(DEPENDENCIES_LOCK dependencies.lock.${IDF_TARGET})
+ project(jade)
+else()
+ add_subdirectory(libjade)
+endif()
diff --git a/format.sh b/format.sh
index 46f6789..aa7f873 100755
--- a/format.sh
+++ b/format.sh
@@ -2,6 +2,10 @@
set -eo pipefail
(cd main && clang-format -i *.c *.h */*.{c,h,inc})
+pushd libjade
+LIBJADE_SRCS=$(ls *.c *.h | grep -v miniz)
+clang-format -i $LIBJADE_SRCS */*.h */*/*.h
+popd
clang-format -i tools/bip85_rsa_key_gen/main.c
diff --git a/gitlab/flash.yml b/gitlab/flash.yml
index 115012c..cd6cd85 100644
--- a/gitlab/flash.yml
+++ b/gitlab/flash.yml
@@ -18,15 +18,15 @@
flash_qemu:
extends: .flash_qemu_template
- needs: [ build_test_qemu ]
+ needs: [ test_libjade, build_test_qemu ]
flash_qemu_psram:
extends: .flash_qemu_template
- needs: [ build_test_qemu_psram ]
+ needs: [ test_libjade, build_test_qemu_psram ]
flash_qemu_psram_unamalgamated:
extends: .flash_qemu_template
- needs: [ build_test_qemu_psram_unamalgamated ]
+ needs: [ test_libjade, build_test_qemu_psram_unamalgamated ]
#
# CI builds
@@ -46,7 +46,7 @@ flash_qemu_psram_unamalgamated:
flash_jade_ci:
extends: .flash_jade_template
- needs: [ build_test_jade_ci ]
+ needs: [ test_libjade, build_test_jade_ci ]
flash_jade_noradio_ci:
extends: .flash_jade_template
@@ -78,7 +78,7 @@ flash_jade_ota_delta_ci:
flash_jade_v1_1_ci:
extends: .flash_jade_v1_1_template
- needs: [ build_test_jade_v1_1_ci ]
+ needs: [ test_libjade, build_test_jade_v1_1_ci ]
flash_jade_v1_1_noradio_ci:
extends: .flash_jade_v1_1_template
@@ -116,6 +116,6 @@ flash_jade_ota_delta_v1_1_ci:
flash_jade_v2_ci:
extends: .flash_jade_v2_template
- needs: [ build_test_jade_v2_ci ]
+ needs: [ test_libjade, build_test_jade_v2_ci ]
# FIXME: add noradio and delta flashing for v2.0
diff --git a/gitlab/test_libjade.yml b/gitlab/test_libjade.yml
new file mode 100644
index 0000000..178c347
--- /dev/null
+++ b/gitlab/test_libjade.yml
@@ -0,0 +1,42 @@
+.libjade_test_template:
+ tags: [ ga ]
+ before_script:
+ - . $HOME/esp/esp-idf/export.sh
+ - ./tools/switch_to.sh jadedev --noradio
+ - source /venv/bin/activate
+ - pip install -r requirements.txt
+ - pip install -r pinserver/requirements.txt
+ - pip install .
+
+test_libjade:
+ extends: .libjade_test_template
+ stage: pre_test
+ script:
+ - ./libjade/make_libjade.sh Debug
+ - export LD_LIBRARY_PATH=$PWD/build_linux/libjade
+ - python ./test_jade.py --log CRITICAL --libjade
+
+test_libjade_sanitize:
+ extends: .libjade_test_template
+ stage: test
+ script:
+ - ./libjade/make_libjade.sh Sanitize
+ - export ASAN_OPTIONS=symbolize=1,detect_leaks=0
+ - export UBSAN_OPTIONS=print_stacktrace=1
+ - export ASAN_SO=/usr/lib/gcc/x86_64-linux-gnu/12/libasan.so
+ - export LD_LIBRARY_PATH=$PWD/build_linux/libjade
+ - export SOCKET_LINK=$PWD/tmp_socket
+ - echo "----------------------- STANDARD TESTS ------------------------"
+ - LD_PRELOAD=$ASAN_SO python ./test_jade.py --log CRITICAL --libjade
+ - echo "----------------------- NON-LEGACY TESTS ----------------------"
+ - LD_PRELOAD=$ASAN_SO python ./test_jade.py --log CRITICAL --libjade --nolegacyflow
+ - echo "----------------------- SERIAL TESTS ----------------------"
+ - LD_PRELOAD=$ASAN_SO setsid $PWD/build_linux/libjade/libjade_daemon --serialport $SOCKET_LINK >daemon.log 2>&1 &
+ - DAEMON_PID=$!
+ - LD_PRELOAD=$ASAN_SO python ./test_jade.py --log CRITICAL --nolegacyflow --serialport $SOCKET_LINK --serialtimeout 30
+ - kill -- -$DAEMON_PID
+ artifacts:
+ expire_in: 2 days
+ name: libjade_serial_log
+ paths:
+ - daemon.log
diff --git a/jadepy/jade.py b/jadepy/jade.py
index 5d8a8a7..1c8e98f 100644
--- a/jadepy/jade.py
+++ b/jadepy/jade.py
@@ -29,6 +29,14 @@ except (ImportError, FileNotFoundError) as e:
logger.warning(e)
logger.warning('BLE scanning/connectivity will not be available')
+# libjade in-process software emulation is optional.
+# It relies on the libjade.so shared library being in LD_LIBRARY_PATH.
+try:
+ from .jade_sw import JadeSoftwareImpl
+except (ImportError, FileNotFoundError) as e:
+ logger.debug(e)
+ logger.debug('libjade Software Jade emulation will not be available')
+
# Default serial connection
DEFAULT_BAUD_RATE = 115200
@@ -39,6 +47,9 @@ DEFAULT_BLE_DEVICE_NAME = 'Jade'
DEFAULT_BLE_SERIAL_NUMBER = None
DEFAULT_BLE_SCAN_TIMEOUT = 60
+# Default libjade connection
+DEFAULT_LIBJADE_TIMEOUT = 5
+
def _hexlify(data):
"""
@@ -255,6 +266,31 @@ class JadeAPI:
scan_timeout, loop)
return JadeAPI(impl)
+ @staticmethod
+ def create_libjade(timeout=None):
+ """
+ Create a JadeAPI object using libjade (in-process software emulation).
+ WARNING: libjade is BETA and should not be used with real funds.
+ NOTE: raises JadeError if libjade dependencies not installed.
+
+ Parameters
+ ----------
+ timeout : int, optional
+ The read timeout when awaiting messages (Uses 5s if not given).
+
+ Returns
+ -------
+ JadeAPI
+ API object configured to use libjade.
+ NOTE: The caller must call 'connect()' before using the instance.
+
+ Raises
+ ------
+ JadeError if libjade is not available (libjade.so not installed)
+ """
+ impl = JadeInterface.create_libjade(timeout)
+ return JadeAPI(impl)
+
def connect(self):
"""
Try to connect the underlying transport interface (eg. serial, ble, etc.)
@@ -1993,18 +2029,18 @@ class JadeAPI:
class JadeInterface:
"""
- Mid-level interface to Jade
- Wraps either a serial or a ble connection
+ Mid-level interface to Jade.
+ Wraps either a serial or ble connection, or an in-process libjade instance.
Calls to send and receive bytes and cbor messages over the interface.
Either:
a) use wrapped with JadeAPI
(recommended)
or:
- b) use with JadeInterface.create_[serial|ble]() as jade:
+ b) use with JadeInterface.create_[serial|ble|libjade]() as jade:
...
or:
- c) use JadeInterface.create_[serial|ble], then call connect() before
+ c) use JadeInterface.create_[serial|ble|libjade], then call connect() before
using, and disconnect() when finished
(caveat cranium)
or:
@@ -2110,6 +2146,31 @@ class JadeInterface:
loop=loop)
return JadeInterface(impl)
+ @staticmethod
+ def create_libjade(timeout=None):
+ """
+ Create a JadeInterface object object using libjade (in-process software emulation).
+ WARNING: libjade is BETA and should not be used with real funds.
+ NOTE: raises JadeError if libjade dependencies not installed.
+
+ Parameters
+ ----------
+ timeout : int, optional
+ The read timeout when awaiting messages (Uses 5s if not given).
+
+ Returns
+ -------
+ JadeInterface
+ Interface object configured to use the libjade.
+ NOTE: The caller must call 'connect()' before using the instance.
+ """
+ this_module = sys.modules[__name__]
+ if not hasattr(this_module, "JadeSoftwareImpl"):
+ raise JadeError(1, "libjade support not installed", None)
+
+ impl = JadeSoftwareImpl(timeout or DEFAULT_LIBJADE_TIMEOUT)
+ return JadeInterface(impl)
+
def connect(self):
"""
Try to connect the underlying transport interface (eg. serial, ble, etc.)
diff --git a/jadepy/jade_serial.py b/jadepy/jade_serial.py
index cc61835..ae60e18 100644
--- a/jadepy/jade_serial.py
+++ b/jadepy/jade_serial.py
@@ -60,8 +60,9 @@ class JadeSerialImpl:
raise JadeError(1, 'Unable to open port', self.device)
# Ensure RTS and DTR are not set (as this can cause the hw to reboot)
- self.ser.setRTS(False)
- self.ser.setDTR(False)
+ if self.device.startswith('/dev/tty'):
+ self.ser.setRTS(False)
+ self.ser.setDTR(False)
logger.info('Connected')
@@ -70,8 +71,9 @@ class JadeSerialImpl:
# Ensure RTS and DTR are not set (as this can cause the hw to reboot)
# and then close the connection
- self.ser.setRTS(False)
- self.ser.setDTR(False)
+ if self.device.startswith('/dev/tty'):
+ self.ser.setRTS(False)
+ self.ser.setDTR(False)
self.ser.close()
# Reset state
diff --git a/jadepy/jade_sw.py b/jadepy/jade_sw.py
new file mode 100644
index 0000000..95b90e7
--- /dev/null
+++ b/jadepy/jade_sw.py
@@ -0,0 +1,92 @@
+from ctypes import CDLL, POINTER, c_ubyte, c_size_t, byref
+import logging
+from .jade_error import JadeError
+
+
+logger = logging.getLogger(__name__)
+
+try:
+ _libjade = CDLL('libjade.so')
+ _libjade.libjade_receive.restype = POINTER(c_ubyte)
+except Exception as _:
+ raise ImportError # libjade.so not available
+
+
+#
+# Experimental, internal, in-process interface to Jade
+# Intended for use via JadeInterface wrapper.
+#
+# Use via JadeInterface.create_libjade() (see JadeInterface)
+#
+class JadeSoftwareImpl:
+
+ _log_levels = {
+ logging.DEBUG: 1,
+ logging.INFO: 2,
+ logging.WARNING: 3,
+ logging.ERROR: 4,
+ logging.CRITICAL: 5, # Note we have no critical logs
+ logging.NOTSET: 5 # Default to critical (i.e. no logging)
+ }
+
+ def __init__(self, timeout):
+ self.timeout = timeout
+ self.libjade = None
+ self.msg = None # Bytes of the current message being read, if any
+
+ def connect(self):
+ assert self.libjade is None
+ self.libjade = _libjade
+ # Respect the python log level for Jade logging
+ log_level = self._log_levels[logger.getEffectiveLevel()]
+ self.libjade.libjade_set_log_level(log_level)
+ # Starts the firmware in a separate thread
+ self.libjade.libjade_start()
+ logger.info('Connected to in-process software Jade')
+
+ def disconnect(self):
+ assert self.libjade is not None
+ self.libjade.libjade_stop()
+ self.libjade = None
+
+ def write(self, bytes_):
+ assert self.libjade is not None
+ if logger.isEnabledFor(logging.DEBUG):
+ logger.debug(f'Pushing {bytes_.hex()}\n')
+ # The software interface takes the whole message in one go
+ num_bytes = len(bytes_)
+ if not self.libjade.libjade_send(bytes_, num_bytes):
+ raise JadeError(1, f'Failed to send {num_bytes} bytes', None)
+ return num_bytes # Let the caller know we wrote all bytes
+
+ def read(self, n):
+ assert self.libjade is not None
+ if n == 0:
+ # Sometimes read is called with 0 bytes.
+ # Treat this as a no-op.
+ logger.debug('Read of 0 bytes requested')
+ return bytes()
+
+ if not self.msg:
+ # The software interface reads the whole message in one go.
+ # Fetch it here, then return it in chunks below
+ logger.debug(f'Calling libjade_receive() with timeout {self.timeout}\n')
+ bytes_len = c_size_t()
+ buff = self.libjade.libjade_receive(self.timeout, byref(bytes_len))
+ if not buff:
+ logger.debug('Timeout calling libjade_receive()\n')
+ return bytes()
+ self.msg = bytes([buff[i] for i in range(bytes_len.value)])
+ self.libjade.libjade_release(buff)
+ if logger.isEnabledFor(logging.DEBUG):
+ logger.debug(f'Received message {self.msg.hex()}\n')
+
+ # Return as much of the message as the caller asked for
+ ret = self.msg[:n]
+ self.msg = self.msg[n:]
+ if False and logger.isEnabledFor(logging.DEBUG):
+ # Not generally useful unless debugging serialization failures
+ logger.debug(f'Returning {ret.hex()} leaving {self.msg.hex()}')
+ elif not self.msg:
+ logger.debug('Message consumed')
+ return ret
diff --git a/jadepy/jade_tcp.py b/jadepy/jade_tcp.py
index 4e3ffd7..2d5885b 100644
--- a/jadepy/jade_tcp.py
+++ b/jadepy/jade_tcp.py
@@ -1,5 +1,6 @@
import socket
import logging
+import os
logger = logging.getLogger(__name__)
@@ -34,21 +35,35 @@ class JadeTCPImpl:
def connect(self):
assert self.isSupportedDevice(self.device)
assert self.tcp_sock is None
-
logger.info(f'Connecting to {self.device}')
- self.tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.tcp_sock.settimeout(self.timeout)
+ conn_path = self.device[len(self.PROTOCOL_PREFIX):]
- url = self.device[len(self.PROTOCOL_PREFIX):].split(':')
- self.tcp_sock.connect((url[0], int(url[1])))
- assert self.tcp_sock is not None
+ is_unix_socket = False
+ if conn_path.startswith('/') or os.path.exists(conn_path):
+ is_unix_socket = True
+ elif '/' in conn_path and ':' not in conn_path:
+ is_unix_socket = True
+ if is_unix_socket:
+ self.tcp_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.tcp_sock.settimeout(self.timeout)
+ self.tcp_sock.connect(conn_path)
+ else:
+ self.tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.tcp_sock.settimeout(self.timeout)
+ if ':' in conn_path:
+ url = conn_path.split(':')
+ self.tcp_sock.connect((url[0], int(url[1])))
+ else:
+ self.tcp_sock.connect((conn_path, 80))
+
+ assert self.tcp_sock is not None
self.tcp_sock.__enter__()
logger.info('Connected')
def disconnect(self):
assert self.tcp_sock is not None
- self.tcp_sock.__exit__()
+ self.tcp_sock.__exit__(None, None, None)
# Reset state
self.tcp_sock = None
diff --git a/libjade/CMakeLists.txt b/libjade/CMakeLists.txt
new file mode 100644
index 0000000..90e69b3
--- /dev/null
+++ b/libjade/CMakeLists.txt
@@ -0,0 +1,118 @@
+cmake_minimum_required(VERSION 3.16) # same we use in idf
+project(libjade C CXX)
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build (Debug|Release|RelWithDebInfo|MinSizeRel|Sanitize)" FORCE)
+ message(STATUS "Building in ${CMAKE_BUILD_TYPE} mode (default)")
+else()
+ message(STATUS "Building in ${CMAKE_BUILD_TYPE} mode (user provided or cached)")
+endif()
+
+if(DEFINED ENV{IDF_PATH})
+ set(IDF_PATH $ENV{IDF_PATH})
+ message(STATUS "Using IDF_PATH from environment: ${IDF_PATH}")
+else()
+ message(FATAL_ERROR "IDF_PATH is not defined. Please set the IDF_PATH environment variable.")
+endif()
+
+find_package(Threads REQUIRED)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "Build position-independent code" FORCE)
+
+set(mbedtls_targets mbedtls mbedcrypto mbedx509 everest p256m)
+set(ENABLE_TESTING CACHE BOOL OFF)
+set(ENABLE_PROGRAMS CACHE BOOL OFF)
+set(GEN_FILES CACHE BOOL OFF)
+add_subdirectory(${IDF_PATH}/components/mbedtls/mbedtls
+ ${CMAKE_CURRENT_BINARY_DIR}/mbedtls)
+
+set(CBOR_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../managed_components/espressif__cbor/tinycbor/src")
+file(GLOB CBOR_SOURCES "${CBOR_SOURCE_DIR}/*.c")
+add_library(cbor_target STATIC ${CBOR_SOURCES})
+target_include_directories(cbor_target PUBLIC "${CBOR_SOURCE_DIR}")
+set(CBOR_INCLUDE_DIR "${CBOR_SOURCE_DIR}")
+set(ESP_MOCKED "${CMAKE_CURRENT_SOURCE_DIR}/include")
+
+# Note -DVERIFY enables (expensive) libsecp256k1 verification
+set(CMAKE_C_FLAGS_DEBUG "-O0 -ggdb3 -DVERIFY -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -fstack-protector-strong -fstack-clash-protection")
+
+# FIXME: revisit what flags we want in release/production
+set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG")
+
+if(CMAKE_BUILD_TYPE STREQUAL "Sanitize")
+ add_compile_options(-O1 -ggdb -fsanitize=address -fsanitize=undefined -fsanitize=alignment -fsanitize-address-use-after-scope -fno-sanitize-recover=all)
+ add_link_options(-fsanitize=address -fsanitize=undefined -fsanitize=alignment -fsanitize-address-use-after-scope -fno-sanitize-recover=all)
+endif()
+
+set(CMAKE_C_VISIBILITY_PRESET hidden)
+set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
+
+# for consistency with the Jade build - we can change if needed
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
+
+set(COMMON_INCLUDES
+ ${CBOR_INCLUDE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/..
+ ${CMAKE_CURRENT_SOURCE_DIR}/../main
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/assets
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32_bsdiff
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32-quirc
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32-quirc/lib
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/src
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/src/ccan
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core
+ ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/src/secp256k1/include
+ ${IDF_PATH}/components/http_parser
+)
+
+set(COMMON_SRC
+ libjade.c
+ ${IDF_PATH}/components/http_parser/http_parser.c
+)
+
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../components/esp32_bc-ur
+ ${CMAKE_CURRENT_BINARY_DIR}/bcur)
+target_include_directories(bcur PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../components/libwally-core/upstream/include)
+target_link_libraries(bcur PRIVATE mbedcrypto)
+
+# libjade.so
+add_library(jade SHARED ${COMMON_SRC})
+target_link_libraries(jade PRIVATE m z bcur cbor_target mbedtls mbedcrypto mbedx509)
+target_include_directories(jade PRIVATE ${COMMON_INCLUDES})
+
+set_source_files_properties(libjade.c PROPERTIES COMPILE_OPTIONS
+ "-Wall;-W;-Wno-format;-Wno-unused-function;-Wno-unused-parameter;-Wno-sign-compare;-Wno-missing-field-initializers;-Wno-narrowing")
+target_compile_definitions(jade PRIVATE LIBJADE_BUILD)
+
+# libjade.a
+add_library(jade_static STATIC ${COMMON_SRC})
+target_link_libraries(jade_static PRIVATE m z mbedtls mbedcrypto mbedx509 bcur)
+target_include_directories(jade_static PRIVATE ${COMMON_INCLUDES})
+target_compile_definitions(jade_static PRIVATE LIBJADE_BUILD)
+
+add_executable(libjade_daemon daemon.c)
+target_link_libraries(libjade_daemon PRIVATE jade_static bcur cbor_target m z mbedtls mbedcrypto mbedx509 Threads::Threads)
+target_include_directories(libjade_daemon PRIVATE ${COMMON_INCLUDES})
+
+# Assets
+set(ASSETS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../components/assets)
+
+add_custom_command(OUTPUT asset_data.inc
+ COMMAND python3 ${ASSETS_DIR}/gen_assets.py ${ASSETS_DIR}/asset_data.json ${CMAKE_CURRENT_BINARY_DIR}/asset_data.inc
+ DEPENDS ${ASSETS_DIR}/gen_assets.py ${ASSETS_DIR}/asset_data.json
+ VERBATIM)
+
+add_custom_command(OUTPUT asset_data_testnet.inc
+ COMMAND python3 ${ASSETS_DIR}/gen_assets.py ${ASSETS_DIR}/asset_data_testnet.json ${CMAKE_CURRENT_BINARY_DIR}/asset_data_testnet.inc
+ DEPENDS ${ASSETS_DIR}/gen_assets.py ${ASSETS_DIR}/asset_data_testnet.json
+ VERBATIM)
+
+add_custom_target(asset_data DEPENDS asset_data.inc asset_data_testnet.inc)
+add_dependencies(jade asset_data)
+add_dependencies(jade_static asset_data)
diff --git a/libjade/README.md b/libjade/README.md
new file mode 100644
index 0000000..9107891
--- /dev/null
+++ b/libjade/README.md
@@ -0,0 +1,60 @@
+# libjade
+
+libjade provides the Jade firmware in a native library.
+
+It can be thought of as an emulated or virtual Jade device that runs using
+native code in the address space of the application linked to it.
+
+This initial implementation is HIGHLY EXPERIMENTAL AND INCOMPLETE, and
+should UNDER NO CIRCUMSTANCES BE USED BEYOND DEVELOPMENT AND TESTING.
+
+## Building and running
+
+libjade is built using cmake. You must have `IDF_PATH` set in your environment
+in the same way as when doing normal jade development. Note that the idf
+tooling is not used, but the idf-provided mbed-tls library is built natively.
+
+To build, run:
+
+```
+./libjade/make_libjade.sh [Debug|Release|RelWithDebInfo|MinSizeRel|Sanitize]
+```
+
+The above command builds the files `libjade.so`, `libjade_static.a` and
+`libjade_daemon` in the directory `build_linux/libjade/`.
+
+See `libjade/libjade.h` for the exposed programmatic interface.
+
+### Python
+
+When the `libjade.so` shared library is available in LD_LIBRARY_PATH, the
+JadeAPI.create_libjade() function can be used to run libjade in-process.
+
+The separate daemon process `libjade_daemon` can be run to expose a serial,
+socket or tcp connection that the existing JadeAPI can connect to.
+
+## Status
+
+- All message handlers are implemented. OTA is untested.
+- The library currently always runs in CI mode (automatically chooses the
+ default option for a given activity). In the future the ability to provide
+ input to the firmware may be added.
+- No GUI is currently exposed.
+- GUI activities are currently leaked.
+- No screen or emulation is available.
+- Some operations that are expected to be constant-time are currently not.
+- Sensitive stack clearing is not implemented.
+- Memory is not locked from paging.
+- No safety or security analysis has been performed on any code under the
+ `libjade/` directory.
+- The programmatic interface is not stable and may change at any time,
+ including in incompatible ways.
+
+## Implementation
+
+Portions of the expected runtime environment are implemented using native
+code stubs. Some portions of the firmware itself (e.g. the gui) are also
+reimplemented as stubs.
+
+On startup, the firmware code runs in a separate thread and processes
+messages using the standard CBOR interface and the standard firmware code.
diff --git a/libjade/daemon.c b/libjade/daemon.c
new file mode 100644
index 0000000..e21a328
--- /dev/null
+++ b/libjade/daemon.c
@@ -0,0 +1,432 @@
+#define _XOPEN_SOURCE 600
+
+#include "libjade.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <esp_log.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+static int master_fd = -1;
+
+#define BUFFER_SIZE 1024
+#define UNIX_PATH_MAX sizeof(((struct sockaddr_un*)0)->sun_path)
+
+static bool set_nonblocking(int fd, const char* context)
+{
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1) {
+ fprintf(stderr, "fcntl(F_GETFL) failed for %s: %s\n", context, strerror(errno));
+ return false;
+ }
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ fprintf(stderr, "fcntl(F_SETFL O_NONBLOCK) failed for %s: %s\n", context, strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+static void serial_init(const char* serial_link_path)
+{
+ char* slave_name;
+
+ master_fd = posix_openpt(O_RDWR | O_NOCTTY);
+ if (master_fd < 0) {
+ perror("posix_openpt");
+ exit(EXIT_FAILURE);
+ }
+
+ if (grantpt(master_fd) < 0) {
+ perror("grantpt");
+ close(master_fd);
+ exit(EXIT_FAILURE);
+ }
+
+ if (unlockpt(master_fd) < 0) {
+ perror("unlockpt");
+ close(master_fd);
+ exit(EXIT_FAILURE);
+ }
+
+ slave_name = ptsname(master_fd);
+ if (slave_name == NULL) {
+ perror("ptsname");
+ close(master_fd);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!set_nonblocking(master_fd, "serial master")) {
+ close(master_fd);
+ exit(EXIT_FAILURE);
+ }
+
+ printf("Virtual serial port: %s\n", slave_name);
+
+ if (serial_link_path) {
+ unlink(serial_link_path);
+ if (symlink(slave_name, serial_link_path) < 0) {
+ fprintf(stderr, "Warning: failed to create symlink at %s -> %s: %s\n", serial_link_path, slave_name,
+ strerror(errno));
+ } else {
+ printf("Created symlink: %s -> %s\n", serial_link_path, slave_name);
+ }
+ }
+}
+
+static bool write_all(int fd, const uint8_t* buffer, size_t size)
+{
+ size_t written = 0;
+ while (written < size) {
+ ssize_t result = write(fd, buffer + written, size - written);
+ if (result < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ fprintf(stderr, "Warning: write would block, data might be delayed or lost.\n");
+ return false;
+ } else {
+ perror("write failed");
+ return false;
+ }
+ } else if (result == 0) {
+ fprintf(stderr, "Warning: write returned 0\n");
+ return false;
+ }
+ written += result;
+ }
+ return true;
+}
+
+static bool handle_jade_to_client(int client_fd, const char* client_info)
+{
+ size_t jade_size = 0;
+ uint8_t* jade_data = libjade_receive(0, &jade_size);
+
+ if (jade_data != NULL) {
+ if (jade_size > 0) {
+ if (!write_all(client_fd, jade_data, jade_size)) {
+ fprintf(
+ stderr, "WARNING: write_all to %s failed (errno: %d - %s).\n", client_info, errno, strerror(errno));
+
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ fprintf(stderr, "ERROR: Fatal write error to %s. Disconnecting.\n", client_info);
+ libjade_release(jade_data);
+ return false;
+ } else {
+ fprintf(stderr, "WARNING: Data for %s might be lost due to write block.\n", client_info);
+ }
+ }
+ }
+ libjade_release(jade_data);
+ }
+ return true;
+}
+
+static void handle_client(int client_fd, const char* client_info)
+{
+ printf("Connection accepted from %s (fd: %d)\n", client_info, client_fd);
+
+ if (!set_nonblocking(client_fd, client_info)) {
+ close(client_fd);
+ return;
+ }
+
+ bool client_connected = true;
+ uint8_t buffer[BUFFER_SIZE];
+ fd_set readfds;
+ struct timeval timeout;
+
+ while (client_connected) {
+ FD_ZERO(&readfds);
+ FD_SET(client_fd, &readfds);
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 100000;
+
+ int select_result = select(client_fd + 1, &readfds, NULL, NULL, &timeout);
+
+ if (select_result < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ fprintf(stderr, "select failed for %s: %s\n", client_info, strerror(errno));
+ client_connected = false;
+ break;
+ }
+
+ if (select_result > 0 && FD_ISSET(client_fd, &readfds)) {
+ ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE);
+ if (bytes_read < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ /* no data */
+ } else if (errno == ECONNRESET) {
+ fprintf(stderr, "Connection reset by peer (%s).\n", client_info);
+ client_connected = false;
+ } else {
+ fprintf(stderr, "read from %s failed: %s\n", client_info, strerror(errno));
+ client_connected = false;
+ }
+ } else if (bytes_read == 0) {
+ printf("Connection closed by peer (%s, EOF).\n", client_info);
+ client_connected = false;
+ } else {
+ if (!libjade_send(buffer, bytes_read)) {
+ fprintf(stderr, "ERROR: libjade_send failed! Dropping connection to %s.\n", client_info);
+ client_connected = false;
+ }
+ }
+ }
+
+ if (client_connected) {
+ client_connected = handle_jade_to_client(client_fd, client_info);
+ }
+ }
+
+ printf("Connection handler finished for %s (fd: %d).\n", client_info, client_fd);
+ close(client_fd);
+}
+
+static void serial_bridge(void)
+{
+ printf("libjade serial daemon started, available via %s\n", ptsname(master_fd));
+ uint8_t serial_buffer[BUFFER_SIZE];
+ fd_set readfds;
+ struct timeval timeout;
+
+ while (1) {
+ FD_ZERO(&readfds);
+ FD_SET(master_fd, &readfds);
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 100000;
+
+ int select_result = select(master_fd + 1, &readfds, NULL, NULL, &timeout);
+
+ if (select_result < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ perror("select failed");
+ break;
+ }
+
+ if (select_result > 0 && FD_ISSET(master_fd, &readfds)) {
+ ssize_t bytes_read = read(master_fd, serial_buffer, BUFFER_SIZE);
+
+ if (bytes_read < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EIO) {
+ perror("read from serial failed");
+ break;
+ }
+ } else if (bytes_read == 0) {
+ printf("Serial port connection closed/EOF.\n");
+ } else {
+ if (!libjade_send(serial_buffer, bytes_read)) {
+ fprintf(stderr, "Warning: libjade_send failed (buffer full?)\n");
+ }
+ }
+ }
+
+ handle_jade_to_client(master_fd, "serial port");
+ }
+ close(master_fd);
+}
+
+static void tcp_bridge(int port)
+{
+ printf("-> TCP mode enabled on port %d.\n", port);
+
+ int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (listen_fd < 0) {
+ perror("socket creation failed (tcp)");
+ return;
+ }
+
+ int optval = 1;
+ if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
+ perror("setsockopt(SO_REUSEADDR) failed");
+ }
+
+ struct sockaddr_in serv_addr;
+ memset(&serv_addr, 0, sizeof(serv_addr));
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ serv_addr.sin_port = htons(port);
+
+ if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
+ perror("bind failed (tcp)");
+ close(listen_fd);
+ return;
+ }
+
+ if (listen(listen_fd, 1) < 0) {
+ perror("listen failed (tcp)");
+ close(listen_fd);
+ return;
+ }
+
+ printf("TCP bridge listening on port %d\n", port);
+
+ while (1) {
+ struct sockaddr_in cli_addr;
+ socklen_t clilen = sizeof(cli_addr);
+
+ int client_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &clilen);
+ if (client_fd < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ perror("accept failed (tcp)");
+ sleep(1);
+ continue;
+ }
+
+ char client_info[64];
+ char client_ip[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, &cli_addr.sin_addr, client_ip, sizeof(client_ip));
+ snprintf(client_info, sizeof(client_info), "%s:%d", client_ip, ntohs(cli_addr.sin_port));
+
+ handle_client(client_fd, client_info);
+ }
+
+ printf("Closing listening socket (fd: %d).\n", listen_fd);
+ close(listen_fd);
+ printf("TCP bridge exiting.\n");
+}
+
+static void socket_bridge(const char* socket_path)
+{
+ printf("-> Socket mode enabled on %s.\n", socket_path);
+
+ int listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (listen_fd < 0) {
+ perror("socket creation failed (unix socket)");
+ return;
+ }
+
+ struct sockaddr_un serv_addr;
+ memset(&serv_addr, 0, sizeof(serv_addr));
+ serv_addr.sun_family = AF_UNIX;
+ strncpy(serv_addr.sun_path, socket_path, sizeof(serv_addr.sun_path) - 1);
+
+ unlink(socket_path);
+
+ if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
+ perror("bind failed (unix socket)");
+ close(listen_fd);
+ return;
+ }
+
+ if (listen(listen_fd, 1) < 0) {
+ perror("listen failed (unix socket)");
+ close(listen_fd);
+ return;
+ }
+
+ printf("Socket bridge listening on %s\n", socket_path);
+
+ while (1) {
+ int client_fd = accept(listen_fd, NULL, NULL);
+ if (client_fd < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ perror("accept failed (unix socket)");
+ sleep(1);
+ continue;
+ }
+
+ char client_info[64];
+ snprintf(client_info, sizeof(client_info), "unix socket client");
+
+ handle_client(client_fd, client_info);
+ }
+
+ printf("Closing listening socket (fd: %d).\n", listen_fd);
+ close(listen_fd);
+ unlink(socket_path);
+ printf("Socket bridge exiting.\n");
+}
+
+static int usage(const char* cmd, const char* error)
+{
+ fprintf(stderr, "Error: %s.\n", error);
+ fprintf(stderr, "Usage: %s [--serialport [SYMLINK_PATH] | --tcp PORT | --socketfile PATH]\n", cmd);
+ return EXIT_FAILURE;
+}
+
+int main(int argc, char* argv[])
+{
+ int serial_mode = 0;
+ char* serial_link_path = NULL;
+ int tcp_mode = 0;
+ int tcp_port = 0;
+ int socket_mode = 0;
+ char* socket_path = NULL;
+
+ for (int i = 1; i < argc; ++i) {
+ if (strcmp(argv[i], "--serialport") == 0) {
+ serial_mode = 1;
+ if (i + 1 < argc && argv[i + 1][0] != '-') {
+ serial_link_path = argv[++i];
+ }
+ } else if (strcmp(argv[i], "--tcp") == 0) {
+ tcp_mode = 1;
+ if (i + 1 < argc && argv[i + 1][0] != '-') {
+ char* endptr;
+ errno = 0;
+ long port_val = strtol(argv[i + 1], &endptr, 10);
+ if (endptr == argv[i + 1] || *endptr || errno == ERANGE || port_val <= 0 || port_val > 65535) {
+ return usage(argv[0], "--tcp PORT must be an integer from 1-65535");
+ } else {
+ tcp_port = (int)port_val;
+ ++i;
+ }
+ } else {
+ return usage(argv[0], "--tcp requires a PORT argument");
+ }
+ } else if (strcmp(argv[i], "--socketfile") == 0) {
+ socket_mode = 1;
+ if (i + 1 < argc && argv[i + 1][0] != '-') {
+ socket_path = argv[++i];
+ } else {
+ return usage(argv[0], "--socketfile requires a PATH argument");
+ }
+ } else {
+ return usage(argv[0], "Unknown option");
+ }
+ }
+
+ if (serial_mode + tcp_mode + socket_mode != 1) {
+ return usage(argv[0], "Exactly one of --serialport, --tcp, or --socketfile must be given");
+ }
+
+ libjade_start();
+ // FIXME: Add log-level cmdline parameter
+ libjade_set_log_level(ESP_LOG_NONE);
+
+ if (serial_mode) {
+ serial_init(serial_link_path);
+ printf("-> Serial mode enabled.\n");
+ serial_bridge();
+ } else if (tcp_mode) {
+ tcp_bridge(tcp_port);
+ } else {
+ socket_bridge(socket_path);
+ }
+
+ printf("Exiting libjade daemon.\n");
+ libjade_stop();
+
+ return EXIT_SUCCESS;
+}
diff --git a/libjade/gui.c b/libjade/gui.c
new file mode 100644
index 0000000..4933600
--- /dev/null
+++ b/libjade/gui.c
@@ -0,0 +1,297 @@
+#include <stdarg.h>
+#include <string.h>
+
+#include "gui.h"
+#include "jade_assert.h"
+#include "utils/event.h"
+#include "utils/malloc_ext.h"
+
+// display.c constants
+const color_t TFT_BLACK = 0x0000;
+const color_t TFT_NAVY = 0x0F00;
+const color_t TFT_DARKGREEN = 0xE003;
+const color_t TFT_DARKCYAN = 0xEF03;
+const color_t TFT_MAROON = 0x0078;
+const color_t TFT_PURPLE = 0x0F78;
+const color_t TFT_OLIVE = 0xE07B;
+const color_t TFT_LIGHTGREY = 0x18C6;
+const color_t TFT_DARKGREY = 0xEF7B;
+const color_t TFT_BLUE = 0x1F00;
+const color_t TFT_GREEN = 0xE007;
+const color_t TFT_CYAN = 0xFF07;
+const color_t TFT_RED = 0x00F8;
+const color_t TFT_MAGENTA = 0x1FF8;
+const color_t TFT_YELLOW = 0xE0FF;
+const color_t TFT_WHITE = 0xFFFF;
+const color_t TFT_ORANGE = 0x20FD;
+const color_t TFT_GREENYELLOW = 0xE5AF;
+const color_t TFT_PINK = 0x19FE;
+// end display.c constants
+
+ESP_EVENT_DEFINE_BASE(GUI_BUTTON_EVENT);
+ESP_EVENT_DEFINE_BASE(GUI_EVENT);
+
+const color_t GUI_BLOCKSTREAM_JADE_GREEN = 0x4C04;
+const color_t GUI_BLOCKSTREAM_BUTTONBORDER_GREY = 0x0421;
+
+const color_t GUI_BLOCKSTREAM_HIGHTLIGHT_DEFAULT = GUI_BLOCKSTREAM_JADE_GREEN;
+const color_t GUI_BLOCKSTREAM_HIGHTLIGHT_ORANGE = 0xE0D3;
+const color_t GUI_BLOCKSTREAM_HIGHTLIGHT_BLUE = 0xD318;
+const color_t GUI_BLOCKSTREAM_HIGHTLIGHT_DARKGREY = 0xA210;
+const color_t GUI_BLOCKSTREAM_HIGHTLIGHT_LIGHTGREY = 0xB294;
+const color_t GUI_BLOCKSTREAM_UNHIGHTLIGHTED_DEFAULT = 0x494A;
+
+typedef struct _activity_holder_t activity_holder_t;
+struct _activity_holder_t {
+ gui_activity_t activity;
+ activity_holder_t* next;
+};
+
+typedef struct {
+ gui_view_node_t* node_to_repaint;
+ gui_activity_t* new_activity;
+ activity_holder_t* to_free;
+} gui_task_job_t;
+
+// current activity being drawn on screen
+static gui_activity_t* current_activity = NULL;
+// stack of activities that currently exist
+static activity_holder_t* existing_activities = NULL;
+
+// Click/select event (ie. which button counts as 'click'/select)
+// and which gui highlight colour is in use
+static gui_event_t gui_click_event = GUI_FRONT_CLICK_EVENT;
+
+// status bar
+struct {
+ bool unused;
+} status_bar;
+
+gui_event_t gui_get_click_event(void) { return gui_click_event; }
+
+void gui_set_click_event(const bool use_wheel_click)
+{
+ gui_click_event = use_wheel_click ? GUI_WHEEL_CLICK_EVENT : GUI_FRONT_CLICK_EVENT;
+}
+
+color_t gui_get_highlight_color(void) { return GUI_BLOCKSTREAM_HIGHTLIGHT_DEFAULT; }
+
+void gui_set_highlight_color(const uint8_t theme) {}
+
+bool gui_get_flipped_orientation(void) { return false; }
+
+bool gui_set_flipped_orientation(const bool flipped_orientation) { return false; }
+
+void gui_init(TaskHandle_t* gui_h)
+{
+ // create a blank activity
+ current_activity = gui_make_activity();
+}
+
+bool gui_initialized(void) { return true; } // gui task started
+
+void gui_set_active(gui_view_node_t* node, bool value) {}
+
+void gui_make_activity_ex(gui_activity_t** ppact, const bool has_status_bar, const char* title, const bool managed)
+{
+ JADE_INIT_OUT_PPTR(ppact);
+ JADE_ASSERT(!title || has_status_bar);
+
+ if (managed) {
+ // Managed activity - add to activities list
+ activity_holder_t* holder = JADE_CALLOC(1, sizeof(activity_holder_t));
+
+ // Add to the stack of existing activities
+ holder->next = existing_activities;
+ existing_activities = holder;
+
+ // Return the activity from within this holder
+ *ppact = &holder->activity;
+ } else {
+ // Unmanaged - just create the activity to return
+ *ppact = JADE_CALLOC(1, sizeof(gui_activity_t));
+ JADE_LOGW("Created unmanaged gui activity at %p", *ppact);
+ }
+}
+
+gui_activity_t* gui_make_activity(void)
+{
+ gui_activity_t* activity = NULL;
+ gui_make_activity_ex(&activity, false, NULL, true);
+ JADE_ASSERT(activity);
+ activity->selectables_wrap = true;
+ return activity;
+}
+
+int32_t gui_activity_wait_button(gui_activity_t* activity, const int32_t default_event_id)
+{
+ int32_t ev_id = default_event_id;
+#ifndef CONFIG_DEBUG_UNATTENDED_CI
+ if (!gui_activity_wait_event(activity, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0)) {
+ ev_id = BTN_EVENT_TIMEOUT;
+ }
+#else
+ gui_activity_wait_event(activity, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, NULL, NULL,
+ CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS / portTICK_PERIOD_MS);
+#endif
+ return ev_id;
+}
+
+void gui_chain_activities(const link_activity_t* link_act, linked_activities_info_t* pActInfo) {}
+
+void gui_set_parent(gui_view_node_t* child, gui_view_node_t* parent) {}
+
+void gui_make_hsplit(gui_view_node_t** ptr, enum gui_split_type kind, uint8_t parts, ...) { *ptr = NULL; }
+
+void gui_make_vsplit(gui_view_node_t** ptr, enum gui_split_type kind, uint8_t parts, ...) { *ptr = NULL; }
+
+void gui_make_button(
+ gui_view_node_t** ptr, const color_t color, const color_t selected_color, const uint32_t event_id, void* args)
+{
+ *ptr = NULL;
+}
+
+void gui_make_fill(gui_view_node_t** ptr, color_t color, enum fill_node_kind fill_type, gui_view_node_t* parent)
+{
+ *ptr = NULL;
+}
+
+void gui_make_text(gui_view_node_t** ptr, const char* text, color_t color) { *ptr = NULL; }
+
+void gui_make_text_font(gui_view_node_t** ptr, const char* text, color_t color, uint32_t font) { *ptr = NULL; }
+
+void gui_make_icon(gui_view_node_t** ptr, const Icon* icon, color_t color, const color_t* bg_color) { *ptr = NULL; }
+
+void gui_set_icon_animation(gui_view_node_t* node, Icon* icons, const size_t num_icons, const size_t frames_per_icon) {}
+
+void gui_set_icon_to_qr(gui_view_node_t* node) {}
+
+void gui_next_qrcode_color(void) {}
+
+void gui_make_picture(gui_view_node_t** ptr, const Picture* picture) { *ptr = NULL; }
+
+void gui_make_qrguide(gui_view_node_t** ptr, color_t color) { *ptr = NULL; }
+
+void gui_set_margins(gui_view_node_t* node, uint32_t sides, ...) {}
+
+void gui_set_padding(gui_view_node_t* node, uint32_t sides, ...) {}
+
+void gui_set_borders(gui_view_node_t* node, const color_t color, const uint16_t thickness, const uint8_t borders) {}
+
+void gui_set_borders_selected_color(gui_view_node_t* node, color_t selected_color) {}
+
+void gui_set_borders_inactive_color(gui_view_node_t* node, color_t inactive_color) {}
+
+void gui_set_colors(gui_view_node_t* node, color_t color, color_t selected_color) {}
+
+void gui_set_color(gui_view_node_t* node, color_t color) {}
+
+void gui_set_align(gui_view_node_t* node, enum gui_horizontal_align halign, enum gui_vertical_align valign) {}
+
+void gui_set_text_scroll(gui_view_node_t* node, color_t background_color) {}
+
+void gui_set_text_scroll_selected(
+ gui_view_node_t* node, bool only_when_selected, color_t background_color, color_t selected_background_color)
+{
+}
+
+void gui_set_text_noise(gui_view_node_t* node, color_t background_color) {}
+
+void gui_set_text_font(gui_view_node_t* node, uint32_t font) {}
+
+void gui_set_text_default_font(gui_view_node_t* node) {}
+
+void gui_update_text(gui_view_node_t* node, const char* text) {}
+
+void gui_update_icon(gui_view_node_t* node, const Icon icon, const bool repaint_parent) {}
+
+void gui_update_picture(gui_view_node_t* node, const Picture* picture, const bool repaint_parent) {}
+
+void gui_wheel_click(void) {}
+
+void gui_front_click(void) {}
+
+void gui_next(void) {}
+
+void gui_prev(void) {}
+
+void gui_set_activity_initial_selection(gui_view_node_t* node) {}
+
+void gui_activity_set_active_selection(
+ gui_activity_t* activity, gui_view_node_t** nodes, size_t num_nodes, const bool* active, gui_view_node_t* selected)
+{
+}
+
+void gui_repaint(gui_view_node_t* node) {}
+
+// Call to initiate a change of current activity - optionally freeing other managed activities.
+// Can also pass a 'retain' activity which is not made current, but is retained and not freed.
+void gui_set_current_activity_ex(gui_activity_t* new_current, const bool free_managed_activities)
+{
+ JADE_ASSERT(new_current);
+
+ // We will post the gui task the new activity, and the list of activities it can free
+ gui_task_job_t switch_info = { .node_to_repaint = NULL, .new_activity = new_current, .to_free = NULL };
+
+ // If freeing others, partition existing activities into those to keep (new current and the
+ // passed 'retain' activity) and those to free (all others).
+ if (free_managed_activities) {
+ activity_holder_t* holder = existing_activities;
+ existing_activities = NULL;
+
+ while (holder) {
+ activity_holder_t* const next = holder->next;
+
+ if (&holder->activity == new_current) {
+ // Retain this activity
+ holder->next = existing_activities;
+ existing_activities = holder;
+ } else {
+ // Discard this activity
+ holder->next = switch_info.to_free;
+ switch_info.to_free = holder;
+ }
+ holder = next;
+ }
+
+ // Sanity check
+ // 'existing_activities' should be the new current activity only, or be completely empty
+ // (if current activity is an "unmanaged" activity)
+ JADE_ASSERT(
+ !existing_activities || ((&existing_activities->activity == new_current) && !existing_activities->next));
+ }
+}
+
+// Initiate change of 'current' activity
+void gui_set_current_activity(gui_activity_t* new_current)
+{
+ // Set a new activity without freeing any other activities
+ gui_set_current_activity_ex(new_current, false);
+}
+
+struct wait_event_data_t {
+ bool unused;
+};
+static wait_event_data_t fake_wait_event_data;
+
+wait_event_data_t* gui_activity_make_wait_event_data(gui_activity_t* activity) { return &fake_wait_event_data; }
+
+void gui_activity_register_event(
+ gui_activity_t* activity, const char* event_base, uint32_t event_id, esp_event_handler_t handler, void* args)
+{
+}
+
+bool gui_activity_wait_event(gui_activity_t* activity, const char* event_base, uint32_t event_id,
+ esp_event_base_t* trigger_event_base, int32_t* trigger_event_id, void** trigger_event_data, TickType_t max_wait)
+{
+ if (trigger_event_id) {
+ *trigger_event_id = ESP_NO_EVENT;
+ }
+ return ESP_OK;
+}
+
+void gui_set_activity_title(gui_activity_t* activity, const char* title) {}
+
+gui_activity_t* gui_current_activity(void) { return current_activity; }
+
+gui_activity_t* gui_display_splash(void) { return gui_make_activity(); }
diff --git a/libjade/icons.inc b/libjade/icons.inc
new file mode 100644
index 0000000..7f744f4
--- /dev/null
+++ b/libjade/icons.inc
@@ -0,0 +1,70 @@
+const uint8_t _binary_statusbar_large_bin_gz_start[1297] = { 0x78, 0xda, 0xec, 0x97, 0x1, 0x64, 0x1c, 0x41, 0x14,
+ 0x40, 0xf, 0x8b, 0xc5, 0x61, 0x10, 0x0, 0x58, 0xa0, 0x14, 0x6c, 0x3, 0x4, 0xb0, 0xa, 0x80, 0x5a, 0x40, 0x1, 0xe,
+ 0x90, 0x6, 0xe7, 0x84, 0x46, 0x2c, 0xac, 0x33, 0x64, 0x9d, 0x43, 0x4, 0x70, 0xa, 0x4, 0x90, 0x2, 0x0, 0x3, 0x0,
+ 0x10, 0x0, 0x14, 0x7, 0x0, 0xc5, 0xe2, 0x30, 0xe5, 0x30, 0xf5, 0x8c, 0x6f, 0x32, 0x2c, 0x3b, 0x5d, 0x51, 0x71, 0xf2,
+ 0x47, 0xed, 0xed, 0xce, 0x9f, 0xff, 0xff, 0x7f, 0x33, 0xff, 0x4f, 0xa3, 0xff, 0x7c, 0x9c, 0xbd, 0x4b, 0xba, 0xb4,
+ 0xea, 0xe6, 0xd9, 0x1a, 0x7e, 0x9d, 0x37, 0x5f, 0xbe, 0xbd, 0xa6, 0x5d, 0x5d, 0xb6, 0x6a, 0x5c, 0x2f, 0x2b, 0xf2,
+ 0xa, 0xbd, 0xb7, 0x41, 0xc2, 0x39, 0x61, 0xf1, 0xeb, 0xe7, 0x54, 0x1a, 0xba, 0x9c, 0x2f, 0x18, 0x79, 0x95, 0x15,
+ 0xfe, 0xcb, 0xd9, 0xa3, 0x73, 0x79, 0x35, 0xbe, 0xd2, 0x1a, 0xe7, 0x58, 0xf3, 0x36, 0x48, 0xbc, 0x64, 0x31, 0x95,
+ 0x86, 0x5a, 0x3b, 0x11, 0xb7, 0xdc, 0xbc, 0x1e, 0x8b, 0xe5, 0xe6, 0xb8, 0x67, 0xf6, 0x7f, 0x91, 0x88, 0x59, 0x40,
+ 0x63, 0x1a, 0xb, 0xb5, 0xe6, 0x7c, 0x1c, 0xf7, 0x30, 0x88, 0x59, 0x50, 0x2f, 0xba, 0x1c, 0xae, 0x8c, 0x98, 0x85,
+ 0xcc, 0xa1, 0x3d, 0x34, 0x8b, 0xa5, 0xd8, 0x42, 0xf8, 0xce, 0x7b, 0x9c, 0x1f, 0xfe, 0xfd, 0x13, 0x1b, 0x29, 0x24,
+ 0x62, 0x16, 0xd3, 0x68, 0x8, 0xb, 0xcf, 0xa0, 0xab, 0x5f, 0xb2, 0xb8, 0xba, 0x6c, 0x7a, 0xbc, 0x1c, 0xf7, 0xe4,
+ 0x81, 0x67, 0x32, 0x44, 0x58, 0x21, 0xd9, 0xf6, 0xb7, 0xce, 0x6d, 0x57, 0xb3, 0xd9, 0xf5, 0x13, 0x33, 0x68, 0xb7,
+ 0x4a, 0xe2, 0xbb, 0x79, 0x9e, 0xcd, 0xe6, 0xb, 0xe6, 0x21, 0xdd, 0xaa, 0xac, 0x10, 0xb, 0xfd, 0x6d, 0x7c, 0x2a,
+ 0xb1, 0x80, 0x6f, 0x66, 0xbc, 0x95, 0xbc, 0xc2, 0x7b, 0xd3, 0xeb, 0x32, 0x81, 0x44, 0xc4, 0x62, 0x2a, 0xd, 0xa2,
+ 0x39, 0xec, 0xf2, 0xaa, 0xab, 0x8f, 0x7b, 0x4e, 0x75, 0x60, 0x91, 0x57, 0x44, 0xd4, 0xd5, 0xdb, 0x15, 0x4f, 0x76,
+ 0xee, 0xb0, 0x73, 0xee, 0xfe, 0x21, 0xaf, 0xee, 0x1f, 0x60, 0xe6, 0x59, 0x74, 0x35, 0xeb, 0x7d, 0xc6, 0xd7, 0x4f,
+ 0xba, 0xec, 0x6a, 0x6b, 0x78, 0x23, 0xf7, 0xab, 0x4b, 0xf4, 0x98, 0x69, 0x7a, 0x6b, 0xd0, 0x82, 0x57, 0x57, 0xeb,
+ 0x92, 0x1c, 0xf0, 0xa1, 0x4b, 0x3a, 0xd5, 0x7c, 0xd1, 0xf4, 0x4d, 0x2f, 0x2c, 0xf0, 0xe9, 0x9f, 0x78, 0xc2, 0x63,
+ 0xa, 0x89, 0x98, 0xc5, 0x34, 0x1a, 0xb2, 0x33, 0xc4, 0x3b, 0x5f, 0x84, 0x7e, 0x21, 0x71, 0xf3, 0x85, 0x5f, 0xf3,
+ 0x45, 0xab, 0x88, 0x2f, 0xee, 0x17, 0x79, 0x5, 0x41, 0x38, 0x41, 0xcc, 0x1a, 0x58, 0xc6, 0x35, 0x2, 0xb, 0x59, 0x97,
+ 0x15, 0x12, 0x31, 0x4, 0x39, 0x9, 0xb1, 0x76, 0xf0, 0xcd, 0x1a, 0xb5, 0x66, 0x3f, 0x20, 0x9d, 0x42, 0x22, 0x66,
+ 0x31, 0x85, 0x6, 0x2c, 0x20, 0x4f, 0xd, 0x63, 0x9b, 0xf8, 0x24, 0x1e, 0xde, 0x75, 0x19, 0xea, 0x88, 0xb8, 0xac,
+ 0x89, 0xe3, 0xe7, 0x1f, 0xbc, 0x88, 0x8d, 0x93, 0x40, 0x55, 0x40, 0x34, 0x66, 0x21, 0x55, 0x88, 0x5, 0x4e, 0x88,
+ 0x35, 0xd8, 0x26, 0x47, 0x5d, 0x5a, 0xe3, 0xeb, 0x30, 0x91, 0x45, 0x44, 0x22, 0x85, 0x45, 0x3a, 0xd, 0xc9, 0x53,
+ 0xf6, 0xea, 0xb8, 0x8f, 0x59, 0x10, 0x1f, 0x3a, 0x50, 0xd2, 0x25, 0x99, 0xc6, 0x2c, 0xfc, 0x1a, 0xe9, 0x7c, 0x9c,
+ 0x6d, 0x32, 0x83, 0xce, 0x30, 0xb, 0xb4, 0xd5, 0xda, 0x8f, 0xae, 0xce, 0x8a, 0xa6, 0xa7, 0x12, 0xb2, 0x2, 0x5f,
+ 0xa9, 0x2c, 0x84, 0x44, 0x12, 0x8b, 0x74, 0x1a, 0x11, 0xb, 0xfc, 0x63, 0x4f, 0xe2, 0xe1, 0x19, 0xf6, 0x98, 0x5e,
+ 0x4f, 0x55, 0x4b, 0xde, 0x92, 0x2d, 0x15, 0xcd, 0x5d, 0x2c, 0x42, 0xad, 0x50, 0xe1, 0x43, 0x2c, 0xa8, 0xb2, 0x60,
+ 0x41, 0x32, 0x4d, 0xaf, 0x91, 0x98, 0x44, 0x3a, 0xb, 0x68, 0xa4, 0xb1, 0x60, 0xb7, 0xf, 0x3b, 0xa2, 0xf1, 0xb9,
+ 0x93, 0x1d, 0x5d, 0x50, 0x97, 0xec, 0xe2, 0x76, 0x45, 0xc7, 0xe4, 0x3c, 0x30, 0xc3, 0x97, 0xe5, 0xc6, 0x9a, 0xed,
+ 0x4a, 0xe2, 0xa7, 0x36, 0x88, 0xbf, 0xbf, 0x65, 0xc7, 0xb9, 0x2f, 0xb0, 0x42, 0x56, 0x58, 0x39, 0xec, 0x5a, 0x15,
+ 0x58, 0x60, 0x1, 0x5b, 0x6a, 0xbd, 0xdc, 0xdc, 0x3c, 0xb7, 0xca, 0xb3, 0xc1, 0x3, 0xbe, 0x7f, 0x7f, 0x4d, 0x63,
+ 0x81, 0x76, 0x3a, 0x8b, 0x74, 0x1a, 0x78, 0xb5, 0xc6, 0xf, 0x6e, 0x8, 0xdf, 0xe1, 0x24, 0x1f, 0x7a, 0x8, 0xd1, 0xde,
+ 0x3f, 0xb4, 0x4a, 0xc8, 0x31, 0x67, 0x8d, 0x67, 0x66, 0x4d, 0xab, 0xd0, 0xb2, 0xe6, 0xec, 0x31, 0x2b, 0xa8, 0xf,
+ 0x4f, 0xcf, 0x5b, 0x61, 0x2d, 0x15, 0xd0, 0xd5, 0x5e, 0x5f, 0x68, 0x60, 0x1, 0x6e, 0xbc, 0xc1, 0x8e, 0xe, 0x82, 0xd,
+ 0x74, 0xd4, 0xda, 0x1a, 0x7a, 0x94, 0xac, 0xd1, 0x25, 0xdc, 0x53, 0x48, 0x48, 0xe7, 0xbf, 0xbb, 0x18, 0x20, 0x11,
+ 0x68, 0x9c, 0x80, 0x8c, 0x93, 0x8, 0xff, 0xb, 0x39, 0x7d, 0x1a, 0xe3, 0x24, 0x82, 0x7c, 0xf8, 0xf4, 0xfd, 0xc7, 0x8,
+ 0x8d, 0x93, 0x27, 0x11, 0xe4, 0x73, 0x31, 0x4e, 0xe3, 0xf4, 0x49, 0x9c, 0x6a, 0xa5, 0x8c, 0x93, 0x8, 0x7f, 0x45,
+ 0x9e, 0x3e, 0x8d, 0x70, 0xd7, 0xd, 0x8f, 0x70, 0x3f, 0xd, 0xd3, 0x38, 0x6f, 0x86, 0xc7, 0xdd, 0xc5, 0xec, 0x1f,
+ 0xe4, 0x5d, 0xfe, 0x76, 0x63, 0x3f, 0xa6, 0x6e, 0xc3, 0x40, 0x18, 0xc0, 0x75, 0x3a, 0xc5, 0x81, 0x8c, 0x12, 0x30,
+ 0xe0, 0x11, 0x34, 0x40, 0xb7, 0x30, 0x60, 0x30, 0x34, 0x53, 0x64, 0x81, 0x8e, 0x20, 0x8, 0x74, 0x15, 0x43, 0x0,
+ 0x8d, 0x54, 0xc2, 0xf5, 0x50, 0x22, 0x94, 0xf, 0xeb, 0x30, 0x14, 0xfa, 0x8e, 0xff, 0xfe, 0x2c, 0xcb, 0x3f, 0xe9,
+ 0x82, 0xfc, 0x66, 0xda, 0xfc, 0xab, 0x12, 0x4c, 0x49, 0x26, 0xc2, 0xc, 0x93, 0xa4, 0x4a, 0xd, 0x3e, 0x75, 0xe5,
+ 0xb5, 0x66, 0xfa, 0x37, 0x16, 0x9b, 0xff, 0x75, 0x7a, 0x15, 0x7a, 0x3e, 0x93, 0x64, 0x36, 0x8f, 0x46, 0x1a, 0x64,
+ 0xa4, 0xaa, 0x56, 0x4e, 0x1d, 0x79, 0x7d, 0xce, 0x9d, 0xae, 0xdc, 0xae, 0x3b, 0xe1, 0x39, 0x8c, 0xa1, 0x5d, 0x83,
+ 0x3f, 0xc6, 0x62, 0x65, 0xc9, 0xdc, 0x42, 0x8f, 0x85, 0x56, 0xe, 0xa9, 0xdb, 0xc2, 0xb9, 0xdf, 0xfe, 0x47, 0x68,
+ 0x97, 0x6a, 0xf4, 0x8c, 0xb8, 0xf2, 0x51, 0xfb, 0x22, 0x7, 0xc9, 0xe0, 0xd4, 0xe0, 0x55, 0x4c, 0x56, 0xa2, 0xdc,
+ 0x35, 0x6, 0x98, 0x6f, 0xae, 0x61, 0xaf, 0x86, 0x41, 0xc2, 0x60, 0xf1, 0xa0, 0x32, 0xea, 0x85, 0xa1, 0x45, 0x35,
+ 0xd2, 0xe0, 0x27, 0x60, 0x38, 0xc0, 0x27, 0xf7, 0x6a, 0x18, 0x24, 0x80, 0x5, 0xea, 0x10, 0xad, 0xe9, 0xd4, 0x63,
+ 0xa1, 0xa3, 0xab, 0x21, 0xce, 0xdb, 0x35, 0x4c, 0x12, 0x6, 0x8b, 0xe8, 0xe4, 0xfa, 0x93, 0x35, 0xd7, 0x63, 0x51,
+ 0xee, 0xc6, 0x79, 0xbb, 0x86, 0x59, 0xc2, 0x60, 0xa1, 0xcf, 0x61, 0x1a, 0x43, 0xbb, 0xf7, 0xf1, 0xbb, 0x49, 0x97,
+ 0xfc, 0xc, 0x38, 0x6f, 0xd7, 0x30, 0x4b, 0x18, 0x2c, 0x44, 0x20, 0x7, 0xe7, 0x16, 0x2f, 0x5d, 0x12, 0xbb, 0x2c,
+ 0xb2, 0xdc, 0xf, 0x2c, 0x56, 0xfe, 0x3c, 0x5d, 0xc4, 0xdd, 0x1a, 0x57, 0x96, 0x19, 0x1a, 0x24, 0xc, 0x16, 0xc9,
+ 0xc9, 0xd5, 0xc5, 0x97, 0xfd, 0xbe, 0xf8, 0x63, 0x2d, 0xea, 0x4a, 0x60, 0x6f, 0x0, 0x8b, 0x3e, 0x9, 0x83, 0xc5,
+ 0xe2, 0xdf, 0xe7, 0x87, 0xba, 0xe4, 0x78, 0xb, 0xac, 0x81, 0x2d, 0xb0, 0x84, 0xcd, 0x22, 0x7, 0xed, 0xf6, 0x4a,
+ 0xe6, 0x40, 0x8b, 0x1c, 0x3e, 0x4b, 0x47, 0x47, 0x1a, 0xd8, 0xc2, 0x26, 0x81, 0x2d, 0x66, 0x92, 0x6b, 0x7a, 0x7e,
+ 0xd5, 0x2e, 0xd9, 0xfc, 0x21, 0x16, 0xe0, 0xc9, 0x58, 0x3, 0x5b, 0x58, 0x25, 0xb0, 0xc5, 0xca, 0xf5, 0xaf, 0xdb,
+ 0xf4, 0x77, 0x25, 0xf7, 0x5b, 0xe8, 0xc9, 0x13, 0xe7, 0x3b, 0x35, 0x80, 0x85, 0x5d, 0xc2, 0xb9, 0xb, 0x38, 0x37,
+ 0xe4, 0x46, 0x27, 0xf6, 0x9d, 0x23, 0x99, 0xac, 0xe7, 0xb, 0xa0, 0x1, 0x2c, 0x8c, 0x12, 0xd5, 0xfb, 0x46, 0x78,
+ 0xf6, 0x2e, 0xd5, 0x77, 0x8e, 0x7c, 0xb2, 0x76, 0x19, 0xce, 0xdb, 0x34, 0xd4, 0xc2, 0x2e, 0x11, 0xdd, 0x2d, 0xc8,
+ 0xff, 0x2b, 0x98, 0x74, 0xae, 0x2b, 0xa3, 0xe, 0x79, 0xaf, 0x1c, 0xbe, 0x9d, 0x32, 0xf4, 0xdd, 0xce, 0x3e, 0xb9,
+ 0x57, 0x3d, 0xa8, 0xcc, 0x32, 0x69, 0x8, 0xe4, 0x4b, 0xc5, 0x9d, 0x1a, 0xc5, 0xc2, 0x2e, 0x51, 0xaf, 0xf2, 0xd4,
+ 0x98, 0x6b, 0xfa, 0xd2, 0xe9, 0xcb, 0xd7, 0xd5, 0xc4, 0xdf, 0xe0, 0xce, 0x19, 0xf3, 0x40, 0x43, 0x2d, 0x2c, 0x12,
+ 0xf5, 0xc, 0x54, 0xe2, 0xc, 0x76, 0x70, 0xbd, 0xbb, 0x63, 0x6d, 0x4, 0x46, 0xc6, 0x6f, 0x66, 0xb7, 0x50, 0xd, 0xb5,
+ 0xb0, 0xff, 0x4e, 0x3c, 0xe8, 0x16, 0x8a, 0xc3, 0x85, 0x13, 0x38, 0x7b, 0xb7, 0x76, 0xb7, 0x5e, 0x89, 0x3b, 0x94,
+ 0xc7, 0xa0, 0xfb, 0xc7, 0x6c, 0x1, 0x34, 0xd4, 0xc2, 0x2a, 0xa1, 0x1d, 0x30, 0xd3, 0x4c, 0x4c, 0x11, 0x24, 0xa4,
+ 0x5a, 0x5d, 0xd6, 0xee, 0xea, 0xa8, 0xf7, 0x80, 0x7b, 0x71, 0x5e, 0x2b, 0xee, 0x7c, 0xb, 0xfd, 0x36, 0x73, 0xff,
+ 0xf7, 0xdf, 0x1f, 0x5f, 0xe9, 0x38, 0xe5 };
+const uint8_t* _binary_statusbar_large_bin_gz_end = _binary_statusbar_large_bin_gz_start + 1297;
diff --git a/libjade/include/arch/sys_arch.h b/libjade/include/arch/sys_arch.h
new file mode 100644
index 0000000..e69de29
diff --git a/libjade/include/driver/gpio.h b/libjade/include/driver/gpio.h
new file mode 100644
index 0000000..e69de29
diff --git a/libjade/include/driver/spi_master.h b/libjade/include/driver/spi_master.h
new file mode 100644
index 0000000..e69de29
diff --git a/libjade/include/esp_app_desc.h b/libjade/include/esp_app_desc.h
new file mode 100644
index 0000000..17184e9
--- /dev/null
+++ b/libjade/include/esp_app_desc.h
@@ -0,0 +1,9 @@
+#ifndef _LIBJADE_ESP_APP_DESC_H_
+#define _LIBJADE_ESP_APP_DESC_H_ 1
+
+typedef struct esp_app_desc {
+ char version[32];
+ int secure_version;
+} esp_app_desc_t;
+
+#endif // _LIBJADE_ESP_APP_DESC_H_
diff --git a/libjade/include/esp_app_format.h b/libjade/include/esp_app_format.h
new file mode 100644
index 0000000..048e976
--- /dev/null
+++ b/libjade/include/esp_app_format.h
@@ -0,0 +1,4 @@
+#ifndef __LIBJADE_ESP_APP_FORMAT__
+#define __LIBJADE_ESP_APP_FORMAT__ 1
+
+#endif // __LIBJADE_ESP_APP_FORMAT__
diff --git a/libjade/include/esp_camera.h b/libjade/include/esp_camera.h
new file mode 100644
index 0000000..e69de29
diff --git a/libjade/include/esp_chip_info.h b/libjade/include/esp_chip_info.h
new file mode 100644
index 0000000..7ffaba0
--- /dev/null
+++ b/libjade/include/esp_chip_info.h
@@ -0,0 +1,10 @@
+#ifndef _LIBJADE_ESP_CHIP_INFO_H_
+#define _LIBJADE_ESP_CHIP_INFO_H_ 1
+
+typedef struct {
+ uint32_t features;
+} esp_chip_info_t;
+
+void esp_chip_info(esp_chip_info_t* out);
+
+#endif // _LIBJADE_ESP_CHIP_INFO_H_
diff --git a/libjade/include/esp_crc.h b/libjade/include/esp_crc.h
new file mode 100644
index 0000000..2fe3625
--- /dev/null
+++ b/libjade/include/esp_crc.h
@@ -0,0 +1,8 @@
+#ifndef _LIBJADE_ESP_CRC_H
+#define _LIBJADE_ESP_CRC_H 1
+
+#include <zlib.h>
+
+static inline uint32_t esp_crc32_le(uint32_t crc, uint8_t const* buf, uint32_t len) { return crc32(crc, buf, len); }
+
+#endif // _LIBJADE_ESP_CRC_H
diff --git a/libjade/include/esp_efuse.h b/libjade/include/esp_efuse.h
new file mode 100644
index 0000000..e69de29
diff --git a/libjade/include/esp_err.h b/libjade/include/esp_err.h
new file mode 100644
index 0000000..78bf174
--- /dev/null
+++ b/libjade/include/esp_err.h
@@ -0,0 +1,13 @@
+#ifndef __LIBJADE_ESP_ERR__
+#define __LIBJADE_ESP_ERR__ 1
+
+typedef int esp_err_t;
+
+#define ESP_OK 0
+#define ESP_FAIL 1
+#define ESP_ERR_INVALID_ARG 2
+#define ESP_ERR_NVS_NOT_FOUND 3
+#define ESP_ERR_NVS_NO_FREE_PAGES 4
+#define ESP_ERR_NVS_NEW_VERSION_FOUND 5
+
+#endif // __LIBJADE_ESP_ERR__
diff --git a/libjade/include/esp_event.h b/libjade/include/esp_event.h
new file mode 100644
index 0000000..9b7012b
--- /dev/null
+++ b/libjade/include/esp_event.h
@@ -0,0 +1,44 @@
+#ifndef _LIBJADE_ESP_EVENT_H_
+#define _LIBJADE_ESP_EVENT_H_ 1
+
+#include <esp_err.h>
+#include <freertos/task.h>
+
+typedef const char* esp_event_base_t;
+typedef void* esp_event_handler_t;
+typedef void* esp_event_handler_instance_t;
+
+typedef void* esp_event_loop_handle_t;
+
+#define ESP_EVENT_DECLARE_BASE(id) extern esp_event_base_t const id
+#define ESP_EVENT_DEFINE_BASE(id) esp_event_base_t const id = #id
+
+#define ESP_EVENT_ANY_BASE NULL
+#define ESP_EVENT_ANY_ID -1
+
+static inline esp_err_t esp_event_loop_create_default(void) { return ESP_OK; }
+
+static inline esp_err_t esp_event_post(
+ esp_event_base_t event_base, int32_t event_id, void* event_data, size_t event_data_size, TickType_t ticks_to_wait)
+{
+ return ESP_OK;
+}
+
+static inline esp_err_t esp_event_handler_instance_register(esp_event_base_t event_base, int32_t event_id,
+ esp_event_handler_t event_handler, void* event_handler_arg, esp_event_handler_instance_t* instance)
+{
+ return ESP_OK;
+}
+
+static inline esp_err_t esp_event_handler_unregister(
+ esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler)
+{
+ return ESP_OK;
+}
+
+static inline esp_err_t esp_event_handler_instance_unregister(
+ esp_event_base_t event_base, int32_t event_id, esp_event_handler_instance_t instance)
+{
+ return ESP_OK;
+}
+#endif // _LIBJADE_ESP_EVENT_H_
diff --git a/libjade/include/esp_heap_caps.h b/libjade/include/esp_heap_caps.h
new file mode 100644
index 0000000..4ad6d85
--- /dev/null
+++ b/libjade/include/esp_heap_caps.h
@@ -0,0 +1,27 @@
+#ifndef _LIBJADE_ESP_HEAP_CAPS_H
+#define _LIBJADE_ESP_HEAP_CAPS_H 1
+
+#define MALLOC_CAP_DEFAULT 1
+#define MALLOC_CAP_SPIRAM 2
+#define MALLOC_CAP_INTERNAL 4
+
+static inline void* heap_caps_malloc(size_t size, uint32_t caps) { return malloc(size); }
+static inline void* heap_caps_malloc_prefer(size_t size, uint32_t caps, ...) { return malloc(size); }
+static inline void* heap_caps_calloc(size_t num_elems, size_t size, uint32_t caps) { return calloc(num_elems, size); }
+static inline void* heap_caps_calloc_prefer(size_t num_elems, size_t size, uint32_t caps, ...)
+{
+ return calloc(num_elems, size);
+}
+static inline void* heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps) { return malloc(size); }
+static inline uint32_t heap_caps_get_free_size(uint32_t caps)
+{
+#ifndef CONFIG_SPIRAM
+ if (caps == (MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM)) {
+ return 0;
+ }
+#endif
+ return 0xffffffff;
+}
+static inline uint32_t heap_caps_get_largest_free_block(uint32_t caps) { return heap_caps_get_free_size(caps); }
+
+#endif // _LIBJADE_ESP_HEAP_CAPS_H
diff --git a/libjade/include/esp_idf_version.h b/libjade/include/esp_idf_version.h
new file mode 100644
index 0000000..634d3d7
--- /dev/null
+++ b/libjade/include/esp_idf_version.h
@@ -0,0 +1,6 @@
+#ifndef _LIBJADE_ESP_IDF_VERSION_H_
+#define _LIBJADE_ESP_IDF_VERSION_H_ 1
+
+const char* esp_get_idf_version(void);
+
+#endif // _LIBJADE_ESP_IDF_VERSION_H_
diff --git a/libjade/include/esp_log.h b/libjade/include/esp_log.h
new file mode 100644
index 0000000..058748a
--- /dev/null
+++ b/libjade/include/esp_log.h
@@ -0,0 +1,44 @@
+#ifndef __LIBJADE_ESP_LOG__
+#define __LIBJADE_ESP_LOG__ 1
+
+#include <stdio.h>
+
+typedef enum {
+ ESP_LOG_VERBOSE = 0,
+ ESP_LOG_DEBUG = 1,
+ ESP_LOG_INFO = 2,
+ ESP_LOG_WARN = 3,
+ ESP_LOG_ERROR = 4,
+ ESP_LOG_NONE = 5
+} esp_log_level_t;
+
+extern esp_log_level_t _libjade_log_level;
+
+#define ESP_LOGD(f, fmt, ...) \
+ do { \
+ if (_libjade_log_level <= ESP_LOG_DEBUG) \
+ fprintf(stderr, f ":" fmt "\n", __VA_ARGS__); \
+ } while (0)
+#define ESP_LOGI(f, fmt, ...) \
+ do { \
+ if (_libjade_log_level <= ESP_LOG_INFO) \
+ fprintf(stderr, f ":" fmt "\n", __VA_ARGS__); \
+ } while (0)
+#define ESP_LOGW(f, fmt, ...) \
+ do { \
+ if (_libjade_log_level <= ESP_LOG_WARN) \
+ fprintf(stderr, f ":" fmt "\n", __VA_ARGS__); \
+ } while (0)
+#define ESP_LOGE(f, fmt, ...) \
+ do { \
+ if (_libjade_log_level <= ESP_LOG_ERROR) \
+ fprintf(stderr, f ":" fmt "\n", __VA_ARGS__); \
+ } while (0)
+
+static inline void esp_log_level_set(const char* tag, esp_log_level_t level)
+{
+ // Do nothing, so our internal call to this function doesn't
+ // overwrite the callers desired log level
+}
+
+#endif // __LIBJADE_ESP_LOG__
diff --git a/libjade/include/esp_mac.h b/libjade/include/esp_mac.h
new file mode 100644
index 0000000..0d171e5
--- /dev/null
+++ b/libjade/include/esp_mac.h
@@ -0,0 +1,8 @@
+#ifndef _LIBJADE_ESP_MAC_H_
+#define _LIBJADE_ESP_MAC_H_ 1
+
+#include <esp_err.h>
+
+esp_err_t esp_efuse_mac_get_default(uint8_t* out);
+
+#endif // _LIBJADE_ESP_MAC_H_
diff --git a/libjade/include/esp_ota_ops.h b/libjade/include/esp_ota_ops.h
new file mode 100644
index 0000000..d2f6037
--- /dev/null
+++ b/libjade/include/esp_ota_ops.h
@@ -0,0 +1,38 @@
+#ifndef _LIBJADE_ESP_OTA_OPS_H_
+#define _LIBJADE_ESP_OTA_OPS_H_ 1
+
+#include <esp_partition.h>
+
+typedef void* esp_ota_handle_t;
+typedef int esp_ota_img_states_t;
+
+#define ESP_OTA_IMG_PENDING_VERIFY 1
+
+static inline esp_err_t esp_ota_mark_app_valid_cancel_rollback(void) { return ESP_OK; };
+
+static inline esp_err_t esp_ota_write(esp_ota_handle_t p, const void* data, size_t size) { return ESP_OK; }
+
+static inline esp_err_t esp_ota_abort(esp_ota_handle_t p) { return ESP_OK; }
+
+static inline esp_err_t esp_ota_begin(const esp_partition_t* p, size_t size, esp_ota_handle_t* out) { return ESP_OK; }
+
+static inline esp_err_t esp_ota_end(esp_ota_handle_t p) { return ESP_OK; }
+
+static inline const esp_partition_t* esp_ota_get_boot_partition(void) { return NULL; }
+static inline esp_err_t esp_ota_set_boot_partition(const esp_partition_t* p) { return ESP_OK; }
+
+static inline esp_err_t esp_ota_get_state_partition(const esp_partition_t* p, esp_ota_img_states_t* out)
+{
+ return ESP_OK;
+}
+
+static inline const esp_partition_t* esp_ota_get_running_partition(void) { return NULL; }
+
+static inline const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t* start) { return NULL; }
+
+static inline esp_err_t esp_ota_get_partition_description(const esp_partition_t* p, esp_app_desc_t* out)
+{
+ return ESP_OK;
+}
+
+#endif // _LIBJADE_ESP_OTA_OPS_H_
diff --git a/libjade/include/esp_partition.h b/libjade/include/esp_partition.h
new file mode 100644
index 0000000..6964b9f
--- /dev/null
+++ b/libjade/include/esp_partition.h
@@ -0,0 +1,22 @@
+#ifndef __LIBJADE_ESP_PARTITION__
+#define __LIBJADE_ESP_PARTITION__ 1
+
+#include <esp_err.h>
+
+typedef struct esp_partition {
+ size_t size;
+} esp_partition_t;
+
+typedef struct esp_image_header {
+ int chip_id;
+} esp_image_header_t;
+typedef void* esp_image_segment_header_t;
+
+static inline esp_err_t esp_partition_read(const esp_partition_t* p, size_t offset, void* dst, size_t size)
+{
+ return ESP_OK;
+}
+
+// TODO: This belongs in esp_efuse.h if we ever want to mock it properly
+static inline bool esp_efuse_check_secure_version(uint32_t version) { return true; }
+#endif // __LIBJADE_ESP_PARTITION__
diff --git a/libjade/include/esp_sleep.h b/libjade/include/esp_sleep.h
new file mode 100644
index 0000000..3ca6f9e
--- /dev/null
+++ b/libjade/include/esp_sleep.h
@@ -0,0 +1,6 @@
+#ifndef _LIBJADE_ESP_SLEEP_H_
+#define _LIBJADE_ESP_SLEEP_H_ 1
+
+void esp_deep_sleep_start(void);
+
+#endif // _LIBJADE_ESP_SLEEP_H_
diff --git a/libjade/include/esp_system.h b/libjade/include/esp_system.h
new file mode 100644
index 0000000..ef405cf
--- /dev/null
+++ b/libjade/include/esp_system.h
@@ -0,0 +1,6 @@
+#ifndef _LIBJADE_ESP_SYSTEM_H_
+#define _LIBJADE_ESP_SYSTEM_H_ 1
+
+void esp_restart();
+
+#endif // _LIBJADE_ESP_SYSTEM_H_
diff --git a/libjade/include/freertos/FreeRTOS.h b/libjade/include/freertos/FreeRTOS.h
new file mode 100644
index 0000000..0cbe337
--- /dev/null
+++ b/libjade/include/freertos/FreeRTOS.h
@@ -0,0 +1,6 @@
+#ifndef _LIBJADE_FREERTOS_FREERTOS_H
+#define _LIBJADE_FREERTOS_FREERTOS_H 1
+
+#define CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS 3
+
+#endif // _LIBJADE_FREERTOS_FREERTOS_H
diff --git a/libjade/include/freertos/idf_additions.h b/libjade/include/freertos/idf_additions.h
new file mode 100644
index 0000000..e69de29
diff --git a/libjade/include/freertos/projdefs.h b/libjade/include/freertos/projdefs.h
new file mode 100644
index 0000000..d315c67
--- /dev/null
+++ b/libjade/include/freertos/projdefs.h
@@ -0,0 +1,13 @@
+#ifndef _LIBJADE_FREERTOS_PROJDEFS_H
+#define _LIBJADE_FREERTOS_PROJDEFS_H 1
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define pdFALSE 0
+#define pdTRUE 1
+#define pdPASS 1 // Same as TRUE
+
+#define portMAX_DELAY 0xfffffffF
+
+#endif // _LIBJADE_FREERTOS_PROJDEFS_H
diff --git a/libjade/include/freertos/ringbuf.h b/libjade/include/freertos/ringbuf.h
new file mode 100644
index 0000000..ce18562
--- /dev/null
+++ b/libjade/include/freertos/ringbuf.h
@@ -0,0 +1,129 @@
+#ifndef _LIBJADE_FREERTOS_RINGBUF_H_
+#define _LIBJADE_FREERTOS_RINGBUF_H_ 1
+
+#include <freertos/projdefs.h>
+#include <pthread.h>
+#include <time.h>
+#include <wally_map.h>
+
+typedef struct Ringbuffer {
+ struct wally_map m;
+ pthread_mutex_t mutex; // We should use a semaphore to wake at some point
+} * RingbufHandle_t;
+
+typedef struct Ringbuffer StaticRingbuffer_t;
+
+#define RINGBUF_TYPE_NOSPLIT 0
+
+static inline RingbufHandle_t xRingbufferCreateStatic(
+ size_t buff_len, int buff_type, uint8_t* storage, StaticRingbuffer_t* out)
+{
+ free(storage); // Jade uses an allocated buffer, which we don't need
+ int ret = wally_map_init(1000, NULL, &out->m);
+ if (ret != WALLY_OK) {
+ abort();
+ }
+ ret = pthread_mutex_init(&out->mutex, NULL);
+ if (ret) {
+ abort();
+ }
+ return out;
+}
+
+static inline RingbufHandle_t xRingbufferCreate(size_t buff_len, int buff_type)
+{
+ RingbufHandle_t out = malloc(sizeof(struct Ringbuffer));
+ if (!out) {
+ abort();
+ }
+ int ret = wally_map_init(buff_len, NULL, &out->m);
+ if (ret != WALLY_OK) {
+ abort();
+ }
+ ret = pthread_mutex_init(&out->mutex, NULL);
+ if (ret) {
+ abort();
+ }
+ return out;
+}
+
+static inline void vRingbufferDelete(RingbufHandle_t rb)
+{
+ struct wally_map* m = &rb->m;
+ int ret = pthread_mutex_destroy(&rb->mutex);
+ if (ret) {
+ abort();
+ }
+ ret = wally_map_clear(m);
+ if (ret) {
+ abort();
+ }
+}
+
+static inline size_t xRingbufferGetMaxItemSize(RingbufHandle_t rb)
+{
+ return 0xffffffff; // ~4GB
+}
+
+static inline int xRingbufferSend(RingbufHandle_t rb, const void* item, size_t item_len, int wait_ticks)
+{
+ struct wally_map* m = &rb->m;
+
+ int ret = pthread_mutex_lock(&rb->mutex);
+ if (ret) {
+ abort();
+ }
+ // Note an integer key in a wally map is denoted by:
+ // { key = NULL, key_len = integer key value }
+ // We increment the key for each message sent.
+ const uint32_t cur_idx = m->num_items ? m->items[m->num_items - 1].key_len : 0;
+ ret = wally_map_add_integer(m, cur_idx + 1, item, item_len);
+ if (ret != WALLY_OK) {
+ abort();
+ }
+ ret = pthread_mutex_unlock(&rb->mutex);
+ if (ret) {
+ abort();
+ }
+ return pdTRUE;
+}
+
+static inline void* xRingbufferReceive(RingbufHandle_t rb, size_t* item_len_out, int wait_ticks)
+{
+ struct wally_map* m = &rb->m;
+ void* value = NULL;
+
+ if (!wait_ticks) {
+ wait_ticks = 1;
+ }
+ while (!value && wait_ticks--) {
+ int ret = pthread_mutex_lock(&rb->mutex);
+ if (ret) {
+ abort();
+ }
+ if (m->num_items) {
+ // Steal the first item from the wally_map to avoid copying
+ *item_len_out = m->items[0].value_len;
+ value = m->items[0].value;
+ m->items[0].value = NULL;
+ m->items[0].value_len = 0;
+ ret = wally_map_remove_integer(m, m->items[0].key_len);
+ if (ret != WALLY_OK) {
+ abort();
+ }
+ }
+ ret = pthread_mutex_unlock(&rb->mutex);
+ if (ret) {
+ abort();
+ }
+ if (!value && wait_ticks) {
+ struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
+ nanosleep(&ts, &ts);
+ // FIXME: Loop sleeping if sleep interrupted
+ }
+ }
+ return value;
+}
+
+static inline void vRingbufferReturnItem(RingbufHandle_t rb, void* item) { wally_free(item); }
+#endif // _LIBJADE_FREERTOS_RINGBUF_H_
diff --git a/libjade/include/freertos/semphr.h b/libjade/include/freertos/semphr.h
new file mode 100644
index 0000000..66d08e3
--- /dev/null
+++ b/libjade/include/freertos/semphr.h
@@ -0,0 +1,55 @@
+#ifndef _LIBJADE_FREERTOS_SEMPHR_H_
+#define _LIBJADE_FREERTOS_SEMPHR_H_ 1
+
+#include <freertos/projdefs.h>
+#include <pthread.h>
+#include <time.h>
+
+typedef struct Semaphore {
+ pthread_mutex_t mutex;
+} * SemaphoreHandle_t;
+
+static inline SemaphoreHandle_t xSemaphoreCreateMutex(void)
+{
+ SemaphoreHandle_t out = malloc(sizeof(struct Semaphore));
+ if (!out) {
+ abort();
+ }
+ int ret = pthread_mutex_init(&out->mutex, NULL);
+ if (ret) {
+ abort();
+ }
+ return out;
+}
+
+static inline SemaphoreHandle_t xSemaphoreCreateBinary(void)
+{
+ // FIXME: Create an actual signal-able semaphore, this is just
+ // a stub.
+ return xSemaphoreCreateMutex();
+}
+
+static inline void vSemaphoreDelete(SemaphoreHandle_t s)
+{
+ int ret = pthread_mutex_destroy(&s->mutex);
+ if (ret) {
+ abort();
+ }
+ free(s);
+}
+
+int xSemaphoreTake(SemaphoreHandle_t s, int timeout)
+{
+ // FIXME: timeout
+ return pthread_mutex_lock(&s->mutex) ? pdFALSE : pdTRUE;
+}
+
+void xSemaphoreGive(SemaphoreHandle_t s)
+{
+ int ret = pthread_mutex_unlock(&s->mutex);
+ if (ret) {
+ abort();
+ }
+}
+
+#endif // _LIBJADE_FREERTOS_SEMPHR_H_
diff --git a/libjade/include/freertos/task.h b/libjade/include/freertos/task.h
new file mode 100644
index 0000000..9283734
--- /dev/null
+++ b/libjade/include/freertos/task.h
@@ -0,0 +1,46 @@
+#ifndef _LIBJADE_FREERTOS_TASK_H
+#define _LIBJADE_FREERTOS_TASK_H 1
+
+#include <freertos/projdefs.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef uint32_t BaseType_t; // Probably belongs elsewhere
+
+typedef void* TaskHandle_t;
+typedef void (*TaskFunction_t)(void* arg);
+
+#define tskIDLE_PRIORITY 1
+
+typedef unsigned long long TickType_t;
+
+typedef void (*TlsDeleteCallbackFunction_t)(int idx, void* p);
+
+void* pvTaskGetThreadLocalStoragePointer(void* task, size_t idx);
+
+void vTaskSetThreadLocalStoragePointerAndDelCallback(void* task, size_t idx, void* p, TlsDeleteCallbackFunction_t cb);
+
+const char* pcTaskGetName(void* task);
+
+TaskHandle_t xTaskGetCurrentTaskHandle(void) { return NULL; }
+
+BaseType_t xTaskCreatePinnedToCore(TaskFunction_t func, const char* name, uint32_t stack_size, void* params,
+ uint32_t ux_prio, TaskHandle_t* output, uint32_t xCoreID);
+
+unsigned int uxTaskPriorityGet(void* task) { return 0; }
+unsigned int uxTaskGetStackHighWaterMark(void* task);
+
+unsigned int xPortGetCoreID(void) { return 0; }
+unsigned int xPortGetFreeHeapSize(void);
+
+#define portTICK_PERIOD_MS 1
+void vTaskDelay(TickType_t delay);
+void vTaskDelayUntil(TickType_t* prev_wake_time, const TickType_t delay);
+void vTaskDelete(void* task);
+TickType_t xTaskGetTickCount(void);
+
+#define eNoAction 0
+
+int xTaskNotify(TaskHandle_t task, unsigned int v, int action);
+
+#endif // _LIBJADE_FREERTOS_TASK_H
diff --git a/libjade/include/jlocale.h b/libjade/include/jlocale.h
new file mode 100644
index 0000000..35760d6
--- /dev/null
+++ b/libjade/include/jlocale.h
@@ -0,0 +1,7 @@
+#ifndef _LIBJADE_JLOCALE_H_
+#define _LIBJADE_JLOCALE_H_ 1
+
+typedef int jlocale_t;
+#define LOCALE_EN 0
+
+#endif // _LIBJADE_JLOCALE_H_
diff --git a/libjade/include/nvs.h b/libjade/include/nvs.h
new file mode 100644
index 0000000..3769a09
--- /dev/null
+++ b/libjade/include/nvs.h
@@ -0,0 +1,6 @@
+#ifndef _LIBJADE_NVS_H_
+#define _LIBJADE_NVS_H_ 1
+
+#define NVS_KEY_NAME_MAX_SIZE 16
+
+#endif
diff --git a/libjade/include/nvs_flash.h b/libjade/include/nvs_flash.h
new file mode 100644
index 0000000..27a6414
--- /dev/null
+++ b/libjade/include/nvs_flash.h
@@ -0,0 +1,62 @@
+#ifndef _LIBJADE_NVS_FLASH_H_
+#define _LIBJADE_NVS_FLASH_H_ 1
+
+#include <esp_err.h>
+
+typedef int nvs_open_mode_t;
+#define NVS_READONLY 0
+#define NVS_READWRITE 1
+
+typedef struct wally_map* nvs_handle_t;
+typedef nvs_handle_t nvs_handle;
+
+typedef struct nvs_iterator {
+ struct wally_map* m;
+ size_t idx;
+} * nvs_iterator_t;
+
+#define NVS_DEFAULT_PART_NAME ""
+
+typedef enum nvs_type { NVS_TYPE_STR, NVS_TYPE_BLOB } nvs_type_t;
+
+#define NVS_NS_NAME_MAX_SIZE 16
+#define NVS_KEY_NAME_MAX_SIZE 16
+
+typedef struct nvs_entry_info {
+ char namespace_name[NVS_NS_NAME_MAX_SIZE];
+ char key[NVS_KEY_NAME_MAX_SIZE];
+ nvs_type_t type;
+} nvs_entry_info_t;
+
+typedef struct nvs_stats {
+ size_t used_entries;
+ size_t free_entries;
+} nvs_stats_t;
+
+esp_err_t nvs_flash_init(void);
+esp_err_t nvs_flash_erase(void);
+esp_err_t nvs_get_stats(const char* part_name, nvs_stats_t* nvs_stats);
+
+esp_err_t nvs_open(const char* ns, nvs_open_mode_t open_mode, nvs_handle_t* out_handle);
+
+static inline void nvs_close(nvs_handle_t handle) {}
+
+static inline esp_err_t nvs_commit(nvs_handle_t handle) { return ESP_OK; }
+
+esp_err_t nvs_erase_key(nvs_handle_t handle, const char* key);
+
+esp_err_t nvs_set_blob(nvs_handle_t handle, const char* key, const void* value, size_t length);
+esp_err_t nvs_get_blob(nvs_handle_t handle, const char* key, void* out_value, size_t* length);
+
+esp_err_t nvs_set_str(nvs_handle_t handle, const char* key, const char* value);
+esp_err_t nvs_get_str(nvs_handle_t handle, const char* key, char* out_value, size_t* length);
+
+esp_err_t nvs_set_u32(nvs_handle_t handle, const char* key, uint32_t value);
+esp_err_t nvs_get_u32(nvs_handle_t handle, const char* key, uint32_t* out_value);
+
+esp_err_t nvs_entry_find(const char* part_name, const char* ns, nvs_type_t type, nvs_iterator_t* output_iterator);
+esp_err_t nvs_entry_next(nvs_iterator_t* iterator);
+esp_err_t nvs_entry_info(const nvs_iterator_t iterator, nvs_entry_info_t* out_info);
+void nvs_release_iterator(nvs_iterator_t iterator);
+
+#endif // _LIBJADE_NVS_FLASH_H_
diff --git a/libjade/include/sdkconfig.h b/libjade/include/sdkconfig.h
new file mode 100644
index 0000000..dc1abd6
--- /dev/null
+++ b/libjade/include/sdkconfig.h
@@ -0,0 +1,42 @@
+#ifndef _LIBJADE_SDKCONFIG_H_
+#define _LIBJADE_SDKCONFIG_H_ 1
+
+// Config defines for a software Jade device
+
+// Export debug mode functions for testing
+#define CONFIG_DEBUG_MODE 1
+
+// Auto "press" OK buttons when tasks are run (after 1ms)
+#define CONFIG_DEBUG_UNATTENDED_CI 1
+#define CONFIG_DEBUG_UNATTENDED_CI_TIMEOUT_MS 1
+
+// Default to no logging
+#define CONFIG_LOG_DEFAULT_LEVEL_NONE
+
+// Tell the firmware code we are building libjade
+#define CONFIG_LIBJADE 1
+
+// libjade currently has no GUI support
+#define CONFIG_LIBJADE_NO_GUI 1
+
+// Users can define CONFIG_LIBJADE_NO_SPIRAM to disable SPIRAM emulation
+// (e.g. to allow testing DIY devices)
+#ifndef CONFIG_LIBJADE_NO_SPIRAM
+#define CONFIG_SPIRAM 1
+#endif // CONFIG_LIBJADE_NO_SPIRAM
+
+// Provide values in order to compile (we don't actually have a screen)
+// FIXME: Allow defaulting to the values for Jade v1 and v2
+#define CONFIG_DISPLAY_WIDTH 320
+#define CONFIG_DISPLAY_HEIGHT 200
+#define CONFIG_DISPLAY_OFFSET_X 0
+#define CONFIG_DISPLAY_OFFSET_Y 0
+#define CONFIG_DISPLAY_FULL_FRAME_BUFFER 1
+#define CONFIG_DISPLAY_FULL_FRAME_BUFFER_DOUBLE 1
+
+// libjade has no camera, but supports the debug scan_qr message
+#define CONFIG_HAS_CAMERA 1
+
+#define CONFIG_IDF_FIRMWARE_CHIP_ID 0 // Needed to build
+
+#endif // _LIBJADE_SDKCONFIG_H_
diff --git a/libjade/include/sodium/crypto_verify_32.h b/libjade/include/sodium/crypto_verify_32.h
new file mode 100644
index 0000000..2cad960
--- /dev/null
+++ b/libjade/include/sodium/crypto_verify_32.h
@@ -0,0 +1,6 @@
+#ifndef _LIBJADE_SODIUM_CRYPTO_VERIFY_32_H_
+#define _LIBJADE_SODIUM_CRYPTO_VERIFY_32_H_ 1
+
+static inline int crypto_verify_32(const unsigned char* x, const unsigned char* y) { return memcmp(x, y, 32); }
+
+#endif // _LIBJADE_SODIUM_CRYPTO_VERIFY_32_H_
diff --git a/libjade/include/sodium/crypto_verify_64.h b/libjade/include/sodium/crypto_verify_64.h
new file mode 100644
index 0000000..93fee60
--- /dev/null
+++ b/libjade/include/sodium/crypto_verify_64.h
@@ -0,0 +1,6 @@
+#ifndef _LIBJADE_SODIUM_CRYPTO_VERIFY_64_H_
+#define _LIBJADE_SODIUM_CRYPTO_VERIFY_64_H_ 1
+
+static inline int crypto_verify_64(const unsigned char* x, const unsigned char* y) { return memcmp(x, y, 64); }
+
+#endif // _LIBJADE_SODIUM_CRYPTO_VERIFY_64_H_
diff --git a/libjade/include/sodium/utils.h b/libjade/include/sodium/utils.h
new file mode 100644
index 0000000..63165e7
--- /dev/null
+++ b/libjade/include/sodium/utils.h
@@ -0,0 +1,6 @@
+#ifndef _LIBJADE_SODIUM_UTILS_H_
+#define _LIBJADE_SODIUM_UTILS_H_ 1
+
+static inline int sodium_memcmp(const void* p1, const void* p2, size_t n) { return memcmp(p1, p2, n); };
+
+#endif // _LIBJADE_SODIUM_UTILS_H_
diff --git a/libjade/libjade.c b/libjade/libjade.c
new file mode 100644
index 0000000..c674cbf
--- /dev/null
+++ b/libjade/libjade.c
@@ -0,0 +1,570 @@
+// A single source file containing a local implementation of the Jade Firmware
+//
+// This hack is designed for local development, debugging and testing.
+//
+// WARNING: THIS CODE IS NOT SUITABLE FOR PROCESSING REAL DATA.
+// DO NOT USE THIS CODE FOR ANY PURPOSE WITH NON-TEST DATA.
+// DOING SO IS INSECURE AND MAY RESULT IN THE LOSS OF FUNDS!
+//
+// Includes the entire Jade firmware code, replacing the GUI and most
+// of the OS support code.
+// This file can be compiled to a shared library which implements an
+// in-processes software Jade emulator/virtual Jade device.
+// The exposed API allows passing and fetching messages using the same
+// binary format that would be passed to a real device by serial/bluetooth.
+//
+#define _GNU_SOURCE 1 // FIXME: needed for strcasestr in qrmode.c
+#include <string.h>
+#undef _GNU_SOURCE
+#include "sdkconfig.h"
+
+#include "libjade.h"
+
+#include "icons.inc"
+
+// Prevent secp symbols being externally visible in our final shared library
+#define SECP256K1_API
+
+// Address sanitizer doesn't like calling sha256 into a non-aligned
+// buffer, even though its technically legal (but slower). Force
+// wally to handle unaligned destination buffers internally to work
+// around this.
+#define HAVE_UNALIGNED_ACCESS 0
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <sys/random.h>
+#include <sys/time.h> // Must be included before we redefine settimeofday()
+
+// Include the tinycbor sources we need for CBOR processing
+#include "managed_components/espressif__cbor/tinycbor/src/cborencoder.c"
+#include "managed_components/espressif__cbor/tinycbor/src/cborparser.c"
+#include "managed_components/espressif__cbor/tinycbor/src/cborparser_dup_string.c"
+#include "managed_components/espressif__cbor/tinycbor/src/cborpretty.c"
+#include "managed_components/espressif__cbor/tinycbor/src/cborpretty_stdio.c"
+#include "managed_components/espressif__cbor/tinycbor/src/cbortojson.c"
+// Include the asset snapshot component sources
+#include "components/assets/assets_snapshot.c"
+// Include the miniz compression code.
+// This is a manually shortened version of the amalgamation from
+// https://github.com/richgel999/miniz with a couple of additional
+// patches for memory safety.
+#include "miniz.c"
+// Include the esp32_deflate component
+#define ESP_PLATFORM 1
+#define ESP_IDF_VERSION 1
+#define ESP_IDF_VERSION_VAL(x, y, z) 1
+#include "components/esp32_deflate/deflate.h"
+#undef ESP_IDF_VERSION_VAL
+#undef ESP_IDF_VERSION
+#undef ESP_PLATFORM
+#include "components/esp32_deflate/deflate.c"
+// qrCode encoding/decoding
+#include "components/esp32-quirc/lib/decode.c"
+#include "components/esp32-quirc/lib/identify.c"
+#include "components/esp32-quirc/lib/quirc.c"
+#include "components/esp32-quirc/lib/version_db.c"
+#include "components/esp32-quirc/openmv/collections.c"
+// bspatch
+#include "components/esp32_bsdiff/bspatch.c"
+
+// abort is mapped to __wrap_abort in the firmware. This calls jade_abort,
+// which calls __real_abort, which we implement as calling (the real) abort
+static void __real_abort(void) { abort(); }
+
+// Make time setting a no-op.
+// We are in the same process as the caller, so anything that depends on the
+// current time will automatically match the callers time.
+static int settimeofday_no_op(const void* yv, const void* tz) { return 0; }
+#define settimeofday settimeofday_no_op
+
+#ifndef CONFIG_LIBJADE_NO_GUI
+#include "main/gui.h"
+
+typedef void* locale_multilang_string_t;
+const locale_multilang_string_t* locale_get(const char* key) { return NULL; }
+const char* locale_lang_with_fallback(const locale_multilang_string_t* str, jlocale_t lang) { return NULL; }
+#endif // CONFIG_LIBJADE_NO_GUI
+
+// Include the core Jade firmware core, including wally/secp.
+#define AMALGAMATED_BUILD
+#include "main/amalgamated.c"
+#undef settimeofday
+
+#ifdef CONFIG_LIBJADE_NO_GUI
+// GUI: Include our fake GUI
+#include "gui.c"
+#endif // CONFIG_LIBJADE_NO_GUI
+
+//
+// Stubs for code that does not apply to libjade or is not yet implemented
+//
+
+// main/idletimer.c
+void idletimer_init(void) {}
+bool idletimer_register_activity(const bool is_ui) { return true; }
+void idletimer_set_min_timeout_secs(uint16_t min_timeout_secs){};
+
+// main/logging.c
+esp_log_level_t _libjade_log_level = ESP_LOG_NONE;
+
+// main/selfcheck.c
+bool debug_selfcheck(jade_process_t* process) { return true; }
+
+esp_app_desc_t running_app_info = { "123456789012345678901" };
+esp_chip_info_t chip_info = { 0 };
+
+void esp_restart() { abort(); }
+
+const char* esp_get_idf_version(void) { return "9.9.99-99-fake_hack"; }
+
+void esp_chip_info(esp_chip_info_t* out)
+{
+ out->features = 0; // FIXME
+}
+
+esp_err_t esp_efuse_mac_get_default(uint8_t* out)
+{
+ memset(out, 0, 6);
+ return ESP_OK;
+}
+
+void esp_deep_sleep_start(void) { abort(); }
+
+// UI
+#ifdef CONFIG_LIBJADE_NO_GUI
+uint8_t GUI_DEFAULT_FONT = 0;
+uint8_t GUI_TITLE_FONT = 1;
+
+// Display
+void display_init(TaskHandle_t* task) {}
+
+Icon* get_icon(const uint8_t* const start, const uint8_t* const end) { return NULL; }
+#endif
+
+void input_init(void) {}
+
+// Serial
+bool serial_init(TaskHandle_t* task) { return true; }
+
+// Camera
+static const uint8_t* debug_image_data = NULL;
+void camera_set_debug_image(const uint8_t* data, const size_t len)
+{
+ JADE_ASSERT(!data == !len);
+ JADE_ASSERT(!len || len == CAMERA_IMAGE_WIDTH * CAMERA_IMAGE_HEIGHT);
+ debug_image_data = data;
+}
+
+void jade_camera_process_images(camera_process_fn_t fn, void* ctx, const bool show_ui, const char* text_label,
+ const bool show_click_button, const qr_frame_guides_t qr_frame_guides, const char* help_url,
+ progress_bar_t* progress_bar)
+{
+ if (debug_image_data) {
+ if (!fn(CAMERA_IMAGE_WIDTH, CAMERA_IMAGE_HEIGHT, debug_image_data, CAMERA_IMAGE_WIDTH * CAMERA_IMAGE_HEIGHT,
+ ctx)) {
+ JADE_LOGW("User callback returned false for fixed debug image - exiting camera regardless");
+ }
+ }
+}
+
+// main/ui/keyboard.c
+void make_keyboard_entry_activity(keyboard_entry_t* kb_entry, const char* title) {}
+
+void run_keyboard_entry_loop(keyboard_entry_t* kb_entry) {}
+
+const uint8_t _binary_pinserver_public_key_pub_start[33]
+ = { 0x03, 0x32, 0xb7, 0xb1, 0x34, 0x8b, 0xde, 0x8c, 0xa4, 0xb4, 0x6b, 0x9d, 0xcc, 0x30, 0x32, 0x0e, 0x14, 0x0c,
+ 0xa2, 0x64, 0x28, 0x16, 0x0a, 0x27, 0xbd, 0xbf, 0xc3, 0x0b, 0x34, 0xec, 0x87, 0xc5, 0x47 };
+
+// Events
+static volatile bool _libjade_stop_requested = false; // Used to stop the firmware
+
+#ifdef CONFIG_LIBJADE_NO_GUI
+void sync_wait_event_handler(void* handler_arg, esp_event_base_t base, int32_t id, void* event_data) {}
+
+esp_err_t sync_wait_event(wait_event_data_t* wait_event_data, esp_event_base_t* trigger_event_base,
+ int32_t* trigger_event_id, void** trigger_event_data, TickType_t max_wait)
+{
+ if (_libjade_stop_requested) {
+ // User requested the firmware to exit
+ pthread_exit(NULL);
+ }
+ return ESP_NO_EVENT;
+}
+
+esp_err_t sync_await_single_event(esp_event_base_t event_base, int32_t event_id, esp_event_base_t* trigger_event_base,
+ int32_t* trigger_event_id, void** trigger_event_data, TickType_t max_wait)
+{
+ return ESP_OK;
+}
+#endif // CONFIG_LIBJADE_NO_GUI
+
+// HW: Task API
+bool run_on_temporary_stack(size_t stack_size, temporary_stack_function_t fn, void* ctx) { return fn(ctx); }
+
+bool run_in_temporary_task(const size_t stack_size, temporary_stack_function_t fn, void* ctx) { return fn(ctx); }
+
+void temp_stack_init(void) {}
+
+// HW: TLS/Sensitive
+static void* _tls_ptrs[3];
+
+void* pvTaskGetThreadLocalStoragePointer(void* task, size_t idx)
+{
+ assert(idx <= sizeof(_tls_ptrs) / sizeof(_tls_ptrs[0]));
+ return _tls_ptrs[idx];
+}
+
+void vTaskSetThreadLocalStoragePointerAndDelCallback(void* task, size_t idx, void* p, TlsDeleteCallbackFunction_t cb)
+{
+ assert(idx <= sizeof(_tls_ptrs) / sizeof(_tls_ptrs[0]));
+ _tls_ptrs[idx] = p;
+ // FIXME: call cb atexit()/thread exit?
+}
+
+const char* pcTaskGetName(void* task) { return "shim_task"; }
+
+BaseType_t xTaskCreatePinnedToCore(TaskFunction_t func, const char* name, uint32_t stack_size, void* params,
+ uint32_t ux_prio, TaskHandle_t* output, uint32_t xCoreID)
+{
+ *output = NULL;
+ func(params);
+ return pdTRUE;
+}
+
+unsigned int uxTaskGetStackHighWaterMark(void* task) { return 0xffffff; }
+
+unsigned int xPortGetFreeHeapSize(void) { return 0xffffff; }
+
+void vTaskDelay(TickType_t delay)
+{
+ // Don't delay, since we don't have multiple threads running
+ // in the firmware to wait on.
+}
+
+void vTaskDelayUntil(TickType_t* prev_wake_time, const TickType_t delay)
+{
+ // Used to control the GUI refresh framerate
+ // FIXME: Implement
+}
+
+void vTaskDelete(void* task)
+{
+ // Don't delete, since we didn't create any task
+}
+
+TickType_t xTaskGetTickCount(void)
+{
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+ abort();
+ }
+ return ((TickType_t)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
+}
+
+int xTaskNotify(TaskHandle_t task, unsigned int v, int action) { return pdTRUE; }
+
+void sensitive_init(void) {}
+
+void sensitive_push(const char* file, int line, void* addr, size_t size) {}
+
+void sensitive_pop(const char* file, int line, void* addr) {}
+
+void sensitive_assert_empty(void) {}
+
+void sensitive_clear_stack(void) {}
+
+// HW: Random
+void get_random(void* bytes_out, size_t len)
+{
+ if (!bytes_out || !len) {
+ abort();
+ }
+
+ uint8_t* current_ptr = (uint8_t*)bytes_out;
+ size_t remaining = len;
+ int getrandom_enosys = 0;
+
+ while (remaining > 0) {
+ const ssize_t bytes_read = getrandom(current_ptr, remaining, 0);
+
+ if (bytes_read == -1) {
+ if (errno == EINTR) {
+ continue;
+ } else if (errno == ENOSYS) {
+ getrandom_enosys = 1;
+ break;
+ } else {
+ abort();
+ }
+ } else if (bytes_read == 0) {
+ abort();
+ } else {
+ current_ptr += bytes_read;
+ remaining -= bytes_read;
+ }
+ }
+
+ if (remaining == 0 && !getrandom_enosys) {
+ // happy path
+ return;
+ }
+
+ if (getrandom_enosys) {
+ // FIXME: find another source of entropy of cryptographic strength or abort()
+ }
+ abort();
+}
+
+void refeed_entropy(const void* additional, size_t len)
+{
+ // Unused since our get_random has no state.
+ // FIXME: change main/random.c to use getrandom, pids etc for libjade, then use it
+}
+
+uint8_t get_uniform_random_byte(uint8_t upper_bound)
+{
+ uint8_t ret;
+ get_random(&ret, sizeof(ret));
+ return ret % upper_bound; // Not used for crypto, so return a biased byte
+}
+
+void random_start_collecting(void) {}
+
+void random_full_initialization(void) {}
+
+int random_mbedtls_cb(void* ctx, uint8_t* buf, const size_t len)
+{
+ // ctx is ignored (should be NULL)
+ get_random(buf, len);
+ return 0;
+}
+
+// HW: NVS storage
+static struct wally_map nvs_storage[5]; // Map of field name to contents
+
+esp_err_t nvs_flash_init(void) { return ESP_OK; }
+
+static struct wally_map* get_nvs_ns(const char* ns)
+{
+ if (!strcmp(ns, DEFAULT_NAMESPACE)) {
+ return &nvs_storage[0];
+ }
+ if (!strcmp(ns, MULTISIG_NAMESPACE)) {
+ return &nvs_storage[1];
+ }
+ if (!strcmp(ns, DESCRIPTOR_NAMESPACE)) {
+ return &nvs_storage[2];
+ }
+ if (!strcmp(ns, OTP_NAMESPACE)) {
+ return &nvs_storage[3];
+ }
+ if (!strcmp(ns, HOTP_COUNTERS_NAMESPACE)) {
+ return &nvs_storage[4];
+ }
+ return NULL;
+}
+
+esp_err_t nvs_open(const char* ns, nvs_open_mode_t open_mode, nvs_handle_t* out_handle)
+{
+ *out_handle = get_nvs_ns(ns);
+ return *out_handle ? ESP_OK : ESP_ERR_NVS_NOT_FOUND;
+}
+
+esp_err_t nvs_set_blob(nvs_handle_t handle, const char* key, const void* value, size_t length)
+{
+ int ret = wally_map_replace(handle, (const unsigned char*)key, strlen(key), value, length);
+ return ret == WALLY_OK ? ESP_OK : ESP_FAIL;
+}
+
+esp_err_t nvs_get_blob(nvs_handle_t handle, const char* key, void* out_value, size_t* length)
+{
+ const struct wally_map_item* item = wally_map_get(handle, (const unsigned char*)key, strlen(key));
+ if (!item || item->value_len > *length) {
+ return ESP_ERR_NVS_NOT_FOUND;
+ }
+ memcpy(out_value, item->value, item->value_len);
+ *length = item->value_len;
+ return ESP_OK;
+}
+
+esp_err_t nvs_set_str(nvs_handle_t handle, const char* key, const char* value)
+{
+ return nvs_set_blob(handle, key, value, strlen(value) + 1); // Include NUL terminator
+}
+
+esp_err_t nvs_get_str(nvs_handle_t handle, const char* key, char* out_value, size_t* length)
+{
+ return nvs_get_blob(handle, key, out_value, length);
+}
+
+esp_err_t nvs_set_u32(nvs_handle_t handle, const char* key, uint32_t value)
+{
+ // FIXME: endianess, if we will allow loading/saving flash
+ return nvs_set_blob(handle, key, (void*)&value, sizeof(value));
+}
+
+esp_err_t nvs_get_u32(nvs_handle_t handle, const char* key, uint32_t* out_value)
+{
+ // FIXME: endianess, if we will allow loading/saving flash
+ size_t length = sizeof(out_value);
+ return nvs_get_blob(handle, key, (void*)out_value, &length);
+}
+
+esp_err_t nvs_erase_key(nvs_handle_t handle, const char* key)
+{
+ if (wally_map_remove(handle, (const unsigned char*)key, strlen(key)) != WALLY_OK) {
+ return ESP_ERR_NVS_NOT_FOUND;
+ }
+ return ESP_OK;
+}
+
+esp_err_t nvs_entry_find(const char* part_name, const char* ns, nvs_type_t type, nvs_iterator_t* output_iterator)
+{
+ *output_iterator = malloc(sizeof(**output_iterator));
+ if (!*output_iterator) {
+ return ESP_FAIL;
+ }
+ if (!((*output_iterator)->m = get_nvs_ns(ns)) || !(*output_iterator)->m->num_items) {
+ goto fail;
+ }
+ // FIXME: Ignores type, pretty sure we only store the same type in each map?
+ (*output_iterator)->idx = 0;
+ return ESP_OK;
+fail:
+ free(*output_iterator);
+ *output_iterator = NULL;
+ return ESP_ERR_NVS_NOT_FOUND;
+}
+
+esp_err_t nvs_entry_next(nvs_iterator_t* iterator)
+{
+ if ((*iterator)->idx >= (*iterator)->m->num_items) {
+ nvs_release_iterator(*iterator);
+ *iterator = NULL;
+ return ESP_ERR_NVS_NOT_FOUND;
+ }
+ ++(*iterator)->idx;
+ return ESP_OK;
+}
+
+esp_err_t nvs_entry_info(const nvs_iterator_t iterator, nvs_entry_info_t* out_info)
+{
+ // FIXME: Only sets key, as thats all we ever read
+ if (!iterator || iterator->idx >= iterator->m->num_items) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ const struct wally_map_item* item = iterator->m->items + iterator->idx;
+ if (item->key_len >= NVS_KEY_NAME_MAX_SIZE) {
+ abort();
+ }
+ memcpy(out_info->key, item->key, item->key_len);
+ out_info->key[item->key_len] = '\0';
+ return ESP_OK;
+}
+
+void nvs_release_iterator(nvs_iterator_t iterator)
+{
+ if (iterator) {
+ free(iterator);
+ }
+}
+
+esp_err_t nvs_flash_erase(void)
+{
+ for (size_t i = 0; i < sizeof(nvs_storage) / sizeof(nvs_storage[0]); ++i) {
+ wally_map_clear(&nvs_storage[i]);
+ }
+ return ESP_OK;
+}
+
+esp_err_t nvs_get_stats(const char* part_name, nvs_stats_t* nvs_stats)
+{
+ nvs_stats->used_entries = 0;
+ for (size_t i = 0; i < sizeof(nvs_storage) / sizeof(nvs_storage[0]); ++i) {
+ nvs_stats->used_entries += nvs_storage[i].num_items;
+ }
+ nvs_stats->free_entries = ESP_NVS_TOTAL_ENTRIES - nvs_stats->used_entries;
+ return ESP_OK;
+}
+
+static void* jade_fw_thread_fn(void* arg)
+{
+ start_dashboard();
+ return NULL; // Never reached
+}
+
+// External API:
+static pthread_t _libjade_thread_id; // Thread ID of the FW thread
+
+void libjade_start(void)
+{
+ ensure_boot_flags();
+ random_start_collecting();
+ validate_running_image();
+ boot_process();
+ sensitive_assert_empty();
+ pthread_create(&_libjade_thread_id, NULL, &jade_fw_thread_fn, NULL);
+}
+
+void libjade_stop(void)
+{
+ // Request the firmware to stop
+ _libjade_stop_requested = true;
+ pthread_join(_libjade_thread_id, NULL);
+ _libjade_stop_requested = false;
+ vRingbufferDelete(shared_in);
+ shared_in = NULL;
+ vRingbufferDelete(serial_out);
+ serial_out = NULL;
+ vRingbufferDelete(internal_out);
+ internal_out = NULL;
+}
+
+static uint8_t _libjade_serial_data_in[MAX_INPUT_MSG_SIZE + 1] = { 0 };
+static uint8_t _libjade_serial_data_out[MAX_OUTPUT_MSG_SIZE] = { 0 };
+static size_t _libjade_serial_read_ptr = 0;
+static TickType_t _libjade_last_processing_time = 0;
+
+bool libjade_send(const uint8_t* data, const size_t size)
+{
+ if (_libjade_serial_read_ptr + size >= MAX_INPUT_MSG_SIZE) {
+ return false;
+ }
+ // Pass the message through as though it came from the serial interface
+ uint8_t* data_with_source = _libjade_serial_data_in;
+ data_with_source[0] = SOURCE_SERIAL;
+ memcpy(data_with_source + 1 + _libjade_serial_read_ptr, data, size);
+ const bool force_reject_if_no_msg = false;
+ handle_data(data_with_source, &_libjade_serial_read_ptr, size, &_libjade_last_processing_time,
+ force_reject_if_no_msg, _libjade_serial_data_out);
+ return true;
+}
+
+uint8_t* libjade_receive(const unsigned int timeout, size_t* size_out)
+{
+ // timeout is in seconds, convert to milliseconds
+ const unsigned int ms = timeout * 1000;
+ void* item = xRingbufferReceive(serial_out, size_out, ms / portTICK_PERIOD_MS);
+ if (!item) {
+ // No message available
+ *size_out = 0;
+ }
+ return item;
+}
+
+void libjade_release(uint8_t* data) { vRingbufferReturnItem(serial_out, (void*)data); }
+
+void libjade_set_log_level(int level)
+{
+ // Note we don't bother about thread safety for _libjade_log_level
+ if (level < 0) {
+ _libjade_log_level = ESP_LOG_VERBOSE;
+ } else if (level >= ESP_LOG_NONE) {
+ _libjade_log_level = ESP_LOG_NONE;
+ } else {
+ _libjade_log_level = (esp_log_level_t)level;
+ }
+}
diff --git a/libjade/libjade.h b/libjade/libjade.h
new file mode 100644
index 0000000..3e80946
--- /dev/null
+++ b/libjade/libjade.h
@@ -0,0 +1,56 @@
+#ifndef _LIBJADE_H_
+#define _LIBJADE_H_ 1
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifndef LIBJADE_API
+#if defined(_WIN32)
+#ifdef LIBJADE_BUILD
+#define LIBJADE_API __declspec(dllexport)
+#else
+#define LIBJADE_API
+#endif
+#elif defined(__GNUC__) && defined(LIBJADE_BUILD)
+#define LIBJADE_API __attribute__((visibility("default")))
+#else
+#define LIBJADE_API
+#endif
+#endif
+
+/*
+ * Start the global libjade instance.
+ * Only one instance may be running at at time, however it can be stopped
+ * and restarted as many times as required.
+ */
+LIBJADE_API void libjade_start(void);
+
+/*
+ * Stop the global libjade instance.
+ */
+LIBJADE_API void libjade_stop(void);
+
+/*
+ * Send a CBOR message to the global libjade instance.
+ */
+LIBJADE_API bool libjade_send(const uint8_t* data, size_t size);
+
+/*
+ * Receive a CBOR reply message from the global libjade instance.
+ * `libjade_release` must be used to free any returned message.
+ */
+LIBJADE_API uint8_t* libjade_receive(unsigned int timeout, size_t* size_out);
+
+/*
+ * Free a CBOR message returned from `libjade_receive`.
+ */
+LIBJADE_API void libjade_release(uint8_t* data);
+
+/*
+ * Set the logging verbosity level for the global libjade instance.
+ * levels are 0-4 in decreasing verbosity, or 5 to disable logging
+ */
+LIBJADE_API void libjade_set_log_level(int level);
+
+#endif /* _LIBJADE_H_ */
diff --git a/libjade/make_icons.py b/libjade/make_icons.py
new file mode 100644
index 0000000..d71968e
--- /dev/null
+++ b/libjade/make_icons.py
@@ -0,0 +1,9 @@
+# python make_icons.py | clang-format >icons.inc
+for name in ['statusbar_large']:
+ contents = open(f'../logo/{name}.bin.gz', 'rb').read()
+ var = f'_binary_{name}_bin_gz'
+ print(f'const uint8_t {var}_start[{len(contents)}] = {{')
+ for n, b in enumerate(contents):
+ sep = ', ' if n else ''
+ print(f'{sep}{hex(b)}')
+ print(f'}};\nconst uint8_t* {var}_end = {var}_start + {len(contents)};\n')
diff --git a/libjade/make_libjade.sh b/libjade/make_libjade.sh
new file mode 100755
index 0000000..8f9ce3d
--- /dev/null
+++ b/libjade/make_libjade.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Build the Jade firmware into a shared library for in-process debugging
+#
+# ./libjade/make_libjade.sh [Debug|Release|RelWithDebInfo|MinSizeRel|Sanitize]
+#
+set -e
+
+BUILD_TYPE="${1:-Debug}"
+
+rm -rf build_linux
+mkdir build_linux
+cd build_linux
+if [ "${BUILD_TYPE}" == "Sanitize" ]; then
+ cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_C_FLAGS"-fsanitize=undefined" -DCMAKE_CXX_FLAGS"-fsanitize=undefined" ..
+else
+ cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ..
+fi
+make -j8
+
+echo "to use libjade set LD_LIBRARY_PATH=$PWD/build_linux/libjade"
+if [ "${BUILD_TYPE}" == "Sanitize" ]; then
+ echo "and ASAN_OPTIONS=symbolize=1,detect_leaks=0 LD_PRELOAD=$(ls /usr/lib/gcc/x86_64-linux-gnu/*/libasan.so) UBSAN_OPTIONS=print_stacktrace=1"
+fi
diff --git a/libjade/miniz.c b/libjade/miniz.c
new file mode 100644
index 0000000..cf978d9
--- /dev/null
+++ b/libjade/miniz.c
@@ -0,0 +1,1889 @@
+#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];
+
+/*
+ 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 <http://unlicense.org/>
+*/
+/**************************************************************************
+ *
+ * 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);
+
+ 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);
+ }
+ }
+ 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 */
+
+ 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_pLZ_flags) = 0;
+ }
+ 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++;
+ *(d->m_pLZ_flags) = 0;
+ }
+
+ 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 (!tdefl_compress_normal(d))
+ return d->m_prev_return_status;
+ }
+
+ 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_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;
+ }
+
+#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 (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;
+
+ 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;
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/
diff --git a/libjade/miniz.h b/libjade/miniz.h
new file mode 100644
index 0000000..cd4944c
--- /dev/null
+++ b/libjade/miniz.h
@@ -0,0 +1,654 @@
+#ifndef MINIZ_EXPORT
+#define MINIZ_EXPORT
+#endif
+/* miniz.c 3.1.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
+ See "unlicense" statement at the end of this file.
+ Rich Geldreich <richgel99@gmail.com>, 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
+
+
+
+/* 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 <stddef.h>
+
+#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
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+
+/* ------------------- 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 <stdio.h>
+#define MZ_FILE FILE
+#endif /* #ifdef MINIZ_NO_STDIO */
+
+#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 _MSC_VER
+#define MZ_FORCEINLINE __forceinline
+#elif defined(__GNUC__)
+#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__))
+#else
+#define MZ_FORCEINLINE inline
+#endif
+
+#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
+
+#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*/
+
+/* ------------------- 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 \
+ { \
+ memset(r, 0, sizeof (tinfl_decompressor)); \
+ } \
+ 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*/
diff --git a/main/amalgamated.c b/main/amalgamated.c
index b2ff2a0..4047331 100644
--- a/main/amalgamated.c
+++ b/main/amalgamated.c
@@ -7,8 +7,10 @@ void __wrap_abort(void);
#define BUILD_ELEMENTS 1
#define BUILD_MINIMAL 1
+#ifndef CONFIG_LIBJADE
#define HAVE_MBEDTLS_SHA256_H
#define HAVE_MBEDTLS_SHA512_H
+#endif
#define ECMULT_WINDOW_SIZE 8
#define ENABLE_MODULE_ECDH 1
#define ENABLE_MODULE_ECDSA_S2C 1
@@ -31,10 +33,14 @@ void __wrap_abort(void);
#ifdef CONFIG_BT_ENABLED
#include "./ble/ble.c"
#endif // CONFIG_BT_ENABLED
+#ifndef CONFIG_LIBJADE
#include "./camera.c"
+#endif
#include "./descriptor.c"
+#ifndef CONFIG_LIBJADE_NO_GUI
#include "./display.c"
#include "./display_hw.c"
+#endif // CONFIG_LIBJADE_NO_GUI
#include "./fonts/BigFont.c"
#include "./fonts/DefaultFont.c"
#include "./fonts/DejaVuSans18.c"
@@ -56,9 +62,13 @@ void __wrap_abort(void);
#include "./fonts/minya24.c"
#include "./fonts/tooney32.c"
#include "./fonts/various_symbols.c"
+#ifndef CONFIG_LIBJADE_NO_GUI
#include "./gui.c"
+#endif // CONFIG_LIBJADE_NO_GUI
#include "./identity.c"
+#ifndef CONFIG_LIBJADE
#include "./idletimer.c"
+#endif // CONFIG_LIBJADE
#ifdef ESP_PLATFORM
#include "./input.c"
#endif // ESP_PLATFORM
@@ -119,15 +129,19 @@ void __wrap_abort(void);
#include "./qrcode.c"
#include "./qrmode.c"
#include "./qrscan.c"
-#include "./random.c"
#include "./rsa.c"
+#ifndef CONFIG_LIBJADE
+#include "./random.c"
#include "./selfcheck.c"
#include "./sensitive.c"
+#endif // CONFIG_LIBJADE
#ifdef ESP_PLATFORM
#include "./serial.c"
#endif // ESP_PLATFORM
#include "./signer.c"
+#ifndef CONFIG_LIBJADE
#include "./smoketest.c"
+#endif // CONFIG_LIBJADE
#include "./storage.c"
#include "./ui/ble_confirm.c"
#include "./ui/camera.c"
@@ -135,7 +149,9 @@ void __wrap_abort(void);
#include "./ui/dashboard.c"
#include "./ui/descriptor.c"
#include "./ui/dialogs.c"
+#ifndef CONFIG_LIBJADE
#include "./ui/keyboard.c"
+#endif // CONFIG_LIBJADE
#include "./ui/mnemonic.c"
#include "./ui/multisig.c"
#include "./ui/ota.c"
@@ -154,11 +170,15 @@ void __wrap_abort(void);
#endif // CONFIG_IDF_TARGET_ESP32S3
#include "./utils/address.c"
#include "./utils/cbor_rpc.c"
+#ifndef CONFIG_LIBJADE_NO_GUI
#include "./utils/event.c"
+#endif // CONFIG_LIBJADE_NO_GUI
#include "./utils/network.c"
#include "./utils/psbt.c"
#include "./utils/shake256.c"
+#ifndef CONFIG_LIBJADE
#include "./utils/temporary_stack.c"
+#endif // CONFIG_LIBJADE
#include "./utils/urldecode.c"
#include "./utils/wally_ext.c"
#include "./versioninfo.c"
diff --git a/main/display.c b/main/display.c
index ef6d2f0..b85b3ae 100644
--- a/main/display.c
+++ b/main/display.c
@@ -10,7 +10,9 @@
#include "storage.h"
#include "utils/malloc_ext.h"
#include "utils/util.h"
+#ifndef CONFIG_LIBJADE
#include <deflate.h>
+#endif
#if defined(CONFIG_ETH_USE_OPENETH)
#define BUF_N 1
diff --git a/main/display_hw.c b/main/display_hw.c
index a1e80a3..7671913 100644
--- a/main/display_hw.c
+++ b/main/display_hw.c
@@ -7,11 +7,15 @@
#include "jade_tasks.h"
#include "utils/malloc_ext.h"
#include "utils/util.h"
+#ifdef CONFIG_LIBJADE
+typedef void* esp_lcd_panel_handle_t;
+#else
#include <driver/gpio.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
+#endif // ndef CONFIG_LIBJADE
#include "freertos/semphr.h"
#include <freertos/FreeRTOS.h>
@@ -42,10 +46,8 @@ static void set_gpio_high(gpio_num_t num)
ESP_ERROR_CHECK(gpio_set_level(num, 1));
}
-#else
-
+#elif !defined(CONFIG_LIBJADE)
#include <driver/spi_common.h>
-
#endif
#ifdef CONFIG_DISPLAY_FULL_FRAME_BUFFER
@@ -214,15 +216,19 @@ static void esp_lcd_init(void* _ignored)
#define Y_FLIPPED false
ph = (void*)1;
#endif
+#ifndef CONFIG_LIBJADE
xSemaphoreGive(init_done);
for (;;) {
vTaskDelay(portMAX_DELAY);
}
+#endif // CONFIG_LIBJADE
}
bool display_hw_flip_orientation(const bool flipped_orientation)
{
+#ifndef CONFIG_LIBJADE
ESP_ERROR_CHECK(esp_lcd_panel_mirror(ph, flipped_orientation ^ X_FLIPPED, flipped_orientation ^ Y_FLIPPED));
+#endif // CONFIG_LIBJADE
return flipped_orientation;
}
@@ -249,6 +255,7 @@ void display_hw_init(TaskHandle_t* gui_handle)
disp_buf = JADE_MALLOC_PREFER_SPIRAM_ALIGNED(CONFIG_DISPLAY_WIDTH * CONFIG_DISPLAY_HEIGHT * sizeof(color_t), 16);
#endif
#endif
+#ifndef CONFIG_LIBJADE
/* We have to initialize the lcd on the same core we are going to call it from,
* see https://github.com/espressif/esp-idf/issues/12347
* otherwise we could run the esp_lcd_init function directly */
@@ -259,6 +266,7 @@ void display_hw_init(TaskHandle_t* gui_handle)
xSemaphoreTake(init_done, portMAX_DELAY);
vTaskDelete(lcdInitTaskHandle);
vSemaphoreDelete(init_done);
+#endif // CONFIG_LIBJADE
}
inline void display_hw_draw_bitmap(int x, int y, int w, int h, const uint16_t* color_data)
@@ -366,7 +374,7 @@ inline void display_hw_draw_rect(int x, int y, int w, int h, const uint16_t colo
}
}
-inline uint16_t* display_hw_get_buffer(void) { return disp_buf; }
+uint16_t* display_hw_get_buffer(void) { return disp_buf; }
#endif
@@ -386,7 +394,9 @@ static inline void switch_buffer(void)
* single/double full screen buffer) */
void display_hw_flush(void)
{
+#ifndef CONFIG_LIBJADE
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(ph, 0, 0, CONFIG_DISPLAY_WIDTH, CONFIG_DISPLAY_HEIGHT, disp_buf));
+#endif // CONFIG_LIBJADE
#ifdef CONFIG_DISPLAY_FULL_FRAME_BUFFER_DOUBLE
/* we only need to switch buffer if we have more than one and we don't bother waiting for writes */
switch_buffer();
diff --git a/main/gui.c b/main/gui.c
index 9d49ad6..673c001 100644
--- a/main/gui.c
+++ b/main/gui.c
@@ -107,6 +107,10 @@ struct {
static void gui_task(void* args);
static void repaint_node(gui_view_node_t* node);
+#ifdef CONFIG_LIBJADE
+#define statusbar_logo_end _binary_statusbar_large_bin_gz_end
+#define statusbar_logo_start _binary_statusbar_large_bin_gz_start
+#else
#if HOME_SCREEN_DEEP_STATUS_BAR
extern const uint8_t statusbar_logo_start[] asm("_binary_statusbar_large_bin_gz_start");
extern const uint8_t statusbar_logo_end[] asm("_binary_statusbar_large_bin_gz_end");
@@ -114,6 +118,7 @@ extern const uint8_t statusbar_logo_end[] asm("_binary_statusbar_large_bin_gz_en
extern const uint8_t statusbar_logo_start[] asm("_binary_statusbar_small_bin_gz_start");
extern const uint8_t statusbar_logo_end[] asm("_binary_statusbar_small_bin_gz_end");
#endif
+#endif // CONFIG_LIBJADE
static void free_view_node(gui_view_node_t* node);
@@ -279,14 +284,22 @@ void gui_init(TaskHandle_t* gui_h)
// Create status-bar
make_status_bar();
+#ifndef CONFIG_LIBJADE
// Create (high priority) gui task
BaseType_t retval
= xTaskCreatePinnedToCore(gui_task, "gui", 3 * 1024, NULL, JADE_TASK_PRIO_GUI, gui_h, JADE_CORE_GUI);
gui_task_handle = gui_h;
JADE_ASSERT_MSG(retval == pdPASS, "Failed to create GUI task, xTaskCreatePinnedToCore() returned %d", retval);
+#endif // CONFIG_LIBJADE
}
-bool gui_initialized(void) { return *gui_task_handle; } // gui task started
+#ifdef CONFIG_LIBJADE
+bool gui_initialized(void) { return gui_task_handle; }
+static bool gui_is_gui_task(void) { return true; }
+#else
+bool gui_initialized(void) { return gui_task_handle && *gui_task_handle; }
+static bool gui_is_gui_task(void) { return gui_task_handle && xTaskGetCurrentTaskHandle() == *gui_task_handle; }
+#endif // ndef CONFIG_LIBJADE
// Is this kind of node selectable?
static inline bool is_kind_selectable(enum view_node_kind kind) { return kind == BUTTON; }
@@ -2144,8 +2157,7 @@ static void repaint_node(gui_view_node_t* node)
JADE_ASSERT(node);
// Ensure we only call the underlying dislay library from the gui_task
- JADE_ASSERT_MSG(xTaskGetCurrentTaskHandle() == *gui_task_handle,
- "ERROR: repaint_node() called from non-gui-task: %s", pcTaskGetName(NULL));
+ JADE_ASSERT_MSG(gui_is_gui_task(), "ERROR: repaint_node() called from non-gui-task: %s", pcTaskGetName(NULL));
// borders use the un-padded constraints
if (node->borders) {
@@ -2534,7 +2546,7 @@ void gui_repaint(gui_view_node_t* node)
// If we are called from the gui task we can immediately repaint the node.
// If not, we should enqueue a message to the gui task to repaint.
- if (xTaskGetCurrentTaskHandle() == *gui_task_handle) {
+ if (gui_is_gui_task()) {
repaint_node(node);
return;
}
diff --git a/main/process/dashboard.c b/main/process/dashboard.c
index 5b8ee8a..94afa5a 100644
--- a/main/process/dashboard.c
+++ b/main/process/dashboard.c
@@ -1475,9 +1475,10 @@ static bool display_totp_screen(otpauth_ctx_t* otp_ctx, uint64_t epoch_value, ch
progress_bar_t time_left = {};
gui_activity_t* const act
= make_show_totp_code_activity(otp_ctx->name, timestr, token, confirm_only, &time_left, &txt_ts, &txt_code);
+#ifndef CONFIG_LIBJADE_NO_GUI
JADE_ASSERT(txt_ts);
JADE_ASSERT(txt_code);
-
+#endif
gui_set_current_activity(act);
vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -2699,6 +2700,8 @@ void dashboard_process(void* process_ptr)
gui_view_node_t* label = NULL;
gui_activity_t* const act_home = make_home_screen_activity(device_name, running_app_info.version,
&home_screen_selected_entry, &home_screen_next_entry, &status_light, &status_text, &label);
+#ifndef CONFIG_LIBJADE_NO_GUI
+ // If no GUI is enabled, we do not expect these elements to be set
JADE_ASSERT(home_screen_selected_entry.symbol);
JADE_ASSERT(home_screen_selected_entry.text);
JADE_ASSERT(home_screen_next_entry.symbol);
@@ -2706,6 +2709,7 @@ void dashboard_process(void* process_ptr)
JADE_ASSERT(status_light);
JADE_ASSERT(status_text);
JADE_ASSERT(label);
+#endif // CONFIG_LIBJADE_NO_GUI
// We may as well associate the long-lived event data with this activity also
wait_event_data_t* const event_data = gui_activity_make_wait_event_data(act_home);
diff --git a/main/process/ota_util.c b/main/process/ota_util.c
index 2017a9e..ac40736 100644
--- a/main/process/ota_util.c
+++ b/main/process/ota_util.c
@@ -367,6 +367,7 @@ void ota_finalize(jade_process_t* process, jade_ota_ctx_t* joctx, const bool is_
return; // Unreachable
error:
+ (void)NULL; // No-op statement for the error label
// We have an error, send an error response.
const char* status_text = ota_get_status_text(joctx->ota_return_status);
JADE_LOGE("OTA error: %s", status_text);
diff --git a/main/process/ota_util.h b/main/process/ota_util.h
index ff34b87..7f752f4 100644
--- a/main/process/ota_util.h
+++ b/main/process/ota_util.h
@@ -4,7 +4,9 @@
#include "../process.h"
#include "../ui.h"
#include "../utils/cbor_rpc.h"
+#ifndef CONFIG_LIBJADE
#include <deflate.h>
+#endif
#include <esp_app_format.h>
#include <esp_ota_ops.h>
#include <esp_partition.h>
diff --git a/main/ui/dialogs.c b/main/ui/dialogs.c
index 11c6757..c8b2293 100644
--- a/main/ui/dialogs.c
+++ b/main/ui/dialogs.c
@@ -695,6 +695,7 @@ gui_activity_t* make_progress_bar_activity(const char* title, const char* messag
void update_progress_bar(progress_bar_t* progress_bar, const size_t total, const size_t current)
{
+#ifndef CONFIG_LIBJADE_NO_GUI
JADE_ASSERT(progress_bar);
JADE_ASSERT(progress_bar->progress_bar);
// progress_bar->pcnt_txt is optional
@@ -730,5 +731,6 @@ void update_progress_bar(progress_bar_t* progress_bar, const size_t total, const
}
progress_bar->percent_last_value = pcnt;
+#endif
}
#endif // AMALGAMATED_BUILD
diff --git a/main/ui/otpauth.c b/main/ui/otpauth.c
index 8cb6730..06db949 100644
--- a/main/ui/otpauth.c
+++ b/main/ui/otpauth.c
@@ -314,8 +314,10 @@ gui_activity_t* make_show_totp_code_activity(const char* name, const char* times
gui_set_parent(*txt_ts, node);
gui_set_align(*txt_ts, GUI_ALIGN_CENTER, GUI_ALIGN_MIDDLE);
+#ifndef CONFIG_LIBJADE_NO_GUI
// Display 'progress' bar (time remaining)
make_progress_bar(vsplit, progress_bar);
+#endif
// Display the OTP code large/central
gui_make_fill(&node, TFT_BLACK, FILL_PLAIN, vsplit);
diff --git a/test_jade.py b/test_jade.py
index 1a7e9e3..50f90ff 100644
--- a/test_jade.py
+++ b/test_jade.py
@@ -19,7 +19,6 @@ import wallycore as wally
from jadepy.jade import JadeAPI, JadeError
# Enable jade logging
-args = None
jadehandler = logging.StreamHandler()
logger = logging.getLogger('jadepy.jade')
@@ -32,7 +31,8 @@ device_logger.addHandler(jadehandler)
def wait(seconds):
- time.sleep(seconds)
+ if not args.libjade and not args.spts:
+ time.sleep(seconds)
def h2b(hexdata):
@@ -1692,6 +1692,12 @@ HmWPvgD3hiTnD5KZuMkxSUsgGraZ9vavB5JSA3F9s5E4cXuCte5rvBs5N4DjfxYssQk1L82Bq4FE"
{'network': 'testnet', 'txn': GOODTX, 'num_inputs': 1,
'change': [None, None]}), 'Unexpected number of output entries')]
+ # Test all the simple cases
+ for badmsg, errormsg in bad_params:
+ if (args.libjade or args.spts) and badmsg[1] in ['ota', 'ota_delta']:
+ continue # Skip ota testing for libjade
+ _test_bad_params(jade, badmsg, errormsg)
+
bad_tx_inputs = [(('badinput0', 'tx_input'), 'Expecting parameters map'),
(('badinput1', 'tx_input',
{'is_witness': True, 'satoshi': 120, 'path': []}), 'extract valid path'),
@@ -1738,10 +1744,6 @@ HmWPvgD3hiTnD5KZuMkxSUsgGraZ9vavB5JSA3F9s5E4cXuCte5rvBs5N4DjfxYssQk1L82Bq4FE"
{'is_witness': True, 'path': [0], 'satoshi': 12345,
'script': TEST_SCRIPT, 'sighash': 2}), 'Unsupported sighash value')]
- # Test all the simple cases
- for badmsg, errormsg in bad_params:
- _test_bad_params(jade, badmsg, errormsg)
-
# Test all the bad tx inputs
for badinput, errormsg in bad_tx_inputs:
# Initiate a good sign-tx
@@ -2340,7 +2342,7 @@ def test_handshake(jade):
urls = result['http_request']['params']['urls']
assert urls == [TEST_URL+'/set_pin', TEST_ONION+'/set_pin']
certs = result['http_request']['params']['root_certificates']
- assert certs == [TEST_CERT]
+ assert certs == [TEST_CERT], f'{certs}'
# 2. This is where the app would call the URL returned with the data
# provided. We use the pinserver class directly here.
@@ -3488,6 +3490,7 @@ ZoxpDgc3UZwmpCgfdCkNmcSQa2tjnZLPohvRFECZP9P1boFKdJ5Sx'
def test_sign_identity(jadeapi):
+
ecdh_nist_cpty = list(_get_test_cases('identity_ssh_nist_matches_trezor.json'))[0]
for identity_data in _get_test_cases(SIGN_IDENTITY_TESTS):
inputdata = identity_data['input']
@@ -3898,20 +3901,23 @@ def run_interface_tests(jadeapi,
test_ping_protocol(jadeapi.jade)
# Only run QR scan/camera tests a) over serial, and b) on proper Jade hw
- if not qemu and not isble and startinfo['BOARD_TYPE'] in ['JADE', 'JADE_V1.1', 'JADE_V2']:
- test_scan_qr(jadeapi)
+ if not qemu and not isble:
+ if args.libjade or startinfo['BOARD_TYPE'] in ['JADE', 'JADE_V1.1', 'JADE_V2']:
+ test_scan_qr(jadeapi)
# Too much input test - sends a lot of data so only run
# if not running over BLE (as would take a long time)
- if not isble:
+ if not isble and not args.libjade and not args.spts:
logger.info(f'Buffer overflow test - PSRAM: {has_psram}')
test_too_much_input(jadeapi.jade, has_psram)
# Negative tests
if negative:
logger.info('Negative tests')
- test_random_bytes(jadeapi.jade)
- test_very_bad_message(jadeapi.jade)
+ if not args.libjade and not args.spts:
+ # TODO: enable these tests at least for args.spts=true
+ test_random_bytes(jadeapi.jade)
+ test_very_bad_message(jadeapi.jade)
test_bad_message(jadeapi.jade)
test_split_message(jadeapi.jade)
test_concatenated_messages(jadeapi.jade)
@@ -4027,6 +4033,11 @@ def mixed_sources_test(serialport, bleid):
def run_all_jade_tests(info):
logger.info('Running Jade tests over selected backend interfaces')
+ if args.libjade:
+ logger.info("Testing libjade")
+ with JadeAPI.create_libjade(timeout=0) as jade:
+ run_jade_tests(jade, isble=False)
+
# 1. Test over serial connection
if not args.skipserial:
logger.info(f'Testing Serial ({args.serialport})')
@@ -4059,6 +4070,11 @@ def run_all_jade_tests(info):Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.