From 64bc88408c0d54cac1008a11608ab505bd3669ed Mon Sep 17 00:00:00 2001 From: graham sanderson Date: Sun, 28 May 2023 19:33:42 -0500 Subject: [PATCH] Squash of rp2040 version --- 3rdparty/miniz/.clang-format | 46 + 3rdparty/miniz/.gitignore | 2 + 3rdparty/miniz/.travis.yml | 2 + 3rdparty/miniz/CMakeLists.txt | 52 + 3rdparty/miniz/ChangeLog.md | 176 + 3rdparty/miniz/LICENSE | 22 + 3rdparty/miniz/amalgamate.sh | 69 + 3rdparty/miniz/examples/example1.c | 105 + 3rdparty/miniz/examples/example2.c | 164 + 3rdparty/miniz/examples/example3.c | 269 + 3rdparty/miniz/examples/example4.c | 102 + 3rdparty/miniz/examples/example5.c | 328 + 3rdparty/miniz/examples/example6.c | 162 + 3rdparty/miniz/miniz.c | 628 ++ 3rdparty/miniz/miniz.h | 477 + 3rdparty/miniz/miniz_common.h | 91 + 3rdparty/miniz/miniz_tdef.c | 1581 +++ 3rdparty/miniz/miniz_tdef.h | 191 + 3rdparty/miniz/miniz_tinfl.c | 746 ++ 3rdparty/miniz/miniz_tinfl.h | 146 + 3rdparty/miniz/miniz_zip.c | 4710 +++++++++ 3rdparty/miniz/miniz_zip.h | 436 + 3rdparty/miniz/readme.md | 37 + 3rdparty/miniz/test.sh | 31 + 3rdparty/miniz/tests/miniz_tester.cpp | 1881 ++++ 3rdparty/miniz/tests/timer.cpp | 152 + 3rdparty/miniz/tests/timer.h | 40 + CMakeLists.txt | 17 + README.md | 161 + README => README_USP | 20 +- devices/device.cpp | 153 +- devices/device.h | 50 +- devices/input/kempston_joy.cpp | 47 +- devices/input/kempston_joy.h | 24 +- devices/input/keyboard.cpp | 69 +- devices/input/keyboard.h | 7 +- devices/input/tape.cpp | 1343 ++- devices/input/tape.h | 275 +- devices/memory.cpp | 167 +- devices/memory.h | 164 +- devices/sound/ay.cpp | 386 +- devices/sound/ay.h | 91 +- devices/sound/beeper.cpp | 47 +- devices/sound/beeper.h | 11 +- devices/sound/device_sound.cpp | 199 +- devices/sound/device_sound.h | 53 +- devices/ula.cpp | 286 +- devices/ula.h | 69 +- embed_tool/CMakeLists.txt | 17 + embed_tool/main.cpp | 288 + file_type.cpp | 19 +- file_type.h | 8 + khan/CMakeLists.txt | 306 + khan/FindEmbedTool.cmake | 44 + khan/fast_ay.S | 709 ++ khan/games/games.h | 25 + khan/games/khan128_games.txt | 15 + khan/games/khan128_turbo_games.txt | 3 + khan/games/khan_games.txt | 13 + khan/khan.c | 1767 ++++ khan/khan.pio | 84 + khan/khan_init.cpp | 163 + khan/khan_init.h | 41 + khan/khan_lib.cpp | 807 ++ khan/khan_lib.h | 164 + khan/memmap_khan.ld | 291 + khan/ram64k.h | 1368 +++ khan/roms/khan128_roms.txt | 4 + khan/roms/khan_roms.txt | 1 + khan/roms/roms.h | 21 + khan/sdl_keys.h | 429 + khan/spoon.cpp | 13 + khan/tusb_config.h | 98 + khan/ui_font.cpp | 1275 +++ khan/z80arm.cpp | 175 + khan/z80arm.h | 186 + khan/z80khan.S | 1704 +++ khan/z80khan.h | 135 + khan/z80khan_gen.S | 9265 +++++++++++++++++ khan/z80t.cpp | 576 + khan/z80t.h | 2329 +++++ options_common.cpp | 220 +- options_common.h | 21 + pico_extras_import.cmake | 57 + pico_sdk_import.cmake | 64 + platform/custom_ui/ui_file_open.cpp | 5 +- platform/custom_ui/ui_keyboard.cpp | 4 +- platform/io.cpp | 36 +- platform/io.h | 11 +- platform/platform.cpp | 6 +- platform/platform.h | 33 +- res/3D_Pacman_1983_Freddy_Kristiansen.z80 | Bin 0 -> 14421 bytes res/CHRON128.SCL | Bin 0 -> 63003 bytes res/CHRON128.sna | Bin 0 -> 49179 bytes res/DOOMDE_1.TAP | Bin 0 -> 10475 bytes res/DOOM_PD.Z80 | Bin 0 -> 73114 bytes res/Donkey_Kong_1986_Ocean.z80 | Bin 0 -> 39025 bytes ...Hobbit_The_v1.2_1982_Melbourne_House_a.z80 | Bin 0 -> 45550 bytes res/JETSET.TAP | Bin 0 -> 33154 bytes res/Jet Set Willy (R D Foord Software).tzx | Bin 0 -> 41069 bytes res/Manic_Miner_1983_Software_Projects.sna | Bin 0 -> 131103 bytes res/Manic_Miner_1983_Software_Projects.z80 | Bin 0 -> 24560 bytes res/ROBOCOP3.TAP | Bin 0 -> 114022 bytes res/Star_Wars_1987_Domark.z80 | Bin 0 -> 40690 bytes res/The Sentinel.tzx | Bin 0 -> 48303 bytes res/aticatac.tap | Bin 0 -> 37370 bytes res/aticatac.tzx | Bin 0 -> 37726 bytes res/ay2.z80 | Bin 0 -> 41936 bytes res/bitbang.tap | Bin 0 -> 568 bytes res/chase.tap | Bin 0 -> 116051 bytes res/dandare1.tap | Bin 0 -> 47114 bytes res/down_abc.tap | Bin 0 -> 86742 bytes res/foo.tap | Bin 0 -> 45846 bytes res/g+g.sna | Bin 0 -> 49179 bytes res/g+g.tap | Bin 0 -> 49574 bytes res/g+g.tzx | Bin 0 -> 49599 bytes res/knightlore.sna | Bin 0 -> 49179 bytes res/knightlore.tzx | Bin 0 -> 39963 bytes res/shock.tap | Bin 0 -> 99937 bytes res/steg.tap | Bin 0 -> 51271 bytes res/thundercats.tap | Bin 0 -> 93883 bytes res/z80ccf.tap | Bin 0 -> 14219 bytes res/z80doc.tap | Bin 0 -> 13758 bytes res/z80docflags.tap | Bin 0 -> 13758 bytes res/z80flags.tap | Bin 0 -> 13758 bytes res/z80full.tap | Bin 0 -> 13758 bytes res/z80memptr.tap | Bin 0 -> 13758 bytes res/z80tests.tap | Bin 0 -> 5573 bytes res/zxpico.tap | Bin 0 -> 7186 bytes snapshot/snapshot.cpp | 392 +- snapshot/snapshot.h | 8 + speccy.cpp | 34 + speccy.h | 7 + speccy_handler.cpp | 346 +- std.h | 12 +- stream/CMakeLists.txt | 12 + stream/stream.c | 577 + stream/stream.h | 126 + tools/io_select.h | 3 + tools/list.h | 4 +- tools/options.cpp | 11 +- tools/options.h | 86 +- z80/z80.cpp | 76 +- z80/z80.h | 122 +- z80/z80_op.h | 75 +- z80/z80_op_cb.h | 245 +- z80/z80_op_dd.h | 364 +- z80/z80_op_ddcb.h | 105 +- z80/z80_op_ed.h | 383 +- z80/z80_op_fd.h | 364 +- z80/z80_op_noprefix.h | 715 +- z80/z80_op_xy.h | 330 + z80/z80_opcodes.cpp | 33 +- 153 files changed, 40548 insertions(+), 2967 deletions(-) create mode 100644 3rdparty/miniz/.clang-format create mode 100644 3rdparty/miniz/.gitignore create mode 100644 3rdparty/miniz/.travis.yml create mode 100644 3rdparty/miniz/CMakeLists.txt create mode 100644 3rdparty/miniz/ChangeLog.md create mode 100644 3rdparty/miniz/LICENSE create mode 100755 3rdparty/miniz/amalgamate.sh create mode 100644 3rdparty/miniz/examples/example1.c create mode 100644 3rdparty/miniz/examples/example2.c create mode 100644 3rdparty/miniz/examples/example3.c create mode 100644 3rdparty/miniz/examples/example4.c create mode 100644 3rdparty/miniz/examples/example5.c create mode 100644 3rdparty/miniz/examples/example6.c create mode 100644 3rdparty/miniz/miniz.c create mode 100644 3rdparty/miniz/miniz.h create mode 100644 3rdparty/miniz/miniz_common.h create mode 100644 3rdparty/miniz/miniz_tdef.c create mode 100644 3rdparty/miniz/miniz_tdef.h create mode 100644 3rdparty/miniz/miniz_tinfl.c create mode 100644 3rdparty/miniz/miniz_tinfl.h create mode 100644 3rdparty/miniz/miniz_zip.c create mode 100644 3rdparty/miniz/miniz_zip.h create mode 100644 3rdparty/miniz/readme.md create mode 100644 3rdparty/miniz/test.sh create mode 100644 3rdparty/miniz/tests/miniz_tester.cpp create mode 100644 3rdparty/miniz/tests/timer.cpp create mode 100644 3rdparty/miniz/tests/timer.h create mode 100644 CMakeLists.txt create mode 100644 README.md rename README => README_USP (97%) create mode 100644 embed_tool/CMakeLists.txt create mode 100644 embed_tool/main.cpp create mode 100644 khan/CMakeLists.txt create mode 100644 khan/FindEmbedTool.cmake create mode 100644 khan/fast_ay.S create mode 100644 khan/games/games.h create mode 100644 khan/games/khan128_games.txt create mode 100644 khan/games/khan128_turbo_games.txt create mode 100644 khan/games/khan_games.txt create mode 100644 khan/khan.c create mode 100644 khan/khan.pio create mode 100644 khan/khan_init.cpp create mode 100644 khan/khan_init.h create mode 100644 khan/khan_lib.cpp create mode 100644 khan/khan_lib.h create mode 100644 khan/memmap_khan.ld create mode 100644 khan/ram64k.h create mode 100644 khan/roms/khan128_roms.txt create mode 100644 khan/roms/khan_roms.txt create mode 100644 khan/roms/roms.h create mode 100644 khan/sdl_keys.h create mode 100644 khan/spoon.cpp create mode 100644 khan/tusb_config.h create mode 100644 khan/ui_font.cpp create mode 100644 khan/z80arm.cpp create mode 100644 khan/z80arm.h create mode 100644 khan/z80khan.S create mode 100644 khan/z80khan.h create mode 100644 khan/z80khan_gen.S create mode 100644 khan/z80t.cpp create mode 100644 khan/z80t.h create mode 100644 pico_extras_import.cmake create mode 100644 pico_sdk_import.cmake create mode 100644 res/3D_Pacman_1983_Freddy_Kristiansen.z80 create mode 100644 res/CHRON128.SCL create mode 100644 res/CHRON128.sna create mode 100644 res/DOOMDE_1.TAP create mode 100644 res/DOOM_PD.Z80 create mode 100644 res/Donkey_Kong_1986_Ocean.z80 create mode 100644 res/Hobbit_The_v1.2_1982_Melbourne_House_a.z80 create mode 100644 res/JETSET.TAP create mode 100644 res/Jet Set Willy (R D Foord Software).tzx create mode 100644 res/Manic_Miner_1983_Software_Projects.sna create mode 100644 res/Manic_Miner_1983_Software_Projects.z80 create mode 100644 res/ROBOCOP3.TAP create mode 100644 res/Star_Wars_1987_Domark.z80 create mode 100644 res/The Sentinel.tzx create mode 100644 res/aticatac.tap create mode 100644 res/aticatac.tzx create mode 100644 res/ay2.z80 create mode 100644 res/bitbang.tap create mode 100644 res/chase.tap create mode 100644 res/dandare1.tap create mode 100644 res/down_abc.tap create mode 100644 res/foo.tap create mode 100644 res/g+g.sna create mode 100644 res/g+g.tap create mode 100644 res/g+g.tzx create mode 100644 res/knightlore.sna create mode 100644 res/knightlore.tzx create mode 100644 res/shock.tap create mode 100644 res/steg.tap create mode 100644 res/thundercats.tap create mode 100644 res/z80ccf.tap create mode 100644 res/z80doc.tap create mode 100644 res/z80docflags.tap create mode 100644 res/z80flags.tap create mode 100644 res/z80full.tap create mode 100644 res/z80memptr.tap create mode 100644 res/z80tests.tap create mode 100644 res/zxpico.tap create mode 100644 stream/CMakeLists.txt create mode 100644 stream/stream.c create mode 100644 stream/stream.h create mode 100644 z80/z80_op_xy.h diff --git a/3rdparty/miniz/.clang-format b/3rdparty/miniz/.clang-format new file mode 100644 index 0000000..5bdf624 --- /dev/null +++ b/3rdparty/miniz/.clang-format @@ -0,0 +1,46 @@ +# +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +AccessModifierOffset: -4 +ConstructorInitializerIndentWidth: 4 +AlignEscapedNewlinesLeft: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: false +AlwaysBreakBeforeMultilineStrings: false +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BinPackParameters: true +ColumnLimit: 0 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +DerivePointerBinding: false +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 60 +PenaltyBreakString: 1000 +PenaltyBreakFirstLessLess: 120 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerBindsToType: false +SpacesBeforeTrailingComments: 1 +Cpp11BracedListStyle: false +Standard: Cpp03 +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +BreakBeforeBraces: Allman +IndentFunctionDeclarationAfterType: false +SpacesInParentheses: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true +SpaceBeforeAssignmentOperators: true +ContinuationIndentWidth: 4 diff --git a/3rdparty/miniz/.gitignore b/3rdparty/miniz/.gitignore new file mode 100644 index 0000000..81631c6 --- /dev/null +++ b/3rdparty/miniz/.gitignore @@ -0,0 +1,2 @@ +/bin +/build diff --git a/3rdparty/miniz/.travis.yml b/3rdparty/miniz/.travis.yml new file mode 100644 index 0000000..7814bfb --- /dev/null +++ b/3rdparty/miniz/.travis.yml @@ -0,0 +1,2 @@ +language: c +script: bash amalgamate.sh \ No newline at end of file diff --git a/3rdparty/miniz/CMakeLists.txt b/3rdparty/miniz/CMakeLists.txt new file mode 100644 index 0000000..d9eb94a --- /dev/null +++ b/3rdparty/miniz/CMakeLists.txt @@ -0,0 +1,52 @@ +PROJECT(miniz_actual C) +cmake_minimum_required(VERSION 2.8.12) +if(CMAKE_BUILD_TYPE STREQUAL "") + # CMake defaults to leaving CMAKE_BUILD_TYPE empty. This screws up + # differentiation between debug and release builds. + set(CMAKE_BUILD_TYPE "Release" CACHE STRING + "Choose the type of build, options are: None (CMAKE_CXX_FLAGS or \ +CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE) +endif () + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) + +set(miniz_SOURCE miniz.c miniz_zip.c miniz_tinfl.c miniz_tdef.c) + +add_library(miniz_actual ${miniz_SOURCE}) +target_include_directories(miniz_actual PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") + +set(EXAMPLE1_SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/examples/example1.c") +set(EXAMPLE2_SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/examples/example2.c") +set(EXAMPLE3_SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/examples/example3.c") +set(EXAMPLE4_SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/examples/example4.c") +set(EXAMPLE5_SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/examples/example5.c") +set(EXAMPLE6_SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/examples/example6.c") +set(MINIZ_TESTER_SRC_LIST + "${CMAKE_CURRENT_SOURCE_DIR}/tests/miniz_tester.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/timer.cpp") + +add_executable(example1 ${EXAMPLE1_SRC_LIST}) +target_link_libraries(example1 miniz_actual) +add_executable(example2 ${EXAMPLE2_SRC_LIST}) +target_link_libraries(example2 miniz_actual) +add_executable(example3 ${EXAMPLE3_SRC_LIST}) +target_link_libraries(example3 miniz_actual) +add_executable(example4 ${EXAMPLE4_SRC_LIST}) +target_link_libraries(example4 miniz_actual) +add_executable(example5 ${EXAMPLE5_SRC_LIST}) +target_link_libraries(example5 miniz_actual) +add_executable(example6 ${EXAMPLE6_SRC_LIST}) +target_link_libraries(example6 miniz_actual) +if(${UNIX}) + target_link_libraries(example6 m) +endif() + +# add_executable(miniz_tester ${MINIZ_TESTER_SRC_LIST}) +# target_link_libraries(miniz_tester miniz) + +install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + ) +file(GLOB INSTALL_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.h) +install(FILES ${INSTALL_HEADERS} DESTINATION include/${PROJECT_NAME}) \ No newline at end of file diff --git a/3rdparty/miniz/ChangeLog.md b/3rdparty/miniz/ChangeLog.md new file mode 100644 index 0000000..c821783 --- /dev/null +++ b/3rdparty/miniz/ChangeLog.md @@ -0,0 +1,176 @@ +## Changelog + +### 2.1.0 + + - More instances of memcpy instead of cast and use memcpy per default + - Remove inline for c90 support + - New function to read files via callback functions when adding them + - Fix out of bounds read while reading Zip64 extended information + - guard memcpy when n == 0 because buffer may be NULL + - Implement inflateReset() function + - Move comp/decomp alloc/free prototypes under guarding #ifndef MZ_NO_MALLOC + - Fix large file support under Windows + - Don't warn if _LARGEFILE64_SOURCE is not defined to 1 + - Fixes for MSVC warnings + - Remove check that path of file added to archive contains ':' or '\' + - Add !defined check on MINIZ_USE_ALIGNED_LOADS_AND_STORES + +### 2.0.8 + + - Remove unimplemented functions (mz_zip_locate_file and mz_zip_locate_file_v2) + - Add license, changelog, readme and example files to release zip + - Fix heap overflow to user buffer in tinfl_status tinfl_decompress + - Fix corrupt archive if uncompressed file smaller than 4 byte and the file is added by mz_zip_writer_add_mem* + +### 2.0.7 + + - Removed need in C++ compiler in cmake build + - Fixed a lot of uninitialized value errors found with Valgrind by memsetting m_dict to 0 in tdefl_init + - Fix resource leak in mz_zip_reader_init_file_v2 + - Fix assert with mz_zip_writer_add_mem* w/MZ_DEFAULT_COMPRESSION + - cmake build: install library and headers + - Remove _LARGEFILE64_SOURCE requirement from apple defines for large files + +### 2.0.6 + + - Improve MZ_ZIP_FLAG_WRITE_ZIP64 documentation + - Remove check for cur_archive_file_ofs > UINT_MAX because cur_archive_file_ofs is not used after this point + - Add cmake debug configuration + - Fix PNG height when creating png files + - Add "iterative" file extraction method based on mz_zip_reader_extract_to_callback. + - Option to use memcpy for unaligned data access + - Define processor/arch macros as zero if not set to one + +### 2.0.4/2.0.5 + + - Fix compilation with the various omission compile definitions + +### 2.0.3 + +- Fix GCC/clang compile warnings +- Added callback for periodic flushes (for ZIP file streaming) +- Use UTF-8 for file names in ZIP files per default + +### 2.0.2 + +- Fix source backwards compatibility with 1.x +- Fix a ZIP bit not being set correctly + +### 2.0.1 + +- Added some tests +- Added CI +- Make source code ANSI C compatible + +### 2.0.0 beta + +- Matthew Sitton merged miniz 1.x to Rich Geldreich's vogl ZIP64 changes. Miniz is now licensed as MIT since the vogl code base is MIT licensed +- Miniz is now split into several files +- Miniz does now not seek backwards when creating ZIP files. That is the ZIP files can be streamed +- Miniz automatically switches to the ZIP64 format when the created ZIP files goes over ZIP file limits +- Similar to [SQLite](https://www.sqlite.org/amalgamation.html) the Miniz source code is amalgamated into one miniz.c/miniz.h pair in a build step (amalgamate.sh). Please use miniz.c/miniz.h in your projects +- Miniz 2 is only source back-compatible with miniz 1.x. It breaks binary compatibility because structures changed + +### v1.16 BETA Oct 19, 2013 + +Still testing, this release is downloadable from [here](http://www.tenacioussoftware.com/miniz_v116_beta_r1.7z). Two key inflator-only robustness and streaming related changes. Also merged in tdefl_compressor_alloc(), tdefl_compressor_free() helpers to make script bindings easier for rustyzip. I would greatly appreciate any help with testing or any feedback. + +The inflator in raw (non-zlib) mode is now usable on gzip or similar streams that have a bunch of bytes following the raw deflate data (problem discovered by rustyzip author williamw520). This version should never read beyond the last byte of the raw deflate data independent of how many bytes you pass into the input buffer. + +The inflator now has a new failure status TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS (-4). Previously, if the inflator was starved of bytes and could not make progress (because the input buffer was empty and the caller did not set the TINFL_FLAG_HAS_MORE_INPUT flag - say on truncated or corrupted compressed data stream) it would append all 0's to the input and try to soldier on. This is scary behavior if the caller didn't know when to stop accepting output (because it didn't know how much uncompressed data was expected, or didn't enforce a sane maximum). v1.16 will instead return TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS immediately if it needs 1 or more bytes to make progress, the input buf is empty, and the caller has indicated that no more input is available. This is a "soft" failure, so you can call the inflator again with more input and it will try to continue, or you can give up and fail. This could be very useful in network streaming scenarios. + +- The inflator coroutine func. is subtle and complex so I'm being cautious about this release. I would greatly appreciate any help with testing or any feedback. + I feel good about these changes, and they've been through several hours of automated testing, but they will probably not fix anything for the majority of prev. users so I'm + going to mark this release as beta for a few weeks and continue testing it at work/home on various things. +- The inflator in raw (non-zlib) mode is now usable on gzip or similiar data streams that have a bunch of bytes following the raw deflate data (problem discovered by rustyzip author williamw520). + This version should *never* read beyond the last byte of the raw deflate data independent of how many bytes you pass into the input buffer. This issue was caused by the various Huffman bitbuffer lookahead optimizations, and + would not be an issue if the caller knew and enforced the precise size of the raw compressed data *or* if the compressed data was in zlib format (i.e. always followed by the byte aligned zlib adler32). + So in other words, you can now call the inflator on deflate streams that are followed by arbitrary amounts of data and it's guaranteed that decompression will stop exactly on the last byte. +- The inflator now has a new failure status: TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS (-4). Previously, if the inflator was starved of bytes and could not make progress (because the input buffer was empty and the + caller did not set the TINFL_FLAG_HAS_MORE_INPUT flag - say on truncated or corrupted compressed data stream) it would append all 0's to the input and try to soldier on. + This is scary, because in the worst case, I believe it was possible for the prev. inflator to start outputting large amounts of literal data. If the caller didn't know when to stop accepting output + (because it didn't know how much uncompressed data was expected, or didn't enforce a sane maximum) it could continue forever. v1.16 cannot fall into this failure mode, instead it'll return + TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS immediately if it needs 1 or more bytes to make progress, the input buf is empty, and the caller has indicated that no more input is available. This is a "soft" + failure, so you can call the inflator again with more input and it will try to continue, or you can give up and fail. This could be very useful in network streaming scenarios. +- Added documentation to all the tinfl return status codes, fixed miniz_tester so it accepts double minus params for Linux, tweaked example1.c, added a simple "follower bytes" test to miniz_tester.cpp. +### v1.15 r4 STABLE - Oct 13, 2013 + +Merged over a few very minor bug fixes that I fixed in the zip64 branch. This is downloadable from [here](http://code.google.com/p/miniz/downloads/list) and also in SVN head (as of 10/19/13). + + +### v1.15 - Oct. 13, 2013 + +Interim bugfix release while I work on the next major release with zip64 and streaming compression/decompression support. Fixed the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com), which could cause the locate files func to not find files when this flag was specified. Also fixed a bug in mz_zip_reader_extract_to_mem_no_alloc() with user provided read buffers (thanks kymoon). I also merged lots of compiler fixes from various github repo branches and Google Code issue reports. I finally added cmake support (only tested under for Linux so far), compiled and tested with clang v3.3 and gcc 4.6 (under Linux), added defl_write_image_to_png_file_in_memory_ex() (supports Y flipping for OpenGL use, real-time compression), added a new PNG example (example6.c - Mandelbrot), and I added 64-bit file I/O support (stat64(), etc.) for glibc. + +- Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug + would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() + (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). +- Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size +- Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. + Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). +- Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes +- mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed +- Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. +- Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti +- Merged MZ_FORCEINLINE fix from hdeanclark +- Fix include before config #ifdef, thanks emil.brink +- Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can + set it to 1 for real-time compression). +- Merged in some compiler fixes from paulharris's github repro. +- Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. +- Added example6.c, which dumps an image of the mandelbrot set to a PNG file. +- Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. +- In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled +- In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch + +### v1.14 - May 20, 2012 + +(SVN Only) Minor tweaks to get miniz.c compiling with the Tiny C Compiler, added #ifndef MINIZ_NO_TIME guards around utime.h includes. Adding mz_free() function, so the caller can free heap blocks returned by miniz using whatever heap functions it has been configured to use, MSVC specific fixes to use "safe" variants of several functions (localtime_s, fopen_s, freopen_s). + +MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + +Compiler specific fixes, some from fermtect. I upgraded to TDM GCC 4.6.1 and now static __forceinline is giving it fits, so I'm changing all usage of __forceinline to MZ_FORCEINLINE and forcing gcc to use __attribute__((__always_inline__)) (and MSVC to use __forceinline). Also various fixes from fermtect for MinGW32: added #include , 64-bit ftell/fseek fixes. + +### v1.13 - May 19, 2012 + +From jason@cornsyrup.org and kelwert@mtu.edu - Most importantly, fixed mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bits. Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. Other stuff: + +Eliminated a bunch of warnings when compiling with GCC 32-bit/64. Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). + +Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) + +Fix ftell() usage in a few of the examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). Fix fail logic handling in mz_zip_add_mem_to_archive_file_in_place() so it always calls mz_zip_writer_finalize_archive() and mz_zip_writer_end(), even if the file add fails. + +- From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. +- Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. +- Eliminated a bunch of warnings when compiling with GCC 32-bit/64. +- Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly +"Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). +- Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. +- Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. +- Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. +- Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) +- Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). + +### v1.12 - 4/12/12 + +More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. +level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. + +### v1.11 - 5/28/11 + +Added statement from unlicense.org + +### v1.10 - 5/27/11 + +- Substantial compressor optimizations: +- Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). +- Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. +- Refactored the compression code for better readability and maintainability. +- Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large drop in throughput on some files). + +### v1.09 - 5/15/11 + +Initial stable release. + + diff --git a/3rdparty/miniz/LICENSE b/3rdparty/miniz/LICENSE new file mode 100644 index 0000000..b6ff45a --- /dev/null +++ b/3rdparty/miniz/LICENSE @@ -0,0 +1,22 @@ +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. diff --git a/3rdparty/miniz/amalgamate.sh b/3rdparty/miniz/amalgamate.sh new file mode 100755 index 0000000..eea63d4 --- /dev/null +++ b/3rdparty/miniz/amalgamate.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +set -e + +mkdir -p amalgamation +OUTPUT_PREFIX=amalgamation/miniz + +cat miniz.h > $OUTPUT_PREFIX.h +cat miniz.c > $OUTPUT_PREFIX.c +cat miniz_common.h >> $OUTPUT_PREFIX.h +cat miniz_tdef.c >> $OUTPUT_PREFIX.c +cat miniz_tdef.h >> $OUTPUT_PREFIX.h +cat miniz_tinfl.c >> $OUTPUT_PREFIX.c +cat miniz_tinfl.h >> $OUTPUT_PREFIX.h +cat miniz_zip.c >> $OUTPUT_PREFIX.c +cat miniz_zip.h >> $OUTPUT_PREFIX.h + + +sed -i '0,/#include "miniz.h"/{s/#include "miniz.h"/#include "miniz.h"/}' $OUTPUT_PREFIX.c +for i in miniz miniz_common miniz_tdef miniz_tinfl miniz_zip +do + sed -i "s/#include \"$i.h\"//g" $OUTPUT_PREFIX.h + sed -i "s/#include \"$i.h\"//g" $OUTPUT_PREFIX.c +done + +echo "int main() { return 0; }" > main.c +echo "Test compile with GCC..." +gcc -pedantic -Wall main.c $OUTPUT_PREFIX.c -o test.out +echo "Test compile with GCC ANSI..." +gcc -ansi -pedantic -Wall main.c $OUTPUT_PREFIX.c -o test.out +if command -v clang +then + echo "Test compile with clang..." + clang -Wall -Wpedantic -fsanitize=unsigned-integer-overflow main.c $OUTPUT_PREFIX.c -o test.out +fi +for def in MINIZ_NO_STDIO MINIZ_NO_TIME MINIZ_NO_ARCHIVE_APIS MINIZ_NO_ARCHIVE_WRITING_APIS MINIZ_NO_ZLIB_APIS MINIZ_NO_ZLIB_COMPATIBLE_NAMES MINIZ_NO_MALLOC +do + echo "Test compile with GCC and define $def..." + gcc -ansi -pedantic -Wall main.c $OUTPUT_PREFIX.c -o test.out -D${def} +done +rm test.out +rm main.c + +cp ChangeLog.md amalgamation/ +cp LICENSE amalgamation/ +cp readme.md amalgamation/ +mkdir -p amalgamation/examples +cp examples/* amalgamation/examples/ + +cd amalgamation +! test -e miniz.zip || rm miniz.zip +cat << EOF | zip -@ miniz +miniz.c +miniz.h +ChangeLog.md +LICENSE +readme.md +examples/example1.c +examples/example2.c +examples/example3.c +examples/example4.c +examples/example5.c +examples/example6.c +EOF +cd .. + +echo "Amalgamation created." + + diff --git a/3rdparty/miniz/examples/example1.c b/3rdparty/miniz/examples/example1.c new file mode 100644 index 0000000..d6e33fa --- /dev/null +++ b/3rdparty/miniz/examples/example1.c @@ -0,0 +1,105 @@ +// example1.c - Demonstrates miniz.c's compress() and uncompress() functions (same as zlib's). +// Public domain, May 15 2011, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c. +#include +#include "miniz.h" +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint; + +// The string to compress. +static const char *s_pStr = "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \ + "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \ + "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \ + "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \ + "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \ + "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \ + "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."; + +int main(int argc, char *argv[]) +{ + uint step = 0; + int cmp_status; + uLong src_len = (uLong)strlen(s_pStr); + uLong cmp_len = compressBound(src_len); + uLong uncomp_len = src_len; + uint8 *pCmp, *pUncomp; + uint total_succeeded = 0; + (void)argc, (void)argv; + + printf("miniz.c version: %s\n", MZ_VERSION); + + do + { + // Allocate buffers to hold compressed and uncompressed data. + pCmp = (mz_uint8 *)malloc((size_t)cmp_len); + pUncomp = (mz_uint8 *)malloc((size_t)src_len); + if ((!pCmp) || (!pUncomp)) + { + printf("Out of memory!\n"); + return EXIT_FAILURE; + } + + // Compress the string. + cmp_status = compress(pCmp, &cmp_len, (const unsigned char *)s_pStr, src_len); + if (cmp_status != Z_OK) + { + printf("compress() failed!\n"); + free(pCmp); + free(pUncomp); + return EXIT_FAILURE; + } + + printf("Compressed from %u to %u bytes\n", (mz_uint32)src_len, (mz_uint32)cmp_len); + + if (step) + { + // Purposely corrupt the compressed data if fuzzy testing (this is a very crude fuzzy test). + uint n = 1 + (rand() % 3); + while (n--) + { + uint i = rand() % cmp_len; + pCmp[i] ^= (rand() & 0xFF); + } + } + + // Decompress. + cmp_status = uncompress(pUncomp, &uncomp_len, pCmp, cmp_len); + total_succeeded += (cmp_status == Z_OK); + + if (step) + { + printf("Simple fuzzy test: step %u total_succeeded: %u\n", step, total_succeeded); + } + else + { + if (cmp_status != Z_OK) + { + printf("uncompress failed!\n"); + free(pCmp); + free(pUncomp); + return EXIT_FAILURE; + } + + printf("Decompressed from %u to %u bytes\n", (mz_uint32)cmp_len, (mz_uint32)uncomp_len); + + // Ensure uncompress() returned the expected data. + if ((uncomp_len != src_len) || (memcmp(pUncomp, s_pStr, (size_t)src_len))) + { + printf("Decompression failed!\n"); + free(pCmp); + free(pUncomp); + return EXIT_FAILURE; + } + } + + free(pCmp); + free(pUncomp); + + step++; + + // Keep on fuzzy testing if there's a non-empty command line. + } while (argc >= 2); + + printf("Success.\n"); + return EXIT_SUCCESS; +} diff --git a/3rdparty/miniz/examples/example2.c b/3rdparty/miniz/examples/example2.c new file mode 100644 index 0000000..c3a84ba --- /dev/null +++ b/3rdparty/miniz/examples/example2.c @@ -0,0 +1,164 @@ +// example2.c - Simple demonstration of miniz.c's ZIP archive API's. +// Note this test deletes the test archive file "__mz_example2_test__.zip" in the current directory, then creates a new one with test data. +// Public domain, May 15 2011, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c. + +#if defined(__GNUC__) + // Ensure we get the 64-bit variants of the CRT's file I/O calls + #ifndef _FILE_OFFSET_BITS + #define _FILE_OFFSET_BITS 64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE 1 + #endif +#endif + +#include +#include "miniz_zip.h" + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint; + +// The string to compress. +static const char *s_pTest_str = + "MISSION CONTROL I wouldn't worry too much about the computer. First of all, there is still a chance that he is right, despite your tests, and" \ + "if it should happen again, we suggest eliminating this possibility by allowing the unit to remain in place and seeing whether or not it" \ + "actually fails. If the computer should turn out to be wrong, the situation is still not alarming. The type of obsessional error he may be" \ + "guilty of is not unknown among the latest generation of HAL 9000 computers. It has almost always revolved around a single detail, such as" \ + "the one you have described, and it has never interfered with the integrity or reliability of the computer's performance in other areas." \ + "No one is certain of the cause of this kind of malfunctioning. It may be over-programming, but it could also be any number of reasons. In any" \ + "event, it is somewhat analogous to human neurotic behavior. Does this answer your query? Zero-five-three-Zero, MC, transmission concluded."; + +static const char *s_pComment = "This is a comment"; + +int main(int argc, char *argv[]) +{ + int i, sort_iter; + mz_bool status; + size_t uncomp_size; + mz_zip_archive zip_archive; + void *p; + const int N = 50; + char data[2048]; + char archive_filename[64]; + static const char *s_Test_archive_filename = "__mz_example2_test__.zip"; + + assert((strlen(s_pTest_str) + 64) < sizeof(data)); + + printf("miniz.c version: %s\n", MZ_VERSION); + + (void)argc, (void)argv; + + // Delete the test archive, so it doesn't keep growing as we run this test + remove(s_Test_archive_filename); + + // Append a bunch of text files to the test archive + for (i = (N - 1); i >= 0; --i) + { + sprintf(archive_filename, "%u.txt", i); + sprintf(data, "%u %s %u", (N - 1) - i, s_pTest_str, i); + + // Add a new file to the archive. Note this is an IN-PLACE operation, so if it fails your archive is probably hosed (its central directory may not be complete) but it should be recoverable using zip -F or -FF. So use caution with this guy. + // A more robust way to add a file to an archive would be to read it into memory, perform the operation, then write a new archive out to a temp file and then delete/rename the files. + // Or, write a new archive to disk to a temp file, then delete/rename the files. For this test this API is fine. + status = mz_zip_add_mem_to_archive_file_in_place(s_Test_archive_filename, archive_filename, data, strlen(data) + 1, s_pComment, (uint16)strlen(s_pComment), MZ_BEST_COMPRESSION); + if (!status) + { + printf("mz_zip_add_mem_to_archive_file_in_place failed!\n"); + return EXIT_FAILURE; + } + } + + // Add a directory entry for testing + status = mz_zip_add_mem_to_archive_file_in_place(s_Test_archive_filename, "directory/", NULL, 0, "no comment", (uint16)strlen("no comment"), MZ_BEST_COMPRESSION); + if (!status) + { + printf("mz_zip_add_mem_to_archive_file_in_place failed!\n"); + return EXIT_FAILURE; + } + + // Now try to open the archive. + memset(&zip_archive, 0, sizeof(zip_archive)); + + status = mz_zip_reader_init_file(&zip_archive, s_Test_archive_filename, 0); + if (!status) + { + printf("mz_zip_reader_init_file() failed!\n"); + return EXIT_FAILURE; + } + + // Get and print information about each file in the archive. + for (i = 0; i < (int)mz_zip_reader_get_num_files(&zip_archive); i++) + { + mz_zip_archive_file_stat file_stat; + if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) + { + printf("mz_zip_reader_file_stat() failed!\n"); + mz_zip_reader_end(&zip_archive); + return EXIT_FAILURE; + } + + printf("Filename: \"%s\", Comment: \"%s\", Uncompressed size: %u, Compressed size: %u, Is Dir: %u\n", file_stat.m_filename, file_stat.m_comment, (uint)file_stat.m_uncomp_size, (uint)file_stat.m_comp_size, mz_zip_reader_is_file_a_directory(&zip_archive, i)); + + if (!strcmp(file_stat.m_filename, "directory/")) + { + if (!mz_zip_reader_is_file_a_directory(&zip_archive, i)) + { + printf("mz_zip_reader_is_file_a_directory() didn't return the expected results!\n"); + mz_zip_reader_end(&zip_archive); + return EXIT_FAILURE; + } + } + } + + // Close the archive, freeing any resources it was using + mz_zip_reader_end(&zip_archive); + + // Now verify the compressed data + for (sort_iter = 0; sort_iter < 2; sort_iter++) + { + memset(&zip_archive, 0, sizeof(zip_archive)); + status = mz_zip_reader_init_file(&zip_archive, s_Test_archive_filename, sort_iter ? MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY : 0); + if (!status) + { + printf("mz_zip_reader_init_file() failed!\n"); + return EXIT_FAILURE; + } + + for (i = 0; i < N; i++) + { + sprintf(archive_filename, "%u.txt", i); + sprintf(data, "%u %s %u", (N - 1) - i, s_pTest_str, i); + + // Try to extract all the files to the heap. + p = mz_zip_reader_extract_file_to_heap(&zip_archive, archive_filename, &uncomp_size, 0); + if (!p) + { + printf("mz_zip_reader_extract_file_to_heap() failed!\n"); + mz_zip_reader_end(&zip_archive); + return EXIT_FAILURE; + } + + // Make sure the extraction really succeeded. + if ((uncomp_size != (strlen(data) + 1)) || (memcmp(p, data, strlen(data)))) + { + printf("mz_zip_reader_extract_file_to_heap() failed to extract the proper data\n"); + mz_free(p); + mz_zip_reader_end(&zip_archive); + return EXIT_FAILURE; + } + + printf("Successfully extracted file \"%s\", size %u\n", archive_filename, (uint)uncomp_size); + printf("File data: \"%s\"\n", (const char *)p); + + // We're done. + mz_free(p); + } + + // Close the archive, freeing any resources it was using + mz_zip_reader_end(&zip_archive); + } + + printf("Success.\n"); + return EXIT_SUCCESS; +} diff --git a/3rdparty/miniz/examples/example3.c b/3rdparty/miniz/examples/example3.c new file mode 100644 index 0000000..a97ba84 --- /dev/null +++ b/3rdparty/miniz/examples/example3.c @@ -0,0 +1,269 @@ +// example3.c - Demonstrates how to use miniz.c's deflate() and inflate() functions for simple file compression. +// Public domain, May 15 2011, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c. +// For simplicity, this example is limited to files smaller than 4GB, but this is not a limitation of miniz.c. +#include +#include +#include "miniz.h" + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint; + +#define my_max(a,b) (((a) > (b)) ? (a) : (b)) +#define my_min(a,b) (((a) < (b)) ? (a) : (b)) + +#define BUF_SIZE (1024 * 1024) +static uint8 s_inbuf[BUF_SIZE]; +static uint8 s_outbuf[BUF_SIZE]; + +int main(int argc, char *argv[]) +{ + const char *pMode; + FILE *pInfile, *pOutfile; + uint infile_size; + int level = Z_BEST_COMPRESSION; + z_stream stream; + int p = 1; + const char *pSrc_filename; + const char *pDst_filename; + long file_loc; + + printf("miniz.c version: %s\n", MZ_VERSION); + + if (argc < 4) + { + printf("Usage: example3 [options] [mode:c or d] infile outfile\n"); + printf("\nModes:\n"); + printf("c - Compresses file infile to a zlib stream in file outfile\n"); + printf("d - Decompress zlib stream in file infile to file outfile\n"); + printf("\nOptions:\n"); + printf("-l[0-10] - Compression level, higher values are slower.\n"); + return EXIT_FAILURE; + } + + while ((p < argc) && (argv[p][0] == '-')) + { + switch (argv[p][1]) + { + case 'l': + { + level = atoi(&argv[1][2]); + if ((level < 0) || (level > 10)) + { + printf("Invalid level!\n"); + return EXIT_FAILURE; + } + break; + } + default: + { + printf("Invalid option: %s\n", argv[p]); + return EXIT_FAILURE; + } + } + p++; + } + + if ((argc - p) < 3) + { + printf("Must specify mode, input filename, and output filename after options!\n"); + return EXIT_FAILURE; + } + else if ((argc - p) > 3) + { + printf("Too many filenames!\n"); + return EXIT_FAILURE; + } + + pMode = argv[p++]; + if (!strchr("cCdD", pMode[0])) + { + printf("Invalid mode!\n"); + return EXIT_FAILURE; + } + + pSrc_filename = argv[p++]; + pDst_filename = argv[p++]; + + printf("Mode: %c, Level: %u\nInput File: \"%s\"\nOutput File: \"%s\"\n", pMode[0], level, pSrc_filename, pDst_filename); + + // Open input file. + pInfile = fopen(pSrc_filename, "rb"); + if (!pInfile) + { + printf("Failed opening input file!\n"); + return EXIT_FAILURE; + } + + // Determine input file's size. + fseek(pInfile, 0, SEEK_END); + file_loc = ftell(pInfile); + fseek(pInfile, 0, SEEK_SET); + + if ((file_loc < 0) || (file_loc > INT_MAX)) + { + // This is not a limitation of miniz or tinfl, but this example. + printf("File is too large to be processed by this example.\n"); + return EXIT_FAILURE; + } + + infile_size = (uint)file_loc; + + // Open output file. + pOutfile = fopen(pDst_filename, "wb"); + if (!pOutfile) + { + printf("Failed opening output file!\n"); + return EXIT_FAILURE; + } + + printf("Input file size: %u\n", infile_size); + + // Init the z_stream + memset(&stream, 0, sizeof(stream)); + stream.next_in = s_inbuf; + stream.avail_in = 0; + stream.next_out = s_outbuf; + stream.avail_out = BUF_SIZE; + + if ((pMode[0] == 'c') || (pMode[0] == 'C')) + { + // Compression. + uint infile_remaining = infile_size; + + if (deflateInit(&stream, level) != Z_OK) + { + printf("deflateInit() failed!\n"); + return EXIT_FAILURE; + } + + for ( ; ; ) + { + int status; + if (!stream.avail_in) + { + // Input buffer is empty, so read more bytes from input file. + uint n = my_min(BUF_SIZE, infile_remaining); + + if (fread(s_inbuf, 1, n, pInfile) != n) + { + printf("Failed reading from input file!\n"); + return EXIT_FAILURE; + } + + stream.next_in = s_inbuf; + stream.avail_in = n; + + infile_remaining -= n; + //printf("Input bytes remaining: %u\n", infile_remaining); + } + + status = deflate(&stream, infile_remaining ? Z_NO_FLUSH : Z_FINISH); + + if ((status == Z_STREAM_END) || (!stream.avail_out)) + { + // Output buffer is full, or compression is done, so write buffer to output file. + uint n = BUF_SIZE - stream.avail_out; + if (fwrite(s_outbuf, 1, n, pOutfile) != n) + { + printf("Failed writing to output file!\n"); + return EXIT_FAILURE; + } + stream.next_out = s_outbuf; + stream.avail_out = BUF_SIZE; + } + + if (status == Z_STREAM_END) + break; + else if (status != Z_OK) + { + printf("deflate() failed with status %i!\n", status); + return EXIT_FAILURE; + } + } + + if (deflateEnd(&stream) != Z_OK) + { + printf("deflateEnd() failed!\n"); + return EXIT_FAILURE; + } + } + else if ((pMode[0] == 'd') || (pMode[0] == 'D')) + { + // Decompression. + uint infile_remaining = infile_size; + + if (inflateInit(&stream)) + { + printf("inflateInit() failed!\n"); + return EXIT_FAILURE; + } + + for ( ; ; ) + { + int status; + if (!stream.avail_in) + { + // Input buffer is empty, so read more bytes from input file. + uint n = my_min(BUF_SIZE, infile_remaining); + + if (fread(s_inbuf, 1, n, pInfile) != n) + { + printf("Failed reading from input file!\n"); + return EXIT_FAILURE; + } + + stream.next_in = s_inbuf; + stream.avail_in = n; + + infile_remaining -= n; + } + + status = inflate(&stream, Z_SYNC_FLUSH); + + if ((status == Z_STREAM_END) || (!stream.avail_out)) + { + // Output buffer is full, or decompression is done, so write buffer to output file. + uint n = BUF_SIZE - stream.avail_out; + if (fwrite(s_outbuf, 1, n, pOutfile) != n) + { + printf("Failed writing to output file!\n"); + return EXIT_FAILURE; + } + stream.next_out = s_outbuf; + stream.avail_out = BUF_SIZE; + } + + if (status == Z_STREAM_END) + break; + else if (status != Z_OK) + { + printf("inflate() failed with status %i!\n", status); + return EXIT_FAILURE; + } + } + + if (inflateEnd(&stream) != Z_OK) + { + printf("inflateEnd() failed!\n"); + return EXIT_FAILURE; + } + } + else + { + printf("Invalid mode!\n"); + return EXIT_FAILURE; + } + + fclose(pInfile); + if (EOF == fclose(pOutfile)) + { + printf("Failed writing to output file!\n"); + return EXIT_FAILURE; + } + + printf("Total input bytes: %u\n", (mz_uint32)stream.total_in); + printf("Total output bytes: %u\n", (mz_uint32)stream.total_out); + printf("Success.\n"); + return EXIT_SUCCESS; +} diff --git a/3rdparty/miniz/examples/example4.c b/3rdparty/miniz/examples/example4.c new file mode 100644 index 0000000..eb591d4 --- /dev/null +++ b/3rdparty/miniz/examples/example4.c @@ -0,0 +1,102 @@ +// example4.c - Uses tinfl.c to decompress a zlib stream in memory to an output file +// Public domain, May 15 2011, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c. +#include "miniz_tinfl.h" +#include +#include + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint; + +#define my_max(a,b) (((a) > (b)) ? (a) : (b)) +#define my_min(a,b) (((a) < (b)) ? (a) : (b)) + +static int tinfl_put_buf_func(const void* pBuf, int len, void *pUser) +{ + return len == (int)fwrite(pBuf, 1, len, (FILE*)pUser); +} + +int main(int argc, char *argv[]) +{ + int status; + FILE *pInfile, *pOutfile; + uint infile_size, outfile_size; + size_t in_buf_size; + uint8 *pCmp_data; + long file_loc; + + if (argc != 3) + { + printf("Usage: example4 infile outfile\n"); + printf("Decompresses zlib stream in file infile to file outfile.\n"); + printf("Input file must be able to fit entirely in memory.\n"); + printf("example3 can be used to create compressed zlib streams.\n"); + return EXIT_FAILURE; + } + + // Open input file. + pInfile = fopen(argv[1], "rb"); + if (!pInfile) + { + printf("Failed opening input file!\n"); + return EXIT_FAILURE; + } + + // Determine input file's size. + fseek(pInfile, 0, SEEK_END); + file_loc = ftell(pInfile); + fseek(pInfile, 0, SEEK_SET); + + if ((file_loc < 0) || (file_loc > INT_MAX)) + { + // This is not a limitation of miniz or tinfl, but this example. + printf("File is too large to be processed by this example.\n"); + return EXIT_FAILURE; + } + + infile_size = (uint)file_loc; + + pCmp_data = (uint8 *)malloc(infile_size); + if (!pCmp_data) + { + printf("Out of memory!\n"); + return EXIT_FAILURE; + } + if (fread(pCmp_data, 1, infile_size, pInfile) != infile_size) + { + printf("Failed reading input file!\n"); + return EXIT_FAILURE; + } + + // Open output file. + pOutfile = fopen(argv[2], "wb"); + if (!pOutfile) + { + printf("Failed opening output file!\n"); + return EXIT_FAILURE; + } + + printf("Input file size: %u\n", infile_size); + + in_buf_size = infile_size; + status = tinfl_decompress_mem_to_callback(pCmp_data, &in_buf_size, tinfl_put_buf_func, pOutfile, TINFL_FLAG_PARSE_ZLIB_HEADER); + if (!status) + { + printf("tinfl_decompress_mem_to_callback() failed with status %i!\n", status); + return EXIT_FAILURE; + } + + outfile_size = ftell(pOutfile); + + fclose(pInfile); + if (EOF == fclose(pOutfile)) + { + printf("Failed writing to output file!\n"); + return EXIT_FAILURE; + } + + printf("Total input bytes: %u\n", (uint)in_buf_size); + printf("Total output bytes: %u\n", outfile_size); + printf("Success.\n"); + return EXIT_SUCCESS; +} diff --git a/3rdparty/miniz/examples/example5.c b/3rdparty/miniz/examples/example5.c new file mode 100644 index 0000000..de23a53 --- /dev/null +++ b/3rdparty/miniz/examples/example5.c @@ -0,0 +1,328 @@ +// example5.c - Demonstrates how to use miniz.c's low-level tdefl_compress() and tinfl_inflate() API's for simple file to file compression/decompression. +// The low-level API's are the fastest, make no use of dynamic memory allocation, and are the most flexible functions exposed by miniz.c. +// Public domain, April 11 2012, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c. +// For simplicity, this example is limited to files smaller than 4GB, but this is not a limitation of miniz.c. + +// Purposely disable a whole bunch of stuff this low-level example doesn't use. +#define MINIZ_NO_STDIO +#define MINIZ_NO_ARCHIVE_APIS +#define MINIZ_NO_TIME +#define MINIZ_NO_ZLIB_APIS +#define MINIZ_NO_MALLOC +#include "miniz.h" + +// Now include stdio.h because this test uses fopen(), etc. (but we still don't want miniz.c's stdio stuff, for testing). +#include +#include + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint; + +#define my_max(a,b) (((a) > (b)) ? (a) : (b)) +#define my_min(a,b) (((a) < (b)) ? (a) : (b)) + +// IN_BUF_SIZE is the size of the file read buffer. +// IN_BUF_SIZE must be >= 1 +#define IN_BUF_SIZE (1024*4) +static uint8 s_inbuf[IN_BUF_SIZE]; + +// COMP_OUT_BUF_SIZE is the size of the output buffer used during compression. +// COMP_OUT_BUF_SIZE must be >= 1 and <= OUT_BUF_SIZE +//#define COMP_OUT_BUF_SIZE (1024*512) +#define COMP_OUT_BUF_SIZE OUT_BUF_SIZE + +// OUT_BUF_SIZE is the size of the output buffer used during decompression. +// OUT_BUF_SIZE must be a power of 2 >= TINFL_LZ_DICT_SIZE (because the low-level decompressor not only writes, but reads from the output buffer as it decompresses) +#define OUT_BUF_SIZE (TINFL_LZ_DICT_SIZE) +//#define OUT_BUF_SIZE (1024*512) +static uint8 s_outbuf[OUT_BUF_SIZE]; + +// tdefl_compressor contains all the state needed by the low-level compressor so it's a pretty big struct (~300k). +// This example makes it a global vs. putting it on the stack, of course in real-world usage you'll probably malloc() or new it. +tdefl_compressor g_deflator; + +int main(int argc, char *argv[]) +{ + const char *pMode; + FILE *pInfile, *pOutfile; + uint infile_size; + int level = 9; + int p = 1; + const char *pSrc_filename; + const char *pDst_filename; + const void *next_in = s_inbuf; + size_t avail_in = 0; + void *next_out = s_outbuf; + size_t avail_out = OUT_BUF_SIZE; + size_t total_in = 0, total_out = 0; + long file_loc; + + assert(COMP_OUT_BUF_SIZE <= OUT_BUF_SIZE); + + printf("miniz.c example5 (demonstrates tinfl/tdefl)\n"); + + if (argc < 4) + { + printf("File to file compression/decompression using the low-level tinfl/tdefl API's.\n"); + printf("Usage: example5 [options] [mode:c or d] infile outfile\n"); + printf("\nModes:\n"); + printf("c - Compresses file infile to a zlib stream in file outfile\n"); + printf("d - Decompress zlib stream in file infile to file outfile\n"); + printf("\nOptions:\n"); + printf("-l[0-10] - Compression level, higher values are slower, 0 is none.\n"); + return EXIT_FAILURE; + } + + while ((p < argc) && (argv[p][0] == '-')) + { + switch (argv[p][1]) + { + case 'l': + { + level = atoi(&argv[1][2]); + if ((level < 0) || (level > 10)) + { + printf("Invalid level!\n"); + return EXIT_FAILURE; + } + break; + } + default: + { + printf("Invalid option: %s\n", argv[p]); + return EXIT_FAILURE; + } + } + p++; + } + + if ((argc - p) < 3) + { + printf("Must specify mode, input filename, and output filename after options!\n"); + return EXIT_FAILURE; + } + else if ((argc - p) > 3) + { + printf("Too many filenames!\n"); + return EXIT_FAILURE; + } + + pMode = argv[p++]; + if (!strchr("cCdD", pMode[0])) + { + printf("Invalid mode!\n"); + return EXIT_FAILURE; + } + + pSrc_filename = argv[p++]; + pDst_filename = argv[p++]; + + printf("Mode: %c, Level: %u\nInput File: \"%s\"\nOutput File: \"%s\"\n", pMode[0], level, pSrc_filename, pDst_filename); + + // Open input file. + pInfile = fopen(pSrc_filename, "rb"); + if (!pInfile) + { + printf("Failed opening input file!\n"); + return EXIT_FAILURE; + } + + // Determine input file's size. + fseek(pInfile, 0, SEEK_END); + file_loc = ftell(pInfile); + fseek(pInfile, 0, SEEK_SET); + + if ((file_loc < 0) || (file_loc > INT_MAX)) + { + // This is not a limitation of miniz or tinfl, but this example. + printf("File is too large to be processed by this example.\n"); + return EXIT_FAILURE; + } + + infile_size = (uint)file_loc; + + // Open output file. + pOutfile = fopen(pDst_filename, "wb"); + if (!pOutfile) + { + printf("Failed opening output file!\n"); + return EXIT_FAILURE; + } + + printf("Input file size: %u\n", infile_size); + + if ((pMode[0] == 'c') || (pMode[0] == 'C')) + { + // The number of dictionary probes to use at each compression level (0-10). 0=implies fastest/minimal possible probing. + static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + + tdefl_status status; + uint infile_remaining = infile_size; + + // create tdefl() compatible flags (we have to compose the low-level flags ourselves, or use tdefl_create_comp_flags_from_zip_params() but that means MINIZ_NO_ZLIB_APIS can't be defined). + mz_uint comp_flags = TDEFL_WRITE_ZLIB_HEADER | s_tdefl_num_probes[MZ_MIN(10, level)] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + + // Initialize the low-level compressor. + status = tdefl_init(&g_deflator, NULL, NULL, comp_flags); + if (status != TDEFL_STATUS_OKAY) + { + printf("tdefl_init() failed!\n"); + return EXIT_FAILURE; + } + + avail_out = COMP_OUT_BUF_SIZE; + + // Compression. + for ( ; ; ) + { + size_t in_bytes, out_bytes; + + if (!avail_in) + { + // Input buffer is empty, so read more bytes from input file. + uint n = my_min(IN_BUF_SIZE, infile_remaining); + + if (fread(s_inbuf, 1, n, pInfile) != n) + { + printf("Failed reading from input file!\n"); + return EXIT_FAILURE; + } + + next_in = s_inbuf; + avail_in = n; + + infile_remaining -= n; + //printf("Input bytes remaining: %u\n", infile_remaining); + } + + in_bytes = avail_in; + out_bytes = avail_out; + // Compress as much of the input as possible (or all of it) to the output buffer. + status = tdefl_compress(&g_deflator, next_in, &in_bytes, next_out, &out_bytes, infile_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); + + next_in = (const char *)next_in + in_bytes; + avail_in -= in_bytes; + total_in += in_bytes; + + next_out = (char *)next_out + out_bytes; + avail_out -= out_bytes; + total_out += out_bytes; + + if ((status != TDEFL_STATUS_OKAY) || (!avail_out)) + { + // Output buffer is full, or compression is done or failed, so write buffer to output file. + uint n = COMP_OUT_BUF_SIZE - (uint)avail_out; + if (fwrite(s_outbuf, 1, n, pOutfile) != n) + { + printf("Failed writing to output file!\n"); + return EXIT_FAILURE; + } + next_out = s_outbuf; + avail_out = COMP_OUT_BUF_SIZE; + } + + if (status == TDEFL_STATUS_DONE) + { + // Compression completed successfully. + break; + } + else if (status != TDEFL_STATUS_OKAY) + { + // Compression somehow failed. + printf("tdefl_compress() failed with status %i!\n", status); + return EXIT_FAILURE; + } + } + } + else if ((pMode[0] == 'd') || (pMode[0] == 'D')) + { + // Decompression. + uint infile_remaining = infile_size; + + tinfl_decompressor inflator; + tinfl_init(&inflator); + + for ( ; ; ) + { + size_t in_bytes, out_bytes; + tinfl_status status; + if (!avail_in) + { + // Input buffer is empty, so read more bytes from input file. + uint n = my_min(IN_BUF_SIZE, infile_remaining); + + if (fread(s_inbuf, 1, n, pInfile) != n) + { + printf("Failed reading from input file!\n"); + return EXIT_FAILURE; + } + + next_in = s_inbuf; + avail_in = n; + + infile_remaining -= n; + } + + in_bytes = avail_in; + out_bytes = avail_out; + status = tinfl_decompress(&inflator, (const mz_uint8 *)next_in, &in_bytes, s_outbuf, (mz_uint8 *)next_out, &out_bytes, (infile_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0) | TINFL_FLAG_PARSE_ZLIB_HEADER); + + avail_in -= in_bytes; + next_in = (const mz_uint8 *)next_in + in_bytes; + total_in += in_bytes; + + avail_out -= out_bytes; + next_out = (mz_uint8 *)next_out + out_bytes; + total_out += out_bytes; + + if ((status <= TINFL_STATUS_DONE) || (!avail_out)) + { + // Output buffer is full, or decompression is done, so write buffer to output file. + uint n = OUT_BUF_SIZE - (uint)avail_out; + if (fwrite(s_outbuf, 1, n, pOutfile) != n) + { + printf("Failed writing to output file!\n"); + return EXIT_FAILURE; + } + next_out = s_outbuf; + avail_out = OUT_BUF_SIZE; + } + + // If status is <= TINFL_STATUS_DONE then either decompression is done or something went wrong. + if (status <= TINFL_STATUS_DONE) + { + if (status == TINFL_STATUS_DONE) + { + // Decompression completed successfully. + break; + } + else + { + // Decompression failed. + printf("tinfl_decompress() failed with status %i!\n", status); + return EXIT_FAILURE; + } + } + } + } + else + { + printf("Invalid mode!\n"); + return EXIT_FAILURE; + } + + fclose(pInfile); + if (EOF == fclose(pOutfile)) + { + printf("Failed writing to output file!\n"); + return EXIT_FAILURE; + } + + printf("Total input bytes: %u\n", (mz_uint32)total_in); + printf("Total output bytes: %u\n", (mz_uint32)total_out); + printf("Success.\n"); + return EXIT_SUCCESS; +} diff --git a/3rdparty/miniz/examples/example6.c b/3rdparty/miniz/examples/example6.c new file mode 100644 index 0000000..abbb64f --- /dev/null +++ b/3rdparty/miniz/examples/example6.c @@ -0,0 +1,162 @@ +// example6.c - Demonstrates how to miniz's PNG writer func +// Public domain, April 11 2012, Rich Geldreich, richgel99@gmail.com. See "unlicense" statement at the end of tinfl.c. +// Mandlebrot set code from http://rosettacode.org/wiki/Mandelbrot_set#C +// Must link this example against libm on Linux. + +// Purposely disable a whole bunch of stuff this low-level example doesn't use. +#define MINIZ_NO_STDIO +#define MINIZ_NO_TIME +#define MINIZ_NO_ZLIB_APIS +#include "miniz.h" + +// Now include stdio.h because this test uses fopen(), etc. (but we still don't want miniz.c's stdio stuff, for testing). +#include +#include +#include + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint; + +typedef struct +{ + uint8 r, g, b; +} rgb_t; + +static void hsv_to_rgb(int hue, int min, int max, rgb_t *p) +{ + const int invert = 0; + const int saturation = 1; + const int color_rotate = 0; + + if (min == max) max = min + 1; + if (invert) hue = max - (hue - min); + if (!saturation) { + p->r = p->g = p->b = 255 * (max - hue) / (max - min); + return; + } + double h = fmod(color_rotate + 1e-4 + 4.0 * (hue - min) / (max - min), 6); + double c = 255.0f * saturation; + double X = c * (1 - fabs(fmod(h, 2) - 1)); + + p->r = p->g = p->b = 0; + + switch((int)h) { + case 0: p->r = c; p->g = X; return; + case 1: p->r = X; p->g = c; return; + case 2: p->g = c; p->b = X; return; + case 3: p->g = X; p->b = c; return; + case 4: p->r = X; p->b = c; return; + default:p->r = c; p->b = X; + } +} + +int main(int argc, char *argv[]) +{ + (void)argc, (void)argv; + + // Image resolution + const int iXmax = 4096; + const int iYmax = 4096; + + // Output filename + static const char *pFilename = "mandelbrot.png"; + + int iX, iY; + const double CxMin = -2.5; + const double CxMax = 1.5; + const double CyMin = -2.0; + const double CyMax = 2.0; + + double PixelWidth = (CxMax - CxMin) / iXmax; + double PixelHeight = (CyMax - CyMin) / iYmax; + + // Z=Zx+Zy*i ; Z0 = 0 + double Zx, Zy; + double Zx2, Zy2; // Zx2=Zx*Zx; Zy2=Zy*Zy + + int Iteration; + const int IterationMax = 200; + + // bail-out value , radius of circle + const double EscapeRadius = 2; + double ER2=EscapeRadius * EscapeRadius; + + uint8 *pImage = (uint8 *)malloc(iXmax * 3 * iYmax); + + // world ( double) coordinate = parameter plane + double Cx,Cy; + + int MinIter = 9999, MaxIter = 0; + + for(iY = 0; iY < iYmax; iY++) + { + Cy = CyMin + iY * PixelHeight; + if (fabs(Cy) < PixelHeight/2) + Cy = 0.0; // Main antenna + + for(iX = 0; iX < iXmax; iX++) + { + uint8 *color = pImage + (iX * 3) + (iY * iXmax * 3); + + Cx = CxMin + iX * PixelWidth; + + // initial value of orbit = critical point Z= 0 + Zx = 0.0; + Zy = 0.0; + Zx2 = Zx * Zx; + Zy2 = Zy * Zy; + + for (Iteration=0;Iteration> 8; + color[2] = 0; + + if (Iteration < MinIter) + MinIter = Iteration; + if (Iteration > MaxIter) + MaxIter = Iteration; + } + } + + for(iY = 0; iY < iYmax; iY++) + { + for(iX = 0; iX < iXmax; iX++) + { + uint8 *color = (uint8 *)(pImage + (iX * 3) + (iY * iXmax * 3)); + + uint Iterations = color[0] | (color[1] << 8U); + + hsv_to_rgb(Iterations, MinIter, MaxIter, (rgb_t *)color); + } + } + + // Now write the PNG image. + { + size_t png_data_size = 0; + void *pPNG_data = tdefl_write_image_to_png_file_in_memory_ex(pImage, iXmax, iYmax, 3, &png_data_size, 6, MZ_FALSE); + if (!pPNG_data) + fprintf(stderr, "tdefl_write_image_to_png_file_in_memory_ex() failed!\n"); + else + { + FILE *pFile = fopen(pFilename, "wb"); + fwrite(pPNG_data, 1, png_data_size, pFile); + fclose(pFile); + printf("Wrote %s\n", pFilename); + } + + // mz_free() is by default just an alias to free() internally, but if you've overridden miniz's allocation funcs you'll probably need to call mz_free(). + mz_free(pPNG_data); + } + + free(pImage); + + return EXIT_SUCCESS; +} diff --git a/3rdparty/miniz/miniz.c b/3rdparty/miniz/miniz.c new file mode 100644 index 0000000..d3e7665 --- /dev/null +++ b/3rdparty/miniz/miniz.c @@ -0,0 +1,628 @@ +/************************************************************************** + * + * 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. + * + **************************************************************************/ + +#include "miniz.h" + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API's */ + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ +#if 0 + mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) + { + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) + { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; + } +#else +/* Faster, but larger CPU cache footprint. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, + 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, + 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, + 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, + 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, + 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, + 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, + 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, + 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, + 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, + 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, + 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, + 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, + 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, + 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, + 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; + const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; + + while (buf_len >= 4) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; + pByte_buf += 4; + buf_len -= 4; + } + + while (buf_len) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + ++pByte_buf; + --buf_len; + } + + return ~crc32; +} +#endif + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +{ + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +void miniz_def_free_func(void *opaque, void *address) +{ + (void)opaque, (void)address; + MZ_FREE(address); +} +void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +{ + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +#ifndef MINIZ_NO_ZLIB_APIS + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; /* Can't make forward progress without some input. + */ + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflateReset(mz_streamp pStream) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + + pDecomp = (inflate_state *)pStream->state; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + /* pDecomp->m_window_bits = window_bits */; + + return MZ_OK; +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + /* flush != MZ_FINISH then we must assume there's more input. */ + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for (;;) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ + else if (flush == MZ_FINISH) + { + /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static struct + { + int m_err; + const char *m_pDesc; + } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif /*MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ diff --git a/3rdparty/miniz/miniz.h b/3rdparty/miniz/miniz.h new file mode 100644 index 0000000..90e4860 --- /dev/null +++ b/3rdparty/miniz/miniz.h @@ -0,0 +1,477 @@ +/* miniz.c 2.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 , 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 zip64 or 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 + +#include "miniz_common.h" +#include "miniz_tdef.h" +#include "miniz_tinfl.h" + +/* Defines to completely disable specific portions of miniz.c: + If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ + +/* 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_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 */ + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ +#define MINIZ_NO_TIME +#endif + +#include + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ +#define MINIZ_X86_OR_X64_CPU 1 +#else +#define MINIZ_X86_OR_X64_CPU 0 +#endif + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ +#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_UNALIGNED_USE_MEMCPY +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ +#define MINIZ_HAS_64BIT_REGISTERS 1 +#else +#define MINIZ_HAS_64BIT_REGISTERS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API Definitions. */ + +/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ +typedef unsigned long mz_ulong; + +/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +/* Compression strategies. */ +enum +{ + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; + +/* Method */ +#define MZ_DEFLATED 8 + +/* Heap allocation callbacks. +Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */ +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ +enum +{ + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +#define MZ_VERSION "10.1.0" +#define MZ_VERNUM 0xA100 +#define MZ_VER_MAJOR 10 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 0 +#define MZ_VER_SUBREVISION 0 + +#ifndef MINIZ_NO_ZLIB_APIS + +/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ +enum +{ + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +/* Return status codes. MZ_PARAM_ERROR is non-standard. */ +enum +{ + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +/* Window bits */ +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +/* Compression/decompression stream struct. */ +typedef struct mz_stream_s +{ + const unsigned char *next_in; /* pointer to next byte to read */ + unsigned int avail_in; /* number of bytes available at next_in */ + mz_ulong total_in; /* total number of bytes consumed so far */ + + unsigned char *next_out; /* pointer to next byte to write */ + unsigned int avail_out; /* number of bytes that can be written to next_out */ + mz_ulong total_out; /* total number of bytes produced so far */ + + char *msg; /* error msg (unused) */ + struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ + + mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ + mz_free_func zfree; /* optional heap free function (defaults to free) */ + void *opaque; /* heap alloc function user pointer */ + + int data_type; /* data_type (unused) */ + mz_ulong adler; /* adler32 of the source or uncompressed data */ + mz_ulong reserved; /* not used */ +} mz_stream; + +typedef mz_stream *mz_streamp; + +/* Returns the version string of miniz.c. */ +const char *mz_version(void); + +/* mz_deflateInit() initializes a compressor with default options: */ +/* Parameters: */ +/* pStream must point to an initialized mz_stream struct. */ +/* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ +/* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ +/* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if the input parameters are bogus. */ +/* MZ_MEM_ERROR on out of memory. */ +int mz_deflateInit(mz_streamp pStream, int level); + +/* mz_deflateInit2() is like mz_deflate(), except with more control: */ +/* Additional parameters: */ +/* method must be MZ_DEFLATED */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ +/* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ +int mz_deflateReset(mz_streamp pStream); + +/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ +/* Return values: */ +/* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ +/* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ +int mz_deflate(mz_streamp pStream, int flush); + +/* mz_deflateEnd() deinitializes a compressor: */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +int mz_deflateEnd(mz_streamp pStream); + +/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +/* Single-call compression functions mz_compress() and mz_compress2(): */ +/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ +mz_ulong mz_compressBound(mz_ulong source_len); + +/* Initializes a decompressor. */ +int mz_inflateInit(mz_streamp pStream); + +/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ +int mz_inflateReset(mz_streamp pStream); + +/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ +/* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ +/* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ +/* Return values: */ +/* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ +/* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_DATA_ERROR if the deflate stream is invalid. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ +/* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ +int mz_inflate(mz_streamp pStream, int flush); + +/* Deinitializes a decompressor. */ +int mz_inflateEnd(mz_streamp pStream); + +/* Single-call decompression. */ +/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +/* Returns a string description of the specified error code, or NULL if the error code is invalid. */ +const char *mz_error(int err); + +/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflateReset mz_inflateReset +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +#endif /* MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif diff --git a/3rdparty/miniz/miniz_common.h b/3rdparty/miniz/miniz_common.h new file mode 100644 index 0000000..a10dae8 --- /dev/null +++ b/3rdparty/miniz/miniz_common.h @@ -0,0 +1,91 @@ +#pragma once +#include +#include +#include +#include + +/* ------------------- Types and macros */ +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef int64_t mz_int64; +typedef uint64_t mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include +#define MZ_FILE FILE +#endif /* #ifdef MINIZ_NO_STDIO */ + +#ifdef MINIZ_NO_TIME +typedef struct mz_dummy_time_t_tag +{ + int m_dummy; +} mz_dummy_time_t; +#define MZ_TIME_T mz_dummy_time_t +#else +#define MZ_TIME_T time_t +#endif + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL +#else +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#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 void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern void miniz_def_free_func(void *opaque, void *address); +extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif diff --git a/3rdparty/miniz/miniz_tdef.c b/3rdparty/miniz/miniz_tdef.c new file mode 100644 index 0000000..0419347 --- /dev/null +++ b/3rdparty/miniz/miniz_tdef.c @@ -0,0 +1,1581 @@ +/************************************************************************** + * + * 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. + * + **************************************************************************/ + +#include "miniz_tdef.h" +#include "miniz.h" + +#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_OBJ(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_OBJ(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_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(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 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], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + 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; + + *(mz_uint64 *)pOutput_buf = bit_buffer; + 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 int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + MZ_ASSERT(TDEFL_LZ_DICT_SIZE_BITS >= 8); + int hdr0 = 0x08 | ((TDEFL_LZ_DICT_SIZE_BITS - 8) << 4); + TDEFL_PUT_BITS(hdr0, 8); + TDEFL_PUT_BITS(31 - (hdr0 << 8)%31 , 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ + if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) + { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) + { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) + { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } + else + { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) + { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) +#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) +#endif +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) + continue; + p = s; + probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + if (!probe_len) + { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); + break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p) +{ + mz_uint32 ret; + memcpy(&ret, p, sizeof(mz_uint32)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) +#endif +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); +#else + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; +#endif + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + 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 + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + /* Simple lazy/greedy parsing state machine. */ + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) + { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + /* Move the lookahead forward by len_to_move bytes. */ + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); + /* Check if it's time to flush the current LZ codes to the internal output buffer. */ + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) + { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) + { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(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_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_OBJ(d->m_dict); + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do + { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ +#endif + +/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at + http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. + This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) + { + MZ_FREE(pComp); + return NULL; + } + /* write dummy header */ + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + /* compress image data */ + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) + { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) + { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + /* write real header */ + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; + mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, + 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, + 0x52, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x44, 0x41, + 0x54 }; + pnghdr[18] = (mz_uint8)(w >> 8); + pnghdr[19] = (mz_uint8)w; + pnghdr[22] = (mz_uint8)(h >> 8); + pnghdr[23] = (mz_uint8)h; + pnghdr[25] = chans[num_chans]; + pnghdr[33] = (mz_uint8)(*pLen_out >> 24); + pnghdr[34] = (mz_uint8)(*pLen_out >> 16); + pnghdr[35] = (mz_uint8)(*pLen_out >> 8); + pnghdr[36] = (mz_uint8)*pLen_out; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + /* write footer (IDAT CRC-32, followed by IEND chunk) */ + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) + { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + /* compute final size of file, grab compressed data buffer and return */ + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ +/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc() +{ + return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); +} + +void tdefl_compressor_free(tdefl_compressor *pComp) +{ + MZ_FREE(pComp); +} +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif diff --git a/3rdparty/miniz/miniz_tdef.h b/3rdparty/miniz/miniz_tdef.h new file mode 100644 index 0000000..22c1355 --- /dev/null +++ b/3rdparty/miniz/miniz_tdef.h @@ -0,0 +1,191 @@ +#pragma once +#include "miniz_common.h" + +#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). */ +#define TDEFL_LESS_MEMORY 0 + +/* 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. */ +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ +/* Returns 0 on failure. */ +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* 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. */ +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); + +/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +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_BITS = 12, //15, + TDEFL_LZ_DICT_SIZE = (1u << TDEFL_LZ_DICT_SIZE_BITS), + 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 = (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.) */ +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ +/* tdefl_compress_buffer() always consumes the entire input buffer. */ +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +/* Create tdefl_compress() flags given zlib-style compression parameters. */ +/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ +/* window_bits may be -15 (raw deflate) or 15 (zlib) */ +/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor structure in C so that */ +/* non-C language bindings to tdefl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc(void); +void tdefl_compressor_free(tdefl_compressor *pComp); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/3rdparty/miniz/miniz_tinfl.c b/3rdparty/miniz/miniz_tinfl.c new file mode 100644 index 0000000..4832642 --- /dev/null +++ b/3rdparty/miniz/miniz_tinfl.c @@ -0,0 +1,746 @@ +/************************************************************************** + * + * 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. + * + **************************************************************************/ + +#include "miniz_tinfl.h" + +#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, pHuff) \ + do \ + { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) \ + { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } \ + else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ +/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ +/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ +/* The slow path is only executed at the very end of the input buffer. */ +/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ +/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do \ + { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) \ + { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) \ + { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } \ + else \ + { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +#ifdef USE_MU +#include "pico.h" +#define TINFL_CALL __not_in_flash("tinfl_decompress") +#else +#define TINFL_CALL +#endif +tinfl_status TINFL_CALL tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; + static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; + static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; + static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) + { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) + { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) + { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) + { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) + { + 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_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) + { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) + { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; + tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) + { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) + { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) + { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) + { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) + { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) + { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for (;;) + { + mz_uint8 *pSrc; + for (;;) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { +#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)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + 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)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +/* Higher level helper functions. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +#ifndef MINIZ_NO_MALLOC +tinfl_decompressor *tinfl_decompressor_alloc() +{ + tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); + if (pDecomp) + tinfl_init(pDecomp); + return pDecomp; +} + +void tinfl_decompressor_free(tinfl_decompressor *pDecomp) +{ + MZ_FREE(pDecomp); +} +#endif + +#ifdef __cplusplus +} +#endif diff --git a/3rdparty/miniz/miniz_tinfl.h b/3rdparty/miniz/miniz_tinfl.h new file mode 100644 index 0000000..c905b18 --- /dev/null +++ b/3rdparty/miniz/miniz_tinfl.h @@ -0,0 +1,146 @@ +#pragma once +#include "miniz_common.h" +/* ------------------- Low-level Decompression API Definitions */ + +#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. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ +/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ +/* Returns 1 on success or 0 on failure. */ +typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +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. */ +tinfl_decompressor *tinfl_decompressor_alloc(void); +void tinfl_decompressor_free(tinfl_decompressor *pDecomp); +#endif + +/* Max size of LZ dictionary. */ +//#define TINFL_LZ_DICT_SIZE 32768 +#define TINFL_LZ_DICT_SIZE 4096 + +/* Return status. */ +typedef enum { + /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ + /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ + /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ + TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, + + /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ + TINFL_STATUS_BAD_PARAM = -3, + + /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ + TINFL_STATUS_ADLER32_MISMATCH = -2, + + /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ + TINFL_STATUS_FAILED = -1, + + /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ + + /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ + /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ + TINFL_STATUS_DONE = 0, + + /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ + /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ + /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + + /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ + /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ + /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ + /* so I may need to add some code to address this. */ + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +/* Initializes the decompressor to its initial state. */ +#define tinfl_init(r) \ + do \ + { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ +/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ +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_BITS = 4, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#else +#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; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +#ifdef __cplusplus +} +#endif diff --git a/3rdparty/miniz/miniz_zip.c b/3rdparty/miniz/miniz_zip.c new file mode 100644 index 0000000..955ee20 --- /dev/null +++ b/3rdparty/miniz/miniz_zip.c @@ -0,0 +1,4710 @@ +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * Copyright 2016 Martin Raiber + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ +#include "miniz_zip.h" + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- .ZIP archive reading */ + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include + +#if defined(_MSC_VER) || defined(__MINGW64__) +static FILE *mz_fopen(const char *pFilename, const char *pMode) +{ + FILE *pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) +{ + FILE *pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; +} +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat64 +#define MZ_FILE_STAT _stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__GNUC__) && defined(_LARGEFILE64_SOURCE) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__APPLE__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen(p, m, s) +#define MZ_DELETE_FILE remove + +#else +#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#ifdef __STRICT_ANSI__ +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#else +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif /* #ifdef _MSC_VER */ +#endif /* #ifdef MINIZ_NO_STDIO */ + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ +enum +{ + /* ZIP archive identifiers and record sizes */ + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + /* Central directory header record offsets */ + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + + /* Local directory header offsets */ + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, + + /* End of central directory offsets */ + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size + +#if defined(DEBUG) || defined(_DEBUG) || defined(NDEBUG) +static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) +{ + MZ_ASSERT(index < pArray->m_size); + return index; +} +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] +#else +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] +#endif + +static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) +{ + memset(pArray, 0, sizeof(mz_zip_array)); + pArray->m_element_size = element_size; +} + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) + { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + if (n > 0) + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) +{ + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) +{ + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + pZip->m_last_error = MZ_ZIP_NO_ERROR; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + pZip->m_pState->m_init_flags = flags; + pZip->m_pState->m_zip64 = MZ_FALSE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do \ + { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices; + mz_uint32 start, end; + const mz_uint32 size = pZip->m_total_files; + + if (size <= 1U) + return; + + pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + + start = (size - 2U) >> 1U; + for (;;) + { + mz_uint64 child, root = start; + for (;;) + { + if ((child = (root << 1U) + 1U) >= size) + break; + child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + if (!start) + break; + start--; + } + + end = size - 1; + while (end > 0) + { + mz_uint64 child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) + { + if ((child = (root << 1U) + 1U) >= end) + break; + child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) +{ + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) + { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) + { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) + { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) +{ + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) + { + zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) + { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) + { + mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data; + void* buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) + { + buf = MZ_MALLOC(ext_data_size); + if(buf==NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8*)buf; + } + else + { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) + { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) + { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +void mz_zip_zero_struct(mz_zip_archive *pZip) +{ + if (pZip) + MZ_CLEAR_OBJ(*pZip); +} + +static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_bool status = MZ_TRUE; + + if (!pZip) + return MZ_FALSE; + + if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; + + return MZ_FALSE; + } + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; + status = MZ_FALSE; + } + } + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return status; +} + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + return mz_zip_reader_end_internal(pZip, MZ_TRUE); +} +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_archive_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) +{ + if (!pMem) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pNeeds_keepalive = NULL; + +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + + pZip->m_pState->m_mem_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); +} + +mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) +{ + mz_uint64 file_size; + MZ_FILE *pFile; + + if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + file_size = archive_size; + if (!file_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + } + + file_size = MZ_FTELL64(pFile); + } + + /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ + + if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) +{ + mz_uint64 cur_file_ofs; + + if ((!pZip) || (!pFile)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + cur_file_ofs = MZ_FTELL64(pFile); + + if (!archive_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + + archive_size = MZ_FTELL64(pFile) - cur_file_ofs; + + if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = archive_size; + pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; +} + +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint bit_flag; + mz_uint method; + + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); + bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + + if ((method != 0) && (method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + return MZ_FALSE; + } + + if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + return MZ_FALSE; + } + + if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, attribute_mapping_id, external_attr; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ + /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ + /* FIXME: Remove this check? Is it necessary - we already check the filename. */ + attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; + (void)attribute_mapping_id; + + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) + { + return MZ_TRUE; + } + + return MZ_FALSE; +} + +static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) +{ + mz_uint n; + const mz_uint8 *p = pCentral_dir_header; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_FALSE; + + if ((!p) || (!pStat)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Extract fields from the central directory record. */ + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + /* Copy as much of the filename and comment as possible. */ + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); + pStat->m_comment[n] = '\0'; + + /* Set some flags for convienance */ + pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); + pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); + pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); + + /* See if we need to read any zip64 extended information fields. */ + /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ + if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; + mz_uint32 field_data_remaining = field_data_size; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_TRUE; + + if (pStat->m_uncomp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_uncomp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_comp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_comp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_local_header_ofs == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + } + } + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const uint32_t size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + + if (pIndex) + *pIndex = 0; + + if (size) + { + /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ + /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ + mz_int64 l = 0, h = (mz_int64)size - 1; + + while (l <= h) + { + mz_int64 m = l + ((h - l) >> 1); + uint32_t file_index = pIndices[(uint32_t)m]; + + int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint32 index; + if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) + return -1; + else + return (int)index; +} + +mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) +{ + mz_uint file_index; + size_t name_len, comment_len; + + if (pIndex) + *pIndex = 0; + + if ((!pZip) || (!pZip->m_pState) || (!pName)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* See if we can use a binary search */ + if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && + (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && + ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + { + return mz_zip_locate_file_binary_search(pZip, pName, pIndex); + } + + /* Locate the entry by scanning the entire central directory */ + name_len = strlen(pName); + if (name_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Ensure supplied output buffer is large enough. */ + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); + + /* Read and parse the local directory entry. */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) + { + if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + } +#endif + + return MZ_TRUE; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + /* Read directly from the archive in memory. */ + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + /* Use a user provided read buffer. */ + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + /* Temporarily allocate a read buffer. */ + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return NULL; + } + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + return NULL; + } + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; + mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pState->m_pMem) + { + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + } + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); +#endif + } + + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + } +#endif + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + status = TINFL_STATUS_FAILED; + } + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); +#endif + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (file_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_reader_extract_iter_state *pState; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + /* Argument sanity check */ + if ((!pZip) || (!pZip->m_pState)) + return NULL; + + /* Allocate an iterator status structure */ + pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); + if (!pState) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + /* Fetch file details */ + if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Encryption and patch files are not supported. */ + if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Init state - save args */ + pState->pZip = pZip; + pState->flags = flags; + + /* Init state - reset variables to defaults */ + pState->status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + pState->file_crc32 = MZ_CRC32_INIT; +#endif + pState->read_buf_ofs = 0; + pState->out_buf_ofs = 0; + pState->pRead_buf = NULL; + pState->pWrite_buf = NULL; + pState->out_blk_remain = 0; + + /* Read and parse the local directory entry. */ + pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; + pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + else + { + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, therefore intermediate read buffer required */ + pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + else + { + /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ + pState->read_buf_size = 0; + } + pState->read_buf_avail = 0; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, init decompressor */ + tinfl_init( &pState->inflator ); + + /* Allocate write buffer */ + if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + if (pState->pRead_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + + return pState; +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_uint32 file_index; + + /* Locate file index by name */ + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return NULL; + + /* Construct iterator */ + return mz_zip_reader_extract_iter_new(pZip, file_index, flags); +} + +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) +{ + size_t copied_to_caller = 0; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) + return 0; + + if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data, calc amount to return. */ + copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining ); + + /* Zip is in memory....or requires reading from a file? */ + if (pState->pZip->m_pState->m_pMem) + { + /* Copy data to caller's buffer */ + memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); + pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; + } + else + { + /* Read directly into caller's buffer */ + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) + { + /* Failed to read all that was asked for, flag failure and alert user */ + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + copied_to_caller = 0; + } + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Compute CRC if not returning compressed data only */ + if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); +#endif + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += copied_to_caller; + pState->out_buf_ofs += copied_to_caller; + pState->comp_remaining -= copied_to_caller; + } + else + { + do + { + /* Calc ptr to write buffer - given current output pos and block size */ + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + /* Calc max output size - given current output pos and block size */ + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + if (!pState->out_blk_remain) + { + /* Read more data from file if none available (and reading from file) */ + if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) + { + /* Calc read size */ + pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += pState->read_buf_avail; + pState->comp_remaining -= pState->read_buf_avail; + pState->read_buf_ofs = 0; + } + + /* Perform decompression */ + in_buf_size = (size_t)pState->read_buf_avail; + pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + pState->read_buf_avail -= in_buf_size; + pState->read_buf_ofs += in_buf_size; + + /* Update current output block size remaining */ + pState->out_blk_remain = out_buf_size; + } + + if (pState->out_blk_remain) + { + /* Calc amount to return. */ + size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); + + /* Copy data to caller's buffer */ + memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Perform CRC */ + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); +#endif + + /* Decrement data consumed from block */ + pState->out_blk_remain -= to_copy; + + /* Inc output offset, while performing sanity check */ + if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Increment counter of data copied to caller */ + copied_to_caller += to_copy; + } + } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); + } + + /* Return how many bytes were copied into user buffer */ + return copied_to_caller; +} + +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) +{ + int status; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) + return MZ_FALSE; + + /* Was decompression completed and requested? */ + if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + pState->status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (pState->file_crc32 != pState->file_stat.m_crc32) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + } +#endif + } + + /* Free buffers */ + if (!pState->pZip->m_pState->m_pMem) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); + if (pState->pWrite_buf) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); + + /* Save status */ + status = pState->status; + + /* Free context */ + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); + + return status == TINFL_STATUS_DONE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; + + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + + if (MZ_FCLOSE(pFile) == EOF) + { + if (status) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + + status = MZ_FALSE; + } + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + + return status; +} + +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} + +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); +} + +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_uint32 *p = (mz_uint32 *)pOpaque; + (void)file_ofs; + *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); + return n; +} + +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + mz_zip_internal_state *pState; + const mz_uint8 *pCentral_dir_header; + mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint64 local_header_ofs = 0; + mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; + mz_bool has_data_descriptor; + mz_uint32 local_header_bit_flags; + + mz_zip_array file_data_array; + mz_zip_array_init(&file_data_array, 1); + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (file_index > pZip->m_total_files) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); + + if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_is_encrypted) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports stored and deflate. */ + if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + if (!file_stat.m_is_supported) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + /* Read and parse the local directory entry. */ + local_header_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); + local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + has_data_descriptor = (local_header_bit_flags & 8) != 0; + + if (local_header_filename_len != strlen(file_stat.m_filename)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (local_header_filename_len) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ + if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_uint32 extra_size_remaining = local_header_extra_len; + const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ + /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ + if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) + { + mz_uint8 descriptor_buf[32]; + mz_bool has_id; + const mz_uint8 *pSrc; + mz_uint32 file_crc32; + mz_uint64 comp_size = 0, uncomp_size = 0; + + mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; + + file_crc32 = MZ_READ_LE32(pSrc); + + if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); + } + else + { + comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); + } + + if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + else + { + if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + mz_zip_array_clear(pZip, &file_data_array); + + if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) + { + if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) + return MZ_FALSE; + + /* 1 more check to be sure, although the extract checks too. */ + if (uncomp_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + return MZ_FALSE; + } + } + + return MZ_TRUE; + +handle_failure: + mz_zip_array_clear(pZip, &file_data_array); + return MZ_FALSE; +} + +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) +{ + mz_zip_internal_state *pState; + uint32_t i; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Basic sanity checks */ + if (!pState->m_zip64) + { + if (pZip->m_total_files > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pZip->m_archive_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + else + { + if (pZip->m_total_files >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + for (i = 0; i < pZip->m_total_files; i++) + { + if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) + { + mz_uint32 found_index; + mz_zip_archive_file_stat stat; + + if (!mz_zip_reader_file_stat(pZip, i, &stat)) + return MZ_FALSE; + + if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) + return MZ_FALSE; + + /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ + if (found_index != i) + return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + } + + if (!mz_zip_validate_file(pZip, i, flags)) + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if ((!pMem) || (!size)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if (!pFilename) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +/* ------------------- .ZIP archive writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) +{ + mz_write_le32(p, (mz_uint32)v); + mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); +} + +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) +#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if (!n) + return 0; + + /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ + if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + return 0; + } + + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + + while (new_capacity < new_size) + new_capacity *= 2; + + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return 0; + } + + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + status = MZ_FALSE; + } + } + + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) +{ + mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; + + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + { + if (!pZip->m_pRead) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (pZip->m_file_offset_alignment) + { + /* Ensure user specified file offset alignment is a power of 2. */ + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + + pZip->m_pState->m_zip64 = zip64; + pZip->m_pState->m_zip64_has_extended_info_fields = zip64; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + return mz_zip_writer_init_v2(pZip, existing_size, 0); +} + +mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_mem_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; + + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + return 0; + } + + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); +} + +mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) +{ + MZ_FILE *pFile; + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + + pZip->m_pState->m_pFile = pFile; + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; + char buf[4096]; + + MZ_CLEAR_OBJ(buf); + + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, 0, flags)) + return MZ_FALSE; + + pZip->m_pState->m_pFile = pFile; + pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_zip_internal_state *pState; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) + { + /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ + if (!pZip->m_pState->m_zip64) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* No sense in trying to write to an archive that's already at the support max size */ + if (pZip->m_pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + (void)pFilename; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); +#else + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (!pFilename) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + } + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; +#endif /* #ifdef MINIZ_NO_STDIO */ + } + else if (pState->m_pMem) + { + /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + } + /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ + else if (!pZip->m_pWrite) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Start writing new files at the archive's current central directory location. */ + /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_central_directory_file_ofs = 0; + + /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ + /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ + /* TODO: We could easily maintain the sorted central directory offsets. */ + mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); +} + +/* TODO: pArchive_name is a terrible name here! */ +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) +#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) +static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) +{ + mz_uint8 *pDst = pBuf; + mz_uint32 field_size = 0; + + MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + MZ_WRITE_LE16(pDst + 2, 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + MZ_WRITE_LE64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pComp_size) + { + MZ_WRITE_LE64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + MZ_WRITE_LE64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + MZ_WRITE_LE16(pBuf + 2, field_size); + + return (mz_uint32)(pDst - pBuf); +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, + mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes, + const char *user_extra_data, mz_uint user_extra_data_len) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + if (!pZip->m_pState->m_zip64) + { + if (local_header_ofs > 0xFFFFFFFF) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + /* Try to resize the central directory array back into its original state. */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ + if (*pArchive_name == '/') + return MZ_FALSE; + + /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/ + + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); +} + +mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_uint16 bit_flags = 0; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + +#ifndef MINIZ_NO_TIME + if (last_modified != NULL) + { + mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); + } + else + { + MZ_TIME_T cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif /* #ifndef MINIZ_NO_TIME */ + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + /* Set DOS Subdirectory attribute bit. */ + ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; + + /* Subdirectories cannot contain data. */ + if ((buf_size) || (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + cur_archive_file_ofs += num_alignment_padding_bytes; + + MZ_CLEAR_OBJ(local_dir_header); + + if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + method = MZ_DEFLATED; + } + + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + + if (pExtra_data != NULL) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + if (uncomp_size) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 gen_flags = MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = size_to_add, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_zip_internal_state *pState; + mz_uint64 file_ofs = 0; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if ((!pState->m_zip64) && (uncomp_size > MZ_UINT32_MAX)) + { + /* Source file is too large for non-zip64 */ + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + pState->m_zip64 = MZ_TRUE; + } + + /* We could support this, but why? */ + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + +#ifndef MINIZ_NO_TIME + if (pFile_time) + { + mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); + } +#endif + + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_archive_file_ofs; + + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + if (uncomp_size && level) + { + method = MZ_DEFLATED; + } + + MZ_CLEAR_OBJ(local_dir_header); + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((read_callback(callback_opaque, file_ofs, pRead_buf, n) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + } + + for (;;) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + tdefl_flush flush = TDEFL_NO_FLUSH; + + if (read_callback(callback_opaque, file_ofs, pRead_buf, in_buf_size)!= in_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + break; + } + + file_ofs += in_buf_size; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) + flush = TDEFL_FULL_FLUSH; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? flush : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + { + mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + break; + } + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, + uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO + +static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pSrc_file); + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pSrc_file); +} + +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, size_to_add, pFile_time, pComment, comment_size, level_and_flags, + user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); +} + +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + MZ_FILE *pSrc_file = NULL; + mz_uint64 uncomp_size = 0; + MZ_TIME_T file_modified_time; + MZ_TIME_T *pFile_time = NULL; + mz_bool status; + + memset(&file_modified_time, 0, sizeof(file_modified_time)); + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + pFile_time = &file_modified_time; + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); +#endif + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); + + MZ_FCLOSE(pSrc_file); + + return status; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) +{ + /* + 64 should be enough for any new zip64 data */ + if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); + + if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) + { + mz_uint8 new_ext_block[64]; + mz_uint8 *pDst = new_ext_block; + mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + mz_write_le16(pDst + sizeof(mz_uint16), 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + mz_write_le64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + } + + if (pComp_size) + { + mz_write_le64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + mz_write_le64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + } + + if (pDisk_start) + { + mz_write_le32(pDst, *pDisk_start); + pDst += sizeof(mz_uint32); + } + + mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); + + if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if ((pExt) && (ext_len)) + { + mz_uint32 extra_size_remaining = ext_len; + const mz_uint8 *pExtra_data = pExt; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + return MZ_TRUE; +} + +/* TODO: This func is now pretty freakin complex due to zip64, split it up? */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; + mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + mz_zip_archive_file_stat src_file_stat; + mz_uint32 src_filename_len, src_comment_len, src_ext_len; + mz_uint32 local_header_filename_size, local_header_extra_len; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ + if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Get pointer to the source central dir header and crack it */ + if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); + src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); + src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; + + /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ + if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + if (!pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) + return MZ_FALSE; + + cur_src_file_ofs = src_file_stat.m_local_header_ofs; + cur_dst_file_ofs = pZip->m_archive_size; + + /* Read the source archive's local dir header */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Compute the total size we need to copy (filename+extra data+compressed data) */ + local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; + + /* Try to find a zip64 extended information field */ + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_zip_array file_data_array; + const mz_uint8 *pExtra_data; + mz_uint32 extra_size_remaining = local_header_extra_len; + + mz_zip_array_init(&file_data_array, 1); + if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + + mz_zip_array_clear(pZip, &file_data_array); + } + + if (!pState->m_zip64) + { + /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ + /* We also check when the archive is finalized so this doesn't need to be perfect. */ + mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + + pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; + + if (approx_new_archive_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + /* Write dest archive padding */ + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + + cur_dst_file_ofs += num_alignment_padding_bytes; + + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + while (src_archive_bytes_remaining) + { + n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_dst_file_ofs += n; + + src_archive_bytes_remaining -= n; + } + + /* Now deal with the optional data descriptor */ + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + /* Copy data descriptor */ + if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + /* src is zip64, dest must be zip64 */ + + /* name uint32_t's */ + /* id 1 (optional in zip64?) */ + /* crc 1 */ + /* comp_size 2 */ + /* uncomp_size 2 */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); + } + else + { + /* src is NOT zip64 */ + mz_bool has_id; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + + if (pZip->m_pState->m_zip64) + { + /* dest is zip64, so upgrade the data descriptor */ + const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0)); + const mz_uint32 src_crc32 = pSrc_descriptor[0]; + const mz_uint64 src_comp_size = pSrc_descriptor[1]; + const mz_uint64 src_uncomp_size = pSrc_descriptor[2]; + + mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); + mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); + + n = sizeof(mz_uint32) * 6; + } + else + { + /* dest is NOT zip64, just copy it as-is */ + n = sizeof(mz_uint32) * (has_id ? 4 : 3); + } + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + /* Finally, add the new central dir header */ + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + + if (pState->m_zip64) + { + /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ + const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; + mz_zip_array new_ext_block; + + mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); + + if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return MZ_FALSE; + } + + MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + mz_zip_array_clear(pZip, &new_ext_block); + } + else + { + /* sanity checks */ + if (cur_dst_file_ofs > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (local_dir_header_ofs >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + } + + /* This shouldn't trigger unless we screwed up during the initial sanity checks */ + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + { + /* TODO: Support central dirs >= 32-bits in size */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + } + + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[256]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + /* Write central directory */ + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += central_dir_size; + } + + if (pState->m_zip64) + { + /* Write zip64 end of central directory header */ + mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; + + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; + + /* Write zip64 end of central directory locator */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; + } + + /* Write end of central directory record */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) +{ + if ((!ppBuf) || (!pSize)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + *ppBuf = NULL; + *pSize = 0; + + if ((!pZip) || (!pZip->m_pState)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_pWrite != mz_zip_heap_write_func) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *ppBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + return mz_zip_writer_end_internal(pZip, MZ_TRUE); +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); +} + +mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + mz_zip_zero_struct(&zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_FILENAME; + return MZ_FALSE; + } + + /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ + /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + /* Create a new archive. */ + if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + created_new_archive = MZ_TRUE; + } + else + { + /* Append to an existing archive. */ + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); + + return MZ_FALSE; + } + } + + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + actual_err = zip_archive.m_last_error; + + /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ + if (!mz_zip_writer_finalize_archive(&zip_archive)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if (!mz_zip_writer_end_internal(&zip_archive, status)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if ((!status) && (created_new_archive)) + { + /* It's a new archive and something went wrong, so just delete it. */ + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + + if (pErr) + *pErr = actual_err; + + return status; +} + +void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) +{ + mz_uint32 file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + + return NULL; + } + + mz_zip_zero_struct(&zip_archive); + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + return NULL; + } + + if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) + { + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + } + + mz_zip_reader_end_internal(&zip_archive, p != NULL); + + if (pErr) + *pErr = zip_archive.m_last_error; + + return p; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* ------------------- Misc utils */ + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; +} + +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; +} + +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = err_num; + return prev_err; +} + +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + return pZip->m_last_error; +} + +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) +{ + return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); +} + +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = MZ_ZIP_NO_ERROR; + return prev_err; +} + +const char *mz_zip_get_error_string(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return "undefined error"; + case MZ_ZIP_TOO_MANY_FILES: + return "too many files"; + case MZ_ZIP_FILE_TOO_LARGE: + return "file too large"; + case MZ_ZIP_UNSUPPORTED_METHOD: + return "unsupported method"; + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return "unsupported encryption"; + case MZ_ZIP_UNSUPPORTED_FEATURE: + return "unsupported feature"; + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return "failed finding central directory"; + case MZ_ZIP_NOT_AN_ARCHIVE: + return "not a ZIP archive"; + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return "invalid header or archive is corrupted"; + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return "unsupported multidisk archive"; + case MZ_ZIP_DECOMPRESSION_FAILED: + return "decompression failed or archive is corrupted"; + case MZ_ZIP_COMPRESSION_FAILED: + return "compression failed"; + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return "unexpected decompressed size"; + case MZ_ZIP_CRC_CHECK_FAILED: + return "CRC-32 check failed"; + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return "unsupported central directory size"; + case MZ_ZIP_ALLOC_FAILED: + return "allocation failed"; + case MZ_ZIP_FILE_OPEN_FAILED: + return "file open failed"; + case MZ_ZIP_FILE_CREATE_FAILED: + return "file create failed"; + case MZ_ZIP_FILE_WRITE_FAILED: + return "file write failed"; + case MZ_ZIP_FILE_READ_FAILED: + return "file read failed"; + case MZ_ZIP_FILE_CLOSE_FAILED: + return "file close failed"; + case MZ_ZIP_FILE_SEEK_FAILED: + return "file seek failed"; + case MZ_ZIP_FILE_STAT_FAILED: + return "file stat failed"; + case MZ_ZIP_INVALID_PARAMETER: + return "invalid parameter"; + case MZ_ZIP_INVALID_FILENAME: + return "invalid filename"; + case MZ_ZIP_BUF_TOO_SMALL: + return "buffer too small"; + case MZ_ZIP_INTERNAL_ERROR: + return "internal error"; + case MZ_ZIP_FILE_NOT_FOUND: + return "file not found"; + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return "archive is too large"; + case MZ_ZIP_VALIDATION_FAILED: + return "validation failed"; + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return "write calledback failed"; + default: + break; + } + + return "unknown error"; +} + +/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return MZ_FALSE; + + return pZip->m_pState->m_zip64; +} + +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + + return pZip->m_pState->m_central_dir.m_size; +} + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) +{ + if (!pZip) + return 0; + return pZip->m_archive_size; +} + +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_file_archive_start_ofs; +} + +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_pFile; +} + +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + if (filename_buf_size) + pFilename[0] = '\0'; + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); +} + +mz_bool mz_zip_end(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_FALSE; + + if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) + return mz_zip_reader_end(pZip); +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) + return mz_zip_writer_end(pZip); +#endif + + return MZ_FALSE; +} + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ diff --git a/3rdparty/miniz/miniz_zip.h b/3rdparty/miniz/miniz_zip.h new file mode 100644 index 0000000..9314361 --- /dev/null +++ b/3rdparty/miniz/miniz_zip.h @@ -0,0 +1,436 @@ + +#pragma once +#include "miniz.h" + +/* ------------------- ZIP archive reading/writing */ + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 +}; + +typedef struct +{ + /* Central directory file index. */ + mz_uint32 m_file_index; + + /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ + mz_uint64 m_central_dir_ofs; + + /* These fields are copied directly from the zip's central dir. */ + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; + +#ifndef MINIZ_NO_TIME + MZ_TIME_T m_time; +#endif + + /* CRC-32 of uncompressed data. */ + mz_uint32 m_crc32; + + /* File's compressed size. */ + mz_uint64 m_comp_size; + + /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ + mz_uint64 m_uncomp_size; + + /* Zip internal and external file attributes. */ + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + + /* Entry's local header file offset in bytes. */ + mz_uint64 m_local_header_ofs; + + /* Size of comment in bytes. */ + mz_uint32 m_comment_size; + + /* MZ_TRUE if the entry appears to be a directory. */ + mz_bool m_is_directory; + + /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ + mz_bool m_is_encrypted; + + /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ + mz_bool m_is_supported; + + /* Filename. If string ends in '/' it's a subdirectory entry. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + + /* Comment field. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; + +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, + MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ + MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ + MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ + MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, + MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000 +} mz_zip_flags; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +typedef struct +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef struct +{ + mz_zip_archive *pZip; + mz_uint flags; + + int status; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint file_crc32; +#endif + mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + void *pWrite_buf; + + size_t out_blk_remain; + + tinfl_decompressor inflator; + +} mz_zip_reader_extract_iter_state; + +/* -------- ZIP reading */ + +/* Inits a ZIP archive reader. */ +/* These functions read and validate the archive's central directory. */ +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +/* Read a archive from a disk file. */ +/* file_start_ofs is the file offset where the archive actually begins, or 0. */ +/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); + +/* Read an archive from an already opened FILE, beginning at the current file position. */ +/* The archive is assumed to be archive_size bytes long. If archive_size is < 0, then the entire rest of the file is assumed to contain the archive. */ +/* The FILE will NOT be closed when mz_zip_reader_end() is called. */ +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); +#endif + +/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +/* -------- ZIP reading or writing */ + +/* Clears a mz_zip_archive struct to all zeros. */ +/* Important: This must be done before passing the struct to any mz_zip functions. */ +void mz_zip_zero_struct(mz_zip_archive *pZip); + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); + +/* Returns the total number of files in the archive. */ +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); + +/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); + +/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ +/* Note that the m_last_error functionality is not thread safe. */ +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); +const char *mz_zip_get_error_string(mz_zip_error mz_err); + +/* MZ_TRUE if the archive file entry is a directory entry. */ +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the file is encrypted/strong encrypted. */ +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); + +/* Retrieves the filename of an archive file entry. */ +/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +/* Attempts to locates a file in the archive's central directory. */ +/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ +/* Returns -1 if the file cannot be found. */ +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); + +/* Returns detailed information about an archive file entry. */ +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +/* MZ_TRUE if the file is in zip64 format. */ +/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); + +/* Returns the total central directory size in bytes. */ +/* The current max supported size is <= MZ_UINT32_MAX. */ +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); + +/* Extracts a archive file to a memory buffer using no memory allocation. */ +/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +/* Extracts a archive file to a memory buffer. */ +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +/* Extracts a archive file to a dynamically allocated heap buffer. */ +/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ +/* Returns NULL and sets the last error on failure. */ +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +/* Extracts a archive file using a callback function to output the file's data. */ +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +/* Extract a file iteratively */ +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); + +#ifndef MINIZ_NO_STDIO +/* Extracts a archive file to a disk file and sets its last accessed and modified times. */ +/* This function only extracts files, not archive directory records. */ +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); + +/* Extracts a archive file starting at the current position in the destination FILE stream. */ +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); +#endif + +#if 0 +/* TODO */ + typedef void *mz_zip_streaming_extract_state_ptr; + mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs); + size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); + mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); +#endif + +/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ +/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + +/* Validates an entire archive by calling mz_zip_validate_file() on each file. */ +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); + +/* Misc utils/helpers, valid for ZIP reading or writing */ +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); + +/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ +mz_bool mz_zip_end(mz_zip_archive *pZip); + +/* -------- ZIP writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +/* Inits a ZIP archive writer. */ +/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ +/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); +mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); +#endif + +/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ +/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ +/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ +/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ +/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ +/* the archive is finalized the file's central directory will be hosed. */ +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); + +/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ +/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ +/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + +/* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ +/* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ +mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 size_to_add, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + +#ifndef MINIZ_NO_STDIO +/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 size_to_add, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); +#endif + +/* Adds a file to an archive by fully cloning the data from another archive. */ +/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); + +/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ +/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ +/* An archive must be manually finalized by calling this function for it to be valid. */ +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); + +/* Finalizes a heap archive, returning a poiner to the heap block and its size. */ +/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); + +/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ +/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +/* -------- Misc. high-level helper functions: */ + +/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ +/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); + +/* Reads a single file from an archive into a heap block. */ +/* If pComment is not NULL, only the file with the specified comment will be extracted. */ +/* Returns NULL on failure. */ +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); +void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifdef __cplusplus +} +#endif + +#endif /* MINIZ_NO_ARCHIVE_APIS */ diff --git a/3rdparty/miniz/readme.md b/3rdparty/miniz/readme.md new file mode 100644 index 0000000..74b7eb3 --- /dev/null +++ b/3rdparty/miniz/readme.md @@ -0,0 +1,37 @@ +## Miniz + +Miniz is a lossless, high performance data compression library in a single source file that implements the zlib (RFC 1950) and Deflate (RFC 1951) compressed data format specification standards. It supports the most commonly used functions exported by the zlib library, but is a completely independent implementation so zlib's licensing requirements do not apply. Miniz also contains simple to use functions for writing .PNG format image files and reading/writing/appending .ZIP format archives. Miniz's compression speed has been tuned to be comparable to zlib's, and it also has a specialized real-time compressor function designed to compare well against fastlz/minilzo. + +## Usage + +Please use the files from the [releases page](https://github.com/richgel999/miniz/releases) in your projects. Do not use the git checkout directly! The different source and header files are [amalgamated](https://www.sqlite.org/amalgamation.html) into one `miniz.c`/`miniz.h` pair in a build step (`amalgamate.sh`). Include `miniz.c` and `miniz.h` in your project to use Miniz. + +## Features + +* MIT licensed +* A portable, single source and header file library written in plain C. Tested with GCC, clang and Visual Studio. +* Easily tuned and trimmed down by defines +* A drop-in replacement for zlib's most used API's (tested in several open source projects that use zlib, such as libpng and libzip). +* Fills a single threaded performance vs. compression ratio gap between several popular real-time compressors and zlib. For example, at level 1, miniz.c compresses around 5-9% better than minilzo, but is approx. 35% slower. At levels 2-9, miniz.c is designed to compare favorably against zlib's ratio and speed. See the miniz performance comparison page for example timings. +* Not a block based compressor: miniz.c fully supports stream based processing using a coroutine-style implementation. The zlib-style API functions can be called a single byte at a time if that's all you've got. +* Easy to use. The low-level compressor (tdefl) and decompressor (tinfl) have simple state structs which can be saved/restored as needed with simple memcpy's. The low-level codec API's don't use the heap in any way. +* Entire inflater (including optional zlib header parsing and Adler-32 checking) is implemented in a single function as a coroutine, which is separately available in a small (~550 line) source file: miniz_tinfl.c +* A fairly complete (but totally optional) set of .ZIP archive manipulation and extraction API's. The archive functionality is intended to solve common problems encountered in embedded, mobile, or game development situations. (The archive API's are purposely just powerful enough to write an entire archiver given a bit of additional higher-level logic.) + +## Known Problems + +* No support for encrypted archives. Not sure how useful this stuff is in practice. +* Minimal documentation. The assumption is that the user is already familiar with the basic zlib API. I need to write an API wiki - for now I've tried to place key comments before each enum/API, and I've included 6 examples that demonstrate how to use the module's major features. + +## Special Thanks + +Thanks to Alex Evans for the PNG writer function. Also, thanks to Paul Holden and Thorsten Scheuermann for feedback and testing, Matt Pritchard for all his encouragement, and Sean Barrett's various public domain libraries for inspiration (and encouraging me to write miniz.c in C, which was much more enjoyable and less painful than I thought it would be considering I've been programming in C++ for so long). + +Thanks to Bruce Dawson for reporting a problem with the level_and_flags archive API parameter (which is fixed in v1.12) and general feedback, and Janez Zemva for indirectly encouraging me into writing more examples. + +## Patents + +I was recently asked if miniz avoids patent issues. miniz purposely uses the same core algorithms as the ones used by zlib. The compressor uses vanilla hash chaining as described [here](http://www.gzip.org/zlib/rfc-deflate.html#algorithm). Also see the [gzip FAQ](http://www.gzip.org/#faq11). In my opinion, if miniz falls prey to a patent attack then zlib/gzip are likely to be at serious risk too. + + +[![Build Status](https://travis-ci.org/uroni/miniz.svg?branch=master)](https://travis-ci.org/uroni/miniz) \ No newline at end of file diff --git a/3rdparty/miniz/test.sh b/3rdparty/miniz/test.sh new file mode 100644 index 0000000..25d651a --- /dev/null +++ b/3rdparty/miniz/test.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +. amalgamate.sh + +g++ tests/miniz_tester.cpp tests/timer.cpp amalgamation/miniz.c -o miniz_tester -I. -ggdb -O2 + +for i in 1 2 3 4 5 6 +do + gcc examples/example$i.c amalgamation/miniz.c -o example$i -lm -I. -ggdb +done + +mkdir -p test_scratch +if ! test -e "test_scratch/linux-4.8.11" +then + cd test_scratch + wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.8.11.tar.xz -O linux-4.8.11.tar.xz + tar xf linux-4.8.11.tar.xz + cd .. +fi + +cd test_scratch +../miniz_tester -v a linux-4.8.11 +../miniz_tester -v -r a linux-4.8.11 +../miniz_tester -v -b -r a linux-4.8.11 +../miniz_tester -v -a a linux-4.8.11 + +mkdir -p large_file +truncate -s 5G large_file/lf +../miniz_tester -v -a a large_file diff --git a/3rdparty/miniz/tests/miniz_tester.cpp b/3rdparty/miniz/tests/miniz_tester.cpp new file mode 100644 index 0000000..fb9cbd1 --- /dev/null +++ b/3rdparty/miniz/tests/miniz_tester.cpp @@ -0,0 +1,1881 @@ +// miniz_tester.cpp +// Note: This module is not intended to make a good example, or be used for anything other than testing. +// It's something quick I put together last year to help regression test miniz/tinfl under Linux/Win32/Mac. It's derived from LZHAM's test module. +#ifdef _MSC_VER +#pragma warning (disable:4127) // warning C4127: conditional expression is constant +#endif + +#if defined(__GNUC__) + // Ensure we get the 64-bit variants of the CRT's file I/O calls + #ifndef _FILE_OFFSET_BITS + #define _FILE_OFFSET_BITS 64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE 1 + #endif +#endif + +#include "miniz.h" +#include "miniz_zip.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "timer.h" + +#define my_max(a,b) (((a) > (b)) ? (a) : (b)) +#define my_min(a,b) (((a) < (b)) ? (a) : (b)) + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint; + +#define TDEFL_PRINT_OUTPUT_PROGRESS + +#if defined(WIN32) + #define WIN32_LEAN_AND_MEAN + #include + #define FILE_STAT_STRUCT _stat + #define FILE_STAT _stat +#else + #include + #define Sleep(ms) usleep(ms*1000) + #define _aligned_malloc(size, alignment) memalign(alignment, size) + #define _aligned_free free + #define fopen fopen64 + #define _fseeki64 fseeko64 + #define _ftelli64 ftello64 + #define _stricmp strcasecmp + #define FILE_STAT_STRUCT stat64 + #define FILE_STAT stat64 +#endif + +#ifdef WIN32 +#define QUAD_INT_FMT "%I64u" +#else +#define QUAD_INT_FMT "%llu" +#endif + +#ifdef _DEBUG +const bool g_is_debug = true; +#else +const bool g_is_debug = false; +#endif + +typedef unsigned char uint8; +typedef unsigned int uint; +typedef unsigned int uint32; +typedef unsigned long long uint64; +typedef long long int64; + +#define TDEFLTEST_COMP_INPUT_BUFFER_SIZE 1024*1024*2 +#define TDEFLTEST_COMP_OUTPUT_BUFFER_SIZE 1024*1024*2 +#define TDEFLTEST_DECOMP_INPUT_BUFFER_SIZE 1024*1024*2 + +static float s_max_small_comp_ratio, s_max_large_comp_ratio; + +struct comp_options +{ + comp_options() : + m_level(7), + m_unbuffered_decompression(false), + m_verify_compressed_data(false), + m_randomize_params(false), + m_randomize_buffer_sizes(false), + m_z_strat(Z_DEFAULT_STRATEGY), + m_random_z_flushing(false), + m_write_zlib_header(true), + m_archive_test(false), + m_write_archives(false) + { + } + + void print() + { + printf("Level: %u\n", m_level); + printf("Write zlib header: %u\n", (uint)m_write_zlib_header); + printf("Unbuffered decompression: %u\n", (uint)m_unbuffered_decompression); + printf("Verify compressed data: %u\n", (uint)m_verify_compressed_data); + printf("Randomize parameters: %u\n", m_randomize_params); + printf("Randomize buffer sizes: %u\n", m_randomize_buffer_sizes); + printf("Deflate strategy: %u\n", m_z_strat); + printf("Random Z stream flushing: %u\n", m_random_z_flushing); + printf("Archive test: %u\n", m_archive_test); + printf("Write archives: %u\n", m_write_archives); + } + + uint m_level; + bool m_unbuffered_decompression; + bool m_verify_compressed_data; + bool m_randomize_params; + bool m_randomize_buffer_sizes; + uint m_z_strat; + bool m_random_z_flushing; + bool m_write_zlib_header; + bool m_archive_test; + bool m_write_archives; +}; + +#define RND_SHR3(x) (x ^= (x << 17), x ^= (x >> 13), x ^= (x << 5)) + +#if 0 +static void random_fill(uint8 *pDst, size_t len, uint32 x) +{ + x ^= (x << 16); + if (!x) x++; + + while (len) + { + RND_SHR3(x); uint64 l0 = x & 0xFFF; + RND_SHR3(x); uint64 l1 = x & 0xFFF; + RND_SHR3(x); uint64 l2 = x & 0xFFF; + RND_SHR3(x); uint c = x; + + uint l = (uint)(((l0*l1*l2)/(16769025ULL) * 32) / 4095); + l = (uint)my_max(1,my_min(l, len)); + len -= l; + + while (l--) + { + *pDst++ = (uint8)c; + } + + if (((int)x < 0) && len) + { + *pDst++ = 0; + len--; + } + } +} +#endif + +static void print_usage() +{ + printf("Usage: [options] [mode] inpath/infile [outfile]\n"); + printf("\n"); + printf("Modes:\n"); + printf("c - Compress \"infile\" to \"outfile\"\n"); + printf("d - Decompress \"infile\" to \"outfile\"\n"); + printf("a - Recursively compress all files under \"inpath\"\n"); + printf("r - Archive decompression test\n"); + printf("\n"); + printf("Options:\n"); + printf("-m[0-10] - Compression level: 0=fastest (Huffman only), 9=best (10=uber)\n"); + printf("-u - Use unbuffered decompression on files that can fit into memory.\n"); + printf(" Unbuffered decompression is faster, but may have more I/O overhead.\n"); + printf("-v - Immediately decompress compressed file after compression for verification.\n"); + printf("-z - Do not write zlib header\n"); + printf("-r - Randomize parameters during recursive testing\n"); + printf("-b - Randomize input/output buffer sizes\n"); + printf("-h - Use random z_flushing\n"); + printf("-x# - Set rand() seed to value\n"); + printf("-t# - Set z_strategy to value [0-4]\n"); + printf("-a - Create single-file archives instead of files during testing\n"); + printf("-w - Test archive cloning\n"); +} + +static void print_error(const char *pMsg, ...) +{ + char buf[1024]; + + va_list args; + va_start(args, pMsg); + vsnprintf(buf, sizeof(buf), pMsg, args); + va_end(args); + + buf[sizeof(buf) - 1] = '\0'; + + fprintf(stderr, "Error: %s", buf); +} + +static FILE* open_file_with_retries(const char *pFilename, const char* pMode) +{ + const uint cNumRetries = 8; + for (uint i = 0; i < cNumRetries; i++) + { + FILE* pFile = fopen(pFilename, pMode); + if (pFile) + return pFile; + Sleep(250); + } + return NULL; +} + +static bool ensure_file_exists_and_is_readable(const char *pFilename) +{ + FILE *p = fopen(pFilename, "rb"); + if (!p) + return false; + + _fseeki64(p, 0, SEEK_END); + uint64 src_file_size = _ftelli64(p); + _fseeki64(p, 0, SEEK_SET); + + if (src_file_size) + { + char buf[1]; + if (fread(buf, 1, 1, p) != 1) + { + fclose(p); + return false; + } + } + fclose(p); + return true; +} + +static bool ensure_file_is_writable(const char *pFilename) +{ + const int cNumRetries = 8; + for (int i = 0; i < cNumRetries; i++) + { + FILE *pFile = fopen(pFilename, "wb"); + if (pFile) + { + fclose(pFile); + return true; + } + Sleep(250); + } + return false; +} + +static int simple_test1(const comp_options &options) +{ + (void)options; + + size_t cmp_len = 0; + + const char *p = "This is a test.This is a test.This is a test.1234567This is a test.This is a test.123456"; + size_t uncomp_len = strlen(p); + + void *pComp_data = tdefl_compress_mem_to_heap(p, uncomp_len, &cmp_len, TDEFL_WRITE_ZLIB_HEADER); + if (!pComp_data) + { + free(pComp_data); + print_error("Compression test failed!\n"); + return EXIT_FAILURE; + } + + printf("Uncompressed size: %u\nCompressed size: %u\n", (uint)uncomp_len, (uint)cmp_len); + + size_t decomp_len = 0; + void *pDecomp_data = tinfl_decompress_mem_to_heap(pComp_data, cmp_len, &decomp_len, TINFL_FLAG_PARSE_ZLIB_HEADER); + + if ((!pDecomp_data) || (decomp_len != uncomp_len) || (memcmp(pDecomp_data, p, uncomp_len))) + { + free(pComp_data); + free(pDecomp_data); + print_error("Compression test failed!\n"); + return EXIT_FAILURE; + } + + printf("Low-level API compression test succeeded.\n"); + + free(pComp_data); + free(pDecomp_data); + + return EXIT_SUCCESS; +} + +static int simple_test2(const comp_options &options) +{ + (void)options; + + uint8 cmp_buf[1024], decomp_buf[1024]; + uLong cmp_len = sizeof(cmp_buf); + + const char *p = "This is a test.This is a test.This is a test.1234567This is a test.This is a test.123456"; + uLong uncomp_len = (uLong)strlen(p); + + int status = compress(cmp_buf, &cmp_len, (const uint8*)p, uncomp_len); + if (status != Z_OK) + { + print_error("Compression test failed!\n"); + return EXIT_FAILURE; + } + + printf("Uncompressed size: %u\nCompressed size: %u\n", (uint)uncomp_len, (uint)cmp_len); + + if (cmp_len > compressBound(uncomp_len)) + { + print_error("compressBound() returned bogus result\n"); + return EXIT_FAILURE; + } + + uLong decomp_len = sizeof(decomp_buf); + status = uncompress(decomp_buf, &decomp_len, cmp_buf, cmp_len);; + + if ((status != Z_OK) || (decomp_len != uncomp_len) || (memcmp(decomp_buf, p, uncomp_len))) + { + print_error("Compression test failed!\n"); + return EXIT_FAILURE; + } + + printf("zlib API compression test succeeded.\n"); + + return EXIT_SUCCESS; +} + +static bool compress_file_zlib(const char* pSrc_filename, const char *pDst_filename, const comp_options &options) +{ + printf("Testing: Streaming zlib compression\n"); + + FILE *pInFile = fopen(pSrc_filename, "rb"); + if (!pInFile) + { + print_error("Unable to read file: %s\n", pSrc_filename); + return false; + } + + FILE *pOutFile = fopen(pDst_filename, "wb"); + if (!pOutFile) + { + print_error("Unable to create file: %s\n", pDst_filename); + return false; + } + + _fseeki64(pInFile, 0, SEEK_END); + uint64 src_file_size = _ftelli64(pInFile); + _fseeki64(pInFile, 0, SEEK_SET); + + fputc('D', pOutFile); fputc('E', pOutFile); fputc('F', pOutFile); fputc('0', pOutFile); + fputc(options.m_write_zlib_header, pOutFile); + + for (uint i = 0; i < 8; i++) + fputc(static_cast((src_file_size >> (i * 8)) & 0xFF), pOutFile); + + uint cInBufSize = TDEFLTEST_COMP_INPUT_BUFFER_SIZE; + uint cOutBufSize = TDEFLTEST_COMP_OUTPUT_BUFFER_SIZE; + if (options.m_randomize_buffer_sizes) + { + cInBufSize = 1 + (rand() % 4096); + cOutBufSize = 1 + (rand() % 4096); + } + printf("Input buffer size: %u, Output buffer size: %u\n", cInBufSize, cOutBufSize); + + uint8 *in_file_buf = static_cast(_aligned_malloc(cInBufSize, 16)); + uint8 *out_file_buf = static_cast(_aligned_malloc(cOutBufSize, 16)); + if ((!in_file_buf) || (!out_file_buf)) + { + print_error("Out of memory!\n"); + _aligned_free(in_file_buf); + _aligned_free(out_file_buf); + fclose(pInFile); + fclose(pOutFile); + return false; + } + + uint64 src_bytes_left = src_file_size; + + uint in_file_buf_size = 0; + uint in_file_buf_ofs = 0; + + uint64 total_output_bytes = 0; + + timer_ticks start_time = timer::get_ticks(); + + z_stream zstream; + memset(&zstream, 0, sizeof(zstream)); + + timer_ticks init_start_time = timer::get_ticks(); + int status = deflateInit2(&zstream, options.m_level, Z_DEFLATED, options.m_write_zlib_header ? Z_DEFAULT_WINDOW_BITS : -Z_DEFAULT_WINDOW_BITS, 9, options.m_z_strat); + timer_ticks total_init_time = timer::get_ticks() - init_start_time; + + if (status != Z_OK) + { + print_error("Failed initializing compressor!\n"); + _aligned_free(in_file_buf); + _aligned_free(out_file_buf); + fclose(pInFile); + fclose(pOutFile); + return false; + } + + printf("deflateInit2() took %3.3fms\n", timer::ticks_to_secs(total_init_time)*1000.0f); + + uint32 x = my_max(1, (uint32)(src_file_size ^ (src_file_size >> 32))); + + for ( ; ; ) + { + if (src_file_size) + { + double total_elapsed_time = timer::ticks_to_secs(timer::get_ticks() - start_time); + double total_bytes_processed = static_cast(src_file_size - src_bytes_left); + double comp_rate = (total_elapsed_time > 0.0f) ? total_bytes_processed / total_elapsed_time : 0.0f; + +#ifdef TDEFL_PRINT_OUTPUT_PROGRESS + for (int i = 0; i < 15; i++) + printf("\b\b\b\b"); + printf("Progress: %3.1f%%, Bytes Remaining: %3.1fMB, %3.3fMB/sec", (1.0f - (static_cast(src_bytes_left) / src_file_size)) * 100.0f, src_bytes_left / 1048576.0f, comp_rate / (1024.0f * 1024.0f)); + printf(" \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); +#endif + } + + if (in_file_buf_ofs == in_file_buf_size) + { + in_file_buf_size = static_cast(my_min(cInBufSize, src_bytes_left)); + + if (fread(in_file_buf, 1, in_file_buf_size, pInFile) != in_file_buf_size) + { + printf("\n"); + print_error("Failure reading from source file!\n"); + _aligned_free(in_file_buf); + _aligned_free(out_file_buf); + fclose(pInFile); + fclose(pOutFile); + deflateEnd(&zstream); + return false; + } + + src_bytes_left -= in_file_buf_size; + + in_file_buf_ofs = 0; + } + + zstream.next_in = &in_file_buf[in_file_buf_ofs]; + zstream.avail_in = in_file_buf_size - in_file_buf_ofs; + zstream.next_out = out_file_buf; + zstream.avail_out = cOutBufSize; + + int flush = !src_bytes_left ? Z_FINISH : Z_NO_FLUSH; + if ((flush == Z_NO_FLUSH) && (options.m_random_z_flushing)) + { + RND_SHR3(x); + if ((x & 15) == 0) + { + RND_SHR3(x); + flush = (x & 31) ? Z_SYNC_FLUSH : Z_FULL_FLUSH; + } + } + status = deflate(&zstream, flush); + + uint num_in_bytes = (in_file_buf_size - in_file_buf_ofs) - zstream.avail_in; + uint num_out_bytes = cOutBufSize - zstream.avail_out; + if (num_in_bytes) + { + in_file_buf_ofs += (uint)num_in_bytes; + assert(in_file_buf_ofs <= in_file_buf_size); + } + + if (num_out_bytes) + { + if (fwrite(out_file_buf, 1, static_cast(num_out_bytes), pOutFile) != num_out_bytes) + { + printf("\n"); + print_error("Failure writing to destination file!\n"); + _aligned_free(in_file_buf); + _aligned_free(out_file_buf); + fclose(pInFile); + fclose(pOutFile); + deflateEnd(&zstream); + return false; + } + + total_output_bytes += num_out_bytes; + } + + if (status != Z_OK) + break; + } + +#ifdef TDEFL_PRINT_OUTPUT_PROGRESS + for (int i = 0; i < 15; i++) + { + printf("\b\b\b\b \b\b\b\b"); + } +#endif + + src_bytes_left += (in_file_buf_size - in_file_buf_ofs); + + uint32 adler32 = zstream.adler; + deflateEnd(&zstream); + + timer_ticks end_time = timer::get_ticks(); + double total_time = timer::ticks_to_secs(my_max(1, end_time - start_time)); + + uint64 cmp_file_size = _ftelli64(pOutFile); + + _aligned_free(in_file_buf); + in_file_buf = NULL; + _aligned_free(out_file_buf); + out_file_buf = NULL; + + fclose(pInFile); + pInFile = NULL; + fclose(pOutFile); + pOutFile = NULL; + + if (status != Z_STREAM_END) + { + print_error("Compression failed with status %i\n", status); + return false; + } + + if (src_bytes_left) + { + print_error("Compressor failed to consume entire input file!\n"); + return false; + } + + printf("Success\n"); + printf("Input file size: " QUAD_INT_FMT ", Compressed file size: " QUAD_INT_FMT ", Ratio: %3.2f%%\n", src_file_size, cmp_file_size, src_file_size ? ((1.0f - (static_cast(cmp_file_size) / src_file_size)) * 100.0f) : 0.0f); + printf("Compression time: %3.6f\nConsumption rate: %9.1f bytes/sec, Emission rate: %9.1f bytes/sec\n", total_time, src_file_size / total_time, cmp_file_size / total_time); + printf("Input file adler32: 0x%08X\n", adler32); + if (src_file_size) + { + if (src_file_size >= 256) + s_max_large_comp_ratio = my_max(s_max_large_comp_ratio, cmp_file_size / (float)src_file_size); + else + s_max_small_comp_ratio = my_max(s_max_small_comp_ratio, cmp_file_size / (float)src_file_size); + } + //printf("Max small comp ratio: %f, Max large comp ratio: %f\n", s_max_small_comp_ratio, s_max_large_comp_ratio); + + return true; +} + +static bool decompress_file_zlib(const char* pSrc_filename, const char *pDst_filename, comp_options options) +{ + FILE *pInFile = fopen(pSrc_filename, "rb"); + if (!pInFile) + { + print_error("Unable to read file: %s\n", pSrc_filename); + return false; + } + + _fseeki64(pInFile, 0, SEEK_END); + uint64 src_file_size = _ftelli64(pInFile); + _fseeki64(pInFile, 0, SEEK_SET); + if (src_file_size < (5+9)) + { + print_error("Compressed file is too small!\n"); + fclose(pInFile); + return false; + } + + int h0 = fgetc(pInFile); + int h1 = fgetc(pInFile); + int h2 = fgetc(pInFile); + int h3 = fgetc(pInFile); + int zlib_header = fgetc(pInFile); + if ((h0 != 'D') | (h1 != 'E') || (h2 != 'F') || (h3 != '0')) + { + print_error("Unrecognized/invalid header in file: %s\n", pSrc_filename); + fclose(pInFile); + return false; + } + + FILE *pOutFile = fopen(pDst_filename, "wb"); + if (!pOutFile) + { + print_error("Unable to create file: %s\n", pDst_filename); + fclose(pInFile); + return false; + } + + uint64 orig_file_size = 0; + for (uint i = 0; i < 8; i++) + orig_file_size |= (static_cast(fgetc(pInFile)) << (i * 8)); + + int total_header_bytes = ftell(pInFile); + + // Avoid running out of memory on large files when using unbuffered decompression. + if ((options.m_unbuffered_decompression) && (orig_file_size > 768*1024*1024)) + { + printf("Output file is too large for unbuffered decompression - switching to streaming decompression.\n"); + options.m_unbuffered_decompression = false; + } + + if (options.m_unbuffered_decompression) + printf("Testing: Unbuffered decompression\n"); + else + printf("Testing: Streaming decompression\n"); + + uint cInBufSize = options.m_unbuffered_decompression ? static_cast(src_file_size) : TDEFLTEST_DECOMP_INPUT_BUFFER_SIZE; + uint out_buf_size = options.m_unbuffered_decompression ? static_cast(orig_file_size) : TINFL_LZ_DICT_SIZE; + + if ((options.m_randomize_buffer_sizes) && (!options.m_unbuffered_decompression)) + { + cInBufSize = 1 + (rand() % 4096); + } + + printf("Input buffer size: %u, Output buffer size: %u\n", cInBufSize, out_buf_size); + + uint8 *in_file_buf = static_cast(_aligned_malloc(cInBufSize, 16)); + uint8 *out_file_buf = static_cast(_aligned_malloc(out_buf_size, 16)); + if ((!in_file_buf) || (!out_file_buf)) + { + print_error("Failed allocating output buffer!\n"); + _aligned_free(in_file_buf); + fclose(pInFile); + fclose(pOutFile); + return false; + } + + uint64 src_bytes_left = src_file_size - total_header_bytes; + uint64 dst_bytes_left = orig_file_size; + + uint in_file_buf_size = 0; + uint in_file_buf_ofs = 0; + uint out_file_buf_ofs = 0; + + timer_ticks start_time = timer::get_ticks(); + double decomp_only_time = 0; + + z_stream zstream; + memset(&zstream, 0, sizeof(zstream)); + + timer_ticks init_start_time = timer::get_ticks(); + int status = zlib_header ? inflateInit(&zstream) : inflateInit2(&zstream, -Z_DEFAULT_WINDOW_BITS); + timer_ticks total_init_time = timer::get_ticks() - init_start_time; + if (status != Z_OK) + { + print_error("Failed initializing decompressor!\n"); + _aligned_free(in_file_buf); + _aligned_free(out_file_buf); + fclose(pInFile); + fclose(pOutFile); + return false; + } + + printf("inflateInit() took %3.3fms\n", timer::ticks_to_secs(total_init_time)*1000.0f); + + for ( ; ; ) + { + if (in_file_buf_ofs == in_file_buf_size) + { + in_file_buf_size = static_cast(my_min(cInBufSize, src_bytes_left)); + + if (fread(in_file_buf, 1, in_file_buf_size, pInFile) != in_file_buf_size) + { + print_error("Failure reading from source file!\n"); + _aligned_free(in_file_buf); + _aligned_free(out_file_buf); + deflateEnd(&zstream); + fclose(pInFile); + fclose(pOutFile); + return false; + } + + src_bytes_left -= in_file_buf_size; + + in_file_buf_ofs = 0; + } + + uint num_in_bytes = (in_file_buf_size - in_file_buf_ofs); + uint num_out_bytes = (out_buf_size - out_file_buf_ofs); + zstream.next_in = in_file_buf + in_file_buf_ofs; + zstream.avail_in = num_in_bytes; + zstream.next_out = out_file_buf + out_file_buf_ofs; + zstream.avail_out = num_out_bytes; + + { + timer decomp_only_timer; + decomp_only_timer.start(); + status = inflate(&zstream, options.m_unbuffered_decompression ? Z_FINISH : Z_SYNC_FLUSH); + decomp_only_time += decomp_only_timer.get_elapsed_secs(); + } + num_in_bytes -= zstream.avail_in; + num_out_bytes -= zstream.avail_out; + + if (num_in_bytes) + { + in_file_buf_ofs += (uint)num_in_bytes; + assert(in_file_buf_ofs <= in_file_buf_size); + } + + out_file_buf_ofs += (uint)num_out_bytes; + + if ((out_file_buf_ofs == out_buf_size) || (status == Z_STREAM_END)) + { + if (fwrite(out_file_buf, 1, static_cast(out_file_buf_ofs), pOutFile) != out_file_buf_ofs) + { + print_error("Failure writing to destination file!\n"); + _aligned_free(in_file_buf); + _aligned_free(out_file_buf); + inflateEnd(&zstream); + fclose(pInFile); + fclose(pOutFile); + return false; + } + out_file_buf_ofs = 0; + } + + if (num_out_bytes > dst_bytes_left) + { + print_error("Decompressor wrote too many bytes to destination file!\n"); + _aligned_free(in_file_buf); + _aligned_free(out_file_buf); + inflateEnd(&zstream); + fclose(pInFile); + fclose(pOutFile); + return false; + } + dst_bytes_left -= num_out_bytes; + + if (status != Z_OK) + break; + } + _aligned_free(in_file_buf); + in_file_buf = NULL; + + _aligned_free(out_file_buf); + out_file_buf = NULL; + + src_bytes_left += (in_file_buf_size - in_file_buf_ofs); + + uint32 adler32 = zstream.adler; + inflateEnd(&zstream); + + timer_ticks end_time = timer::get_ticks(); + double total_time = timer::ticks_to_secs(my_max(1, end_time - start_time)); + + fclose(pInFile); + pInFile = NULL; + + fclose(pOutFile); + pOutFile = NULL; + + if (status != Z_STREAM_END) + { + print_error("Decompression FAILED with status %i\n", status); + return false; + } + + if ((src_file_size < UINT_MAX) && (orig_file_size < UINT_MAX)) + { + if ((((size_t)zstream.total_in + total_header_bytes) != src_file_size) || (zstream.total_out != orig_file_size)) + { + print_error("Decompression FAILED to consume all input or write all expected output!\n"); + return false; + } + } + + if (dst_bytes_left) + { + print_error("Decompressor FAILED to output the entire output file!\n"); + return false; + } + + if (src_bytes_left) + { + print_error("Decompressor FAILED to read " QUAD_INT_FMT " bytes from input buffer\n", src_bytes_left); + } + + printf("Success\n"); + printf("Source file size: " QUAD_INT_FMT ", Decompressed file size: " QUAD_INT_FMT "\n", src_file_size, orig_file_size); + if (zlib_header) printf("Decompressed adler32: 0x%08X\n", adler32); + printf("Overall decompression time (decompression init+I/O+decompression): %3.6f\n Consumption rate: %9.1f bytes/sec, Decompression rate: %9.1f bytes/sec\n", total_time, src_file_size / total_time, orig_file_size / total_time); + printf("Decompression only time (not counting decompression init or I/O): %3.6f\n Consumption rate: %9.1f bytes/sec, Decompression rate: %9.1f bytes/sec\n", decomp_only_time, src_file_size / decomp_only_time, orig_file_size / decomp_only_time); + + return true; +} + +static bool compare_files(const char *pFilename1, const char* pFilename2) +{ + FILE* pFile1 = open_file_with_retries(pFilename1, "rb"); + if (!pFile1) + { + print_error("Failed opening file: %s\n", pFilename1); + return false; + } + + FILE* pFile2 = open_file_with_retries(pFilename2, "rb"); + if (!pFile2) + { + print_error("Failed opening file: %s\n", pFilename2); + fclose(pFile1); + return false; + } + + _fseeki64(pFile1, 0, SEEK_END); + int64 fileSize1 = _ftelli64(pFile1); + _fseeki64(pFile1, 0, SEEK_SET); + + _fseeki64(pFile2, 0, SEEK_END); + int64 fileSize2 = _ftelli64(pFile2); + _fseeki64(pFile2, 0, SEEK_SET); + + if (fileSize1 != fileSize2) + { + print_error("Files to compare are not the same size: %I64i vs. %I64i.\n", fileSize1, fileSize2); + fclose(pFile1); + fclose(pFile2); + return false; + } + + const uint cBufSize = 1024 * 1024; + std::vector buf1(cBufSize); + std::vector buf2(cBufSize); + + int64 bytes_remaining = fileSize1; + while (bytes_remaining) + { + const uint bytes_to_read = static_cast(my_min(cBufSize, bytes_remaining)); + + if (fread(&buf1.front(), bytes_to_read, 1, pFile1) != 1) + { + print_error("Failed reading from file: %s\n", pFilename1); + fclose(pFile1); + fclose(pFile2); + return false; + } + + if (fread(&buf2.front(), bytes_to_read, 1, pFile2) != 1) + { + print_error("Failed reading from file: %s\n", pFilename2); + fclose(pFile1); + fclose(pFile2); + return false; + } + + if (memcmp(&buf1.front(), &buf2.front(), bytes_to_read) != 0) + { + print_error("File data comparison failed!\n"); + fclose(pFile1); + fclose(pFile2); + return false; + } + + bytes_remaining -= bytes_to_read; + } + + fclose(pFile1); + fclose(pFile2); + return true; +} + +static bool zip_create(const char *pZip_filename, const char *pSrc_filename) +{ + mz_zip_archive zip; + memset(&zip, 0, sizeof(zip)); + if ((rand() % 100) >= 10) + zip.m_file_offset_alignment = 1 << (rand() & 15); + if (!mz_zip_writer_init_file(&zip, pZip_filename, 65537)) + { + print_error("Failed creating zip archive \"%s\" (1)!\n", pZip_filename); + return false; + } + + mz_bool success = MZ_TRUE; + + const char *pStr = "This is a test!This is a test!This is a test!\n"; + size_t comp_size; + void *pComp_data = tdefl_compress_mem_to_heap(pStr, strlen(pStr), &comp_size, 256); + success &= mz_zip_writer_add_mem_ex(&zip, "precomp.txt", pComp_data, comp_size, "Comment", (uint16)strlen("Comment"), MZ_ZIP_FLAG_COMPRESSED_DATA, strlen(pStr), mz_crc32(MZ_CRC32_INIT, (const uint8 *)pStr, strlen(pStr))); + + success &= mz_zip_writer_add_mem(&zip, "cool/", NULL, 0, 0); + + success &= mz_zip_writer_add_mem(&zip, "1.txt", pStr, strlen(pStr), 9); + int n = rand() & 4095; + for (int i = 0; i < n; i++) + { + char name[256], buf[256], comment[256]; + sprintf(name, "t%u.txt", i); + sprintf(buf, "%u\n", i*5377); + sprintf(comment, "comment: %u\n", i); + success &= mz_zip_writer_add_mem_ex(&zip, name, buf, strlen(buf), comment, (uint16)strlen(comment), i % 10, 0, 0); + } + + const char *pTestComment = "test comment"; + success &= mz_zip_writer_add_file(&zip, "test.bin", pSrc_filename, pTestComment, (uint16)strlen(pTestComment), 9); + + if (ensure_file_exists_and_is_readable("changelog.txt")) + success &= mz_zip_writer_add_file(&zip, "changelog.txt", "changelog.txt", "This is a comment", (uint16)strlen("This is a comment"), 9); + + if (!success) + { + mz_zip_writer_end(&zip); + remove(pZip_filename); + print_error("Failed creating zip archive \"%s\" (2)!\n", pZip_filename); + return false; + } + + if (!mz_zip_writer_finalize_archive(&zip)) + { + mz_zip_writer_end(&zip); + remove(pZip_filename); + print_error("Failed creating zip archive \"%s\" (3)!\n", pZip_filename); + return false; + } + + mz_zip_writer_end(&zip); + + struct FILE_STAT_STRUCT stat_buf; + FILE_STAT(pZip_filename, &stat_buf); + uint64 actual_file_size = stat_buf.st_size; + if (zip.m_archive_size != actual_file_size) + { + print_error("Archive's actual size and zip archive object's size differ for file \"%s\"!\n", pZip_filename); + return false; + } + + printf("Created zip file \"%s\", file size: " QUAD_INT_FMT "\n", pZip_filename, zip.m_archive_size); + return true; +} + +static size_t zip_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)pOpaque, (void)ofs, (void)pBuf, (void)n; + return n; +} + +static bool zip_extract(const char *pZip_filename, const char *pDst_filename) +{ + mz_zip_archive zip; + memset(&zip, 0, sizeof(zip)); + if (!mz_zip_reader_init_file(&zip, pZip_filename, 0)) + { + print_error("Failed opening zip archive \"%s\"!\n", pZip_filename); + return false; + } + + int file_index = mz_zip_reader_locate_file(&zip, "test.bin", "test Comment", 0); + int alt_file_index = mz_zip_reader_locate_file(&zip, "test.bin", "test Comment e", 0); + if ((file_index < 0) || (alt_file_index >= 0)) + { + print_error("Archive \"%s\" is missing test.bin file!\n", pZip_filename); + mz_zip_reader_end(&zip); + return false; + } + + alt_file_index = mz_zip_reader_locate_file(&zip, "test.bin", NULL, 0); + if (alt_file_index != file_index) + { + print_error("mz_zip_reader_locate_file() failed!\n", pZip_filename); + mz_zip_reader_end(&zip); + return false; + } + + if (!mz_zip_reader_extract_to_file(&zip, file_index, pDst_filename, 0)) + { + print_error("Failed extracting test.bin from archive \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + mz_zip_reader_end(&zip); + return false; + } + + for (uint i = 0; i < mz_zip_reader_get_num_files(&zip); i++) + { + mz_zip_archive_file_stat stat; + if (!mz_zip_reader_file_stat(&zip, i, &stat)) + { + print_error("Failed testing archive -1 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + mz_zip_reader_end(&zip); + return false; + } + //printf("\"%s\" %I64u %I64u\n", stat.m_filename, stat.m_comp_size, stat.m_uncomp_size); + size_t size = 0; + + mz_bool status = mz_zip_reader_extract_to_callback(&zip, i, zip_write_callback, NULL, 0); + if (!status) + { + print_error("Failed testing archive -2 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + mz_zip_reader_end(&zip); + return false; + } + + if (stat.m_uncomp_size<100*1024*1024) + { + void *p = mz_zip_reader_extract_to_heap(&zip, i, &size, 0); + if (!p) + { + print_error("Failed testing archive -3 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + mz_zip_reader_end(&zip); + return false; + } + free(p); + } + + if (stat.m_uncomp_size<100*1024*1024) + { + /* Use iterative reader to read onto heap in one big chunk */ + mz_zip_reader_extract_iter_state *pIter = mz_zip_reader_extract_iter_new(&zip, i, 0); + void *p = malloc(stat.m_uncomp_size); + if ((!pIter) && (0 != stat.m_uncomp_size)) + { + print_error("Failed testing archive -4 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + free(p); + mz_zip_reader_end(&zip); + return false; + } + if (pIter) + { + if (stat.m_uncomp_size != mz_zip_reader_extract_iter_read(pIter, p, stat.m_uncomp_size) ) + { + print_error("Failed testing archive -5 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + free(p); + mz_zip_reader_extract_iter_free(pIter); + mz_zip_reader_end(&zip); + return false; + } + if (MZ_TRUE != mz_zip_reader_extract_iter_free(pIter)) + { + print_error("Failed testing archive -6 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + free(p); + mz_zip_reader_end(&zip); + return false; + } + } + free(p); + } + + if (stat.m_uncomp_size<100*1024) + { + /* Use iterative reader to read file one byte at a time */ + mz_zip_reader_extract_iter_state *pIter = mz_zip_reader_extract_iter_new(&zip, i, 0); + uint8_t byBuffer; + if ((!pIter) && (0 != stat.m_uncomp_size)) + { + print_error("Failed testing archive -7 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + mz_zip_reader_end(&zip); + return false; + } + if (pIter) + { + for ( uint64_t uiIndex = 0; uiIndex < stat.m_uncomp_size; uiIndex++ ) + { + if (sizeof(byBuffer) != mz_zip_reader_extract_iter_read(pIter, &byBuffer, sizeof(byBuffer))) + { + print_error("Failed testing archive -8 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + mz_zip_reader_extract_iter_free(pIter); + mz_zip_reader_end(&zip); + return false; + } + } + if (MZ_TRUE != mz_zip_reader_extract_iter_free(pIter)) + { + print_error("Failed testing archive -9 \"%s\" err: %s!\n", pZip_filename, mz_zip_get_error_string(mz_zip_get_last_error(&zip))); + mz_zip_reader_end(&zip); + return false; + } + } + } + } + printf("Verified %u files\n", mz_zip_reader_get_num_files(&zip)); + + mz_zip_reader_end(&zip); + + printf("Extracted file \"%s\"\n", pDst_filename); + return true; +} + +typedef std::vector< std::string > string_array; + +#if defined(WIN32) +static bool find_files(std::string pathname, const std::string &filename, string_array &files, bool recursive, int depth = 0) +{ + if (!pathname.empty()) + { + char c = pathname[pathname.size() - 1]; + if ((c != ':') && (c != '\\') && (c != '/')) + pathname += "\\"; + } + + WIN32_FIND_DATAA find_data; + + HANDLE findHandle = FindFirstFileA((pathname + filename).c_str(), &find_data); + if (findHandle == INVALID_HANDLE_VALUE) + { + HRESULT hres = GetLastError(); + if ((!depth) && (hres != NO_ERROR) && (hres != ERROR_FILE_NOT_FOUND)) + return false; + } + else + { + do + { + const bool is_directory = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + const bool is_system = (find_data.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0; + const bool is_hidden = false;//(find_data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0; + + std::string filename(find_data.cFileName); + + if ((!is_directory) && (!is_system) && (!is_hidden)) + files.push_back(pathname + filename); + + } while (FindNextFileA(findHandle, &find_data)); + + FindClose(findHandle); + } + + if (recursive) + { + string_array paths; + + HANDLE findHandle = FindFirstFileA((pathname + "*").c_str(), &find_data); + if (findHandle != INVALID_HANDLE_VALUE) + { + do + { + const bool is_directory = (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + const bool is_system = (find_data.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0; + const bool is_hidden = false;//(find_data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0; + + std::string filename(find_data.cFileName); + + if ((is_directory) && (!is_hidden) && (!is_system)) + paths.push_back(filename); + + } while (FindNextFileA(findHandle, &find_data)); + + FindClose(findHandle); + + for (uint i = 0; i < paths.size(); i++) + { + const std::string &path = paths[i]; + if (path[0] == '.') + continue; + + if (!find_files(pathname + path, filename, files, true, depth + 1)) + return false; + } + } + } + + return true; +} +#else +#include +#include +static bool find_files(std::string pathname, const std::string &pattern, string_array &files, bool recursive, int depth = 0) +{ + if (!pathname.empty()) + { + char c = pathname[pathname.size() - 1]; + if ((c != ':') && (c != '\\') && (c != '/')) + pathname += "/"; + } + + DIR *dp = opendir(pathname.c_str()); + + if (!dp) + return depth ? true : false; + + string_array paths; + + for ( ; ; ) + { + struct dirent *ep = readdir(dp); + if (!ep) + break; + + const bool is_directory = (ep->d_type & DT_DIR) != 0; + const bool is_file = (ep->d_type & DT_REG) != 0; + + if (ep->d_name[0] == '.') + continue; + + std::string filename(ep->d_name); + + if (is_directory) + { + if (recursive) + paths.push_back(filename); + } + else if (is_file) + { + if (0 == fnmatch(pattern.c_str(), filename.c_str(), 0)) + files.push_back(pathname + filename); + } + } + + closedir(dp); + dp = NULL; + + if (recursive) + { + for (uint i = 0; i < paths.size(); i++) + { + const std::string &path = paths[i]; + if (!find_files(pathname + path, pattern, files, true, depth + 1)) + return false; + } + } + + return true; +} +#endif + +static bool test_recursive(const char *pPath, comp_options options) +{ + string_array files; + if (!find_files(pPath, "*", files, true)) + { + print_error("Failed finding files under path \"%s\"!\n", pPath); + return false; + } + + uint total_files_compressed = 0; + uint64 total_source_size = 0; + uint64 total_comp_size = 0; + +#ifdef WIN32 + MEMORYSTATUS initial_mem_status; + GlobalMemoryStatus(&initial_mem_status); +#endif + + timer_ticks start_tick_count = timer::get_ticks(); + + const int first_file_index = 0; + + uint unique_id = static_cast(timer::get_init_ticks()); + char cmp_file[256], decomp_file[256]; + + sprintf(cmp_file, "__comp_temp_%u__.tmp", unique_id); + sprintf(decomp_file, "__decomp_temp_%u__.tmp", unique_id); + + for (uint file_index = first_file_index; file_index < files.size(); file_index++) + { + const std::string &src_file = files[file_index]; + + printf("***** [%u of %u] Compressing file \"%s\" to \"%s\"\n", 1 + file_index, (uint)files.size(), src_file.c_str(), cmp_file); + + if ((strstr(src_file.c_str(), "__comp_temp") != NULL) || (strstr(src_file.c_str(), "__decomp_temp") != NULL)) + { + printf("Skipping temporary file \"%s\"\n", src_file.c_str()); + continue; + } + + FILE *pFile = fopen(src_file.c_str(), "rb"); + if (!pFile) + { + printf("Skipping unreadable file \"%s\"\n", src_file.c_str()); + continue; + } + _fseeki64(pFile, 0, SEEK_END); + int64 src_file_size = _ftelli64(pFile); + + if (src_file_size) + { + _fseeki64(pFile, 0, SEEK_SET); + if (fgetc(pFile) == EOF) + { + printf("Skipping unreadable file \"%s\"\n", src_file.c_str()); + fclose(pFile); + continue; + } + } + + fclose(pFile); + + if (!ensure_file_is_writable(cmp_file)) + { + print_error("Unable to create file \"%s\"!\n", cmp_file); + return false; + } + + comp_options file_options(options); + if (options.m_randomize_params) + { + file_options.m_level = rand() % 11; + file_options.m_unbuffered_decompression = (rand() & 1) != 0; + file_options.m_z_strat = rand() % (Z_FIXED + 1); + file_options.m_write_zlib_header = (rand() & 1) != 0; + file_options.m_random_z_flushing = (rand() & 1) != 0; + file_options.print(); + } + + bool status; + if (file_options.m_archive_test) + { + printf("Creating test archive with file \"%s\", size " QUAD_INT_FMT "\n", src_file.c_str(), src_file_size); + status = zip_create(cmp_file, src_file.c_str()); + } + else + status = compress_file_zlib(src_file.c_str(), cmp_file, file_options); + if (!status) + { + print_error("Failed compressing file \"%s\" to \"%s\"\n", src_file.c_str(), cmp_file); + return false; + } + + if (file_options.m_verify_compressed_data) + { + printf("Decompressing file \"%s\" to \"%s\"\n", cmp_file, decomp_file); + + if (!ensure_file_is_writable(decomp_file)) + { + print_error("Unable to create file \"%s\"!\n", decomp_file); + return false; + } + + if (file_options.m_archive_test) + status = zip_extract(cmp_file, decomp_file); + else + status = decompress_file_zlib(cmp_file, decomp_file, file_options); + + if (!status) + { + print_error("Failed decompressing file \"%s\" to \"%s\"\n", src_file.c_str(), decomp_file); + return false; + } + + printf("Comparing file \"%s\" to \"%s\"\n", decomp_file, src_file.c_str()); + + if (!compare_files(decomp_file, src_file.c_str())) + { + print_error("Failed comparing decompressed file data while compressing \"%s\" to \"%s\"\n", src_file.c_str(), cmp_file); + return false; + } + else + { + printf("Decompressed file compared OK to original file.\n"); + } + } + + int64 cmp_file_size = 0; + pFile = fopen(cmp_file, "rb"); + if (pFile) + { + _fseeki64(pFile, 0, SEEK_END); + cmp_file_size = _ftelli64(pFile); + fclose(pFile); + } + + total_files_compressed++; + total_source_size += src_file_size; + total_comp_size += cmp_file_size; + +#ifdef WIN32 + MEMORYSTATUS mem_status; + GlobalMemoryStatus(&mem_status); + + const int64 bytes_allocated = initial_mem_status.dwAvailVirtual- mem_status.dwAvailVirtual; + + printf("Memory allocated relative to first file: %I64i\n", bytes_allocated); +#endif + + printf("\n"); + } + + timer_ticks end_tick_count = timer::get_ticks(); + + double total_elapsed_time = timer::ticks_to_secs(end_tick_count - start_tick_count); + + printf("Test successful: %f secs\n", total_elapsed_time); + printf("Total files processed: %u\n", total_files_compressed); + printf("Total source size: " QUAD_INT_FMT "\n", total_source_size); + printf("Total compressed size: " QUAD_INT_FMT "\n", total_comp_size); + printf("Max small comp ratio: %f, Max large comp ratio: %f\n", s_max_small_comp_ratio, s_max_large_comp_ratio); + + remove(cmp_file); + remove(decomp_file); + + return true; +} + +static size_t dummy_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; (void)pBuf; + uint32 *pCRC = (uint32*)pOpaque; + *pCRC = mz_crc32(*pCRC, (const uint8*)pBuf, n); + return n; +} + +static bool test_archives(const char *pPath, comp_options options) +{ + (void)options; + + string_array files; + if (!find_files(pPath, "*.zip", files, true)) + { + print_error("Failed finding files under path \"%s\"!\n", pPath); + return false; + } + + uint total_archives = 0; + uint64 total_bytes_processed = 0; + uint64 total_files_processed = 0; + uint total_errors = 0; + +#ifdef WIN32 + MEMORYSTATUS initial_mem_status; + GlobalMemoryStatus(&initial_mem_status); +#endif + + const int first_file_index = 0; + uint unique_id = static_cast(timer::get_init_ticks()); + char cmp_file[256], decomp_file[256]; + + sprintf(decomp_file, "__decomp_temp_%u__.tmp", unique_id); + + string_array failed_archives; + + for (uint file_index = first_file_index; file_index < files.size(); file_index++) + { + const std::string &src_file = files[file_index]; + + printf("***** [%u of %u] Testing archive file \"%s\"\n", 1 + file_index, (uint)files.size(), src_file.c_str()); + + if ((strstr(src_file.c_str(), "__comp_temp") != NULL) || (strstr(src_file.c_str(), "__decomp_temp") != NULL)) + { + printf("Skipping temporary file \"%s\"\n", src_file.c_str()); + continue; + } + + FILE *pFile = fopen(src_file.c_str(), "rb"); + if (!pFile) + { + printf("Skipping unreadable file \"%s\"\n", src_file.c_str()); + continue; + } + _fseeki64(pFile, 0, SEEK_END); + int64 src_file_size = _ftelli64(pFile); + fclose(pFile); + + (void)src_file_size; + + sprintf(cmp_file, "__comp_temp_%u__.zip", file_index); + + mz_zip_archive src_archive; + memset(&src_archive, 0, sizeof(src_archive)); + + if (!mz_zip_reader_init_file(&src_archive, src_file.c_str(), 0)) + { + failed_archives.push_back(src_file); + + print_error("Failed opening archive \"%s\"!\n", src_file.c_str()); + total_errors++; + continue; + } + + mz_zip_archive dst_archive; + memset(&dst_archive, 0, sizeof(dst_archive)); + if (options.m_write_archives) + { + if (!ensure_file_is_writable(cmp_file)) + { + print_error("Unable to create file \"%s\"!\n", cmp_file); + return false; + } + + if (!mz_zip_writer_init_file(&dst_archive, cmp_file, 0)) + { + print_error("Failed creating archive \"%s\"!\n", cmp_file); + total_errors++; + continue; + } + } + + int i; + //for (i = 0; i < mz_zip_reader_get_num_files(&src_archive); i++) + for (i = mz_zip_reader_get_num_files(&src_archive) - 1; i >= 0; --i) + { + if (mz_zip_reader_is_file_encrypted(&src_archive, i)) + continue; + + mz_zip_archive_file_stat file_stat; + bool status = mz_zip_reader_file_stat(&src_archive, i, &file_stat) != 0; + + int locate_file_index = mz_zip_reader_locate_file(&src_archive, file_stat.m_filename, NULL, 0); + if (locate_file_index != i) + { + mz_zip_archive_file_stat locate_file_stat; + mz_zip_reader_file_stat(&src_archive, locate_file_index, &locate_file_stat); + if (_stricmp(locate_file_stat.m_filename, file_stat.m_filename) != 0) + { + print_error("mz_zip_reader_locate_file() failed!\n"); + return false; + } + else + { + printf("Warning: Duplicate filenames in archive!\n"); + } + } + + if ((file_stat.m_method) && (file_stat.m_method != MZ_DEFLATED)) + continue; + + if (status) + { + char name[260]; + mz_zip_reader_get_filename(&src_archive, i, name, sizeof(name)); + + size_t extracted_size = 0; + void *p = mz_zip_reader_extract_file_to_heap(&src_archive, name, &extracted_size, 0); + if (!p) + status = false; + + uint32 extracted_crc32 = MZ_CRC32_INIT; + if (!mz_zip_reader_extract_file_to_callback(&src_archive, name, dummy_zip_file_write_callback, &extracted_crc32, 0)) + status = false; + + if (mz_crc32(MZ_CRC32_INIT, (const uint8*)p, extracted_size) != extracted_crc32) + status = false; + + mz_zip_reader_extract_iter_state *pIter = mz_zip_reader_extract_file_iter_new(&src_archive, name, 0); + void *q = malloc(extracted_size); + mz_zip_reader_extract_iter_read(pIter, q, extracted_size); + mz_zip_reader_extract_iter_free(pIter); + + if (mz_crc32(MZ_CRC32_INIT, (const uint8*)q, extracted_size) != extracted_crc32) + status = false; + + free(q); + free(p); + + if (options.m_write_archives) + { + if ((status) && (!mz_zip_writer_add_from_zip_reader(&dst_archive, &src_archive, i))) + { + print_error("Failed adding new file to archive \"%s\"!\n", cmp_file); + status = false; + } + } + + total_bytes_processed += file_stat.m_uncomp_size; + total_files_processed++; + } + + if (!status) + break; + } + + mz_zip_reader_end(&src_archive); + + //if (i < mz_zip_reader_get_num_files(&src_archive)) + if (i >= 0) + { + failed_archives.push_back(src_file); + + print_error("Failed processing archive \"%s\"!\n", src_file.c_str()); + total_errors++; + } + + if (options.m_write_archives) + { + if (!mz_zip_writer_finalize_archive(&dst_archive) || !mz_zip_writer_end(&dst_archive)) + { + failed_archives.push_back(src_file); + + print_error("Failed finalizing archive \"%s\"!\n", cmp_file); + total_errors++; + } + } + + total_archives++; + +#ifdef WIN32 + MEMORYSTATUS mem_status; + GlobalMemoryStatus(&mem_status); + + const int64 bytes_allocated = initial_mem_status.dwAvailVirtual- mem_status.dwAvailVirtual; + + printf("Memory allocated relative to first file: %I64i\n", bytes_allocated); +#endif + + printf("\n"); + } + + printf("Total archives processed: %u\n", total_archives); + printf("Total errors: %u\n", total_errors); + printf("Total bytes processed: " QUAD_INT_FMT "\n", total_bytes_processed); + printf("Total archive files processed: " QUAD_INT_FMT "\n", total_files_processed); + + printf("List of failed archives:\n"); + for (uint i = 0; i < failed_archives.size(); ++i) + printf("%s\n", failed_archives[i].c_str()); + + remove(cmp_file); + remove(decomp_file); + + return true; +} + +int main_internal(string_array cmd_line) +{ + comp_options options; + + if (!cmd_line.size()) + { + print_usage(); + if (simple_test1(options) || simple_test2(options)) + return EXIT_FAILURE; + return EXIT_SUCCESS; + } + + enum op_mode_t + { + OP_MODE_INVALID = -1, + OP_MODE_COMPRESS = 0, + OP_MODE_DECOMPRESS = 1, + OP_MODE_ALL = 2, + OP_MODE_ARCHIVES = 3 + }; + + op_mode_t op_mode = OP_MODE_INVALID; + + for (int i = 0; i < (int)cmd_line.size(); i++) + { + const std::string &str = cmd_line[i]; + if (str[0] == '-') + { + if (str.size() < 2) + { + print_error("Invalid option: %s\n", str.c_str()); + return EXIT_FAILURE; + } + switch (tolower(str[1])) + { + case 'u': + { + options.m_unbuffered_decompression = true; + break; + } + case 'm': + { + int comp_level = atoi(str.c_str() + 2); + if ((comp_level < 0) || (comp_level > (int)10)) + { + print_error("Invalid compression level: %s\n", str.c_str()); + return EXIT_FAILURE; + } + + options.m_level = comp_level; + break; + } + case 'v': + { + options.m_verify_compressed_data = true; + break; + } + case 'r': + { + options.m_randomize_params = true; + break; + } + case 'b': + { + options.m_randomize_buffer_sizes = true; + break; + } + case 'h': + { + options.m_random_z_flushing = true; + break; + } + case 'x': + { + int seed = atoi(str.c_str() + 2); + srand(seed); + printf("Using random seed: %i\n", seed); + break; + } + case 't': + { + options.m_z_strat = my_min(Z_FIXED, my_max(0, atoi(str.c_str() + 2))); + break; + } + case 'z': + { + options.m_write_zlib_header = false; + break; + } + case 'a': + { + options.m_archive_test = true; + break; + } + case 'w': + { + options.m_write_archives = true; + break; + } + default: + { + print_error("Invalid option: %s\n", str.c_str()); + return EXIT_FAILURE; + } + } + + cmd_line.erase(cmd_line.begin() + i); + i--; + + continue; + } + + if (str.size() != 1) + { + print_error("Invalid mode: %s\n", str.c_str()); + return EXIT_FAILURE; + } + switch (tolower(str[0])) + { + case 'c': + { + op_mode = OP_MODE_COMPRESS; + break; + } + case 'd': + { + op_mode = OP_MODE_DECOMPRESS; + break; + } + case 'a': + { + op_mode = OP_MODE_ALL; + break; + } + case 'r': + { + op_mode = OP_MODE_ARCHIVES; + break; + } + default: + { + print_error("Invalid mode: %s\n", str.c_str()); + return EXIT_FAILURE; + } + } + cmd_line.erase(cmd_line.begin() + i); + break; + } + + if (op_mode == OP_MODE_INVALID) + { + print_error("No mode specified!\n"); + print_usage(); + return EXIT_FAILURE; + } + + printf("Using options:\n"); + options.print(); + printf("\n"); + + int exit_status = EXIT_FAILURE; + + switch (op_mode) + { + case OP_MODE_COMPRESS: + { + if (cmd_line.size() < 2) + { + print_error("Must specify input and output filenames!\n"); + return EXIT_FAILURE; + } + else if (cmd_line.size() > 2) + { + print_error("Too many filenames!\n"); + return EXIT_FAILURE; + } + + const std::string &src_file = cmd_line[0]; + const std::string &cmp_file = cmd_line[1]; + + bool comp_result = compress_file_zlib(src_file.c_str(), cmp_file.c_str(), options); + if (comp_result) + exit_status = EXIT_SUCCESS; + + if ((comp_result) && (options.m_verify_compressed_data)) + { + char decomp_file[256]; + + sprintf(decomp_file, "__decomp_temp_%u__.tmp", (uint)timer::get_ms()); + + if (!decompress_file_zlib(cmp_file.c_str(), decomp_file, options)) + { + print_error("Failed decompressing file \"%s\" to \"%s\"\n", cmp_file.c_str(), decomp_file); + return EXIT_FAILURE; + } + + printf("Comparing file \"%s\" to \"%s\"\n", decomp_file, src_file.c_str()); + + if (!compare_files(decomp_file, src_file.c_str())) + { + print_error("Failed comparing decompressed file data while compressing \"%s\" to \"%s\"\n", src_file.c_str(), cmp_file.c_str()); + return EXIT_FAILURE; + } + else + { + printf("Decompressed file compared OK to original file.\n"); + } + + remove(decomp_file); + } + + break; + } + case OP_MODE_DECOMPRESS: + { + if (cmd_line.size() < 2) + { + print_error("Must specify input and output filenames!\n"); + return EXIT_FAILURE; + } + else if (cmd_line.size() > 2) + { + print_error("Too many filenames!\n"); + return EXIT_FAILURE; + } + if (decompress_file_zlib(cmd_line[0].c_str(), cmd_line[1].c_str(), options)) + exit_status = EXIT_SUCCESS; + break; + } + case OP_MODE_ALL: + { + if (!cmd_line.size()) + { + print_error("No directory specified!\n"); + return EXIT_FAILURE; + } + else if (cmd_line.size() != 1) + { + print_error("Too many filenames!\n"); + return EXIT_FAILURE; + } + if (test_recursive(cmd_line[0].c_str(), options)) + exit_status = EXIT_SUCCESS; + break; + } + case OP_MODE_ARCHIVES: + { + if (!cmd_line.size()) + { + print_error("No directory specified!\n"); + return EXIT_FAILURE; + } + else if (cmd_line.size() != 1) + { + print_error("Too many filenames!\n"); + return EXIT_FAILURE; + } + if (test_archives(cmd_line[0].c_str(), options)) + exit_status = EXIT_SUCCESS; + break; + } + default: + { + print_error("No mode specified!\n"); + print_usage(); + return EXIT_FAILURE; + } + } + + return exit_status; +} + +int main(int argc, char *argv[]) +{ +#if defined(_WIN64) || defined(__LP64__) || defined(_LP64) + printf("miniz.c x64 Command Line Test App - Compiled %s %s\n", __DATE__, __TIME__); +#else + printf("miniz.c x86 Command Line Test App - Compiled %s %s\n", __DATE__, __TIME__); +#endif + timer::get_ticks(); + + string_array cmd_line; + for (int i = 1; i < argc; i++) + cmd_line.push_back(std::string(argv[i])); + + int exit_status = main_internal(cmd_line); + + return exit_status; +} diff --git a/3rdparty/miniz/tests/timer.cpp b/3rdparty/miniz/tests/timer.cpp new file mode 100644 index 0000000..bb35b6c --- /dev/null +++ b/3rdparty/miniz/tests/timer.cpp @@ -0,0 +1,152 @@ +// File: timer.cpp - Simple high-precision timer class. Supports Win32, X360, and POSIX/Linux +#include +#include +#include +#include + +#include "timer.h" + +#if defined(WIN32) +#include +#elif defined(_XBOX) +#include +#endif + +unsigned long long timer::g_init_ticks; +unsigned long long timer::g_freq; +double timer::g_inv_freq; + +#if defined(WIN32) || defined(_XBOX) +inline void query_counter(timer_ticks *pTicks) +{ + QueryPerformanceCounter(reinterpret_cast(pTicks)); +} +inline void query_counter_frequency(timer_ticks *pTicks) +{ + QueryPerformanceFrequency(reinterpret_cast(pTicks)); +} +#elif defined(__GNUC__) +#include +inline void query_counter(timer_ticks *pTicks) +{ + struct timeval cur_time; + gettimeofday(&cur_time, NULL); + *pTicks = static_cast(cur_time.tv_sec)*1000000ULL + static_cast(cur_time.tv_usec); +} +inline void query_counter_frequency(timer_ticks *pTicks) +{ + *pTicks = 1000000; +} +#endif + +timer::timer() : + m_start_time(0), + m_stop_time(0), + m_started(false), + m_stopped(false) +{ + if (!g_inv_freq) + init(); +} + +timer::timer(timer_ticks start_ticks) +{ + if (!g_inv_freq) + init(); + + m_start_time = start_ticks; + + m_started = true; + m_stopped = false; +} + +void timer::start(timer_ticks start_ticks) +{ + m_start_time = start_ticks; + + m_started = true; + m_stopped = false; +} + +void timer::start() +{ + query_counter(&m_start_time); + + m_started = true; + m_stopped = false; +} + +void timer::stop() +{ + assert(m_started); + + query_counter(&m_stop_time); + + m_stopped = true; +} + +double timer::get_elapsed_secs() const +{ + assert(m_started); + if (!m_started) + return 0; + + timer_ticks stop_time = m_stop_time; + if (!m_stopped) + query_counter(&stop_time); + + timer_ticks delta = stop_time - m_start_time; + return delta * g_inv_freq; +} + +timer_ticks timer::get_elapsed_us() const +{ + assert(m_started); + if (!m_started) + return 0; + + timer_ticks stop_time = m_stop_time; + if (!m_stopped) + query_counter(&stop_time); + + timer_ticks delta = stop_time - m_start_time; + return (delta * 1000000ULL + (g_freq >> 1U)) / g_freq; +} + +void timer::init() +{ + if (!g_inv_freq) + { + query_counter_frequency(&g_freq); + g_inv_freq = 1.0f / g_freq; + + query_counter(&g_init_ticks); + } +} + +timer_ticks timer::get_init_ticks() +{ + if (!g_inv_freq) + init(); + + return g_init_ticks; +} + +timer_ticks timer::get_ticks() +{ + if (!g_inv_freq) + init(); + + timer_ticks ticks; + query_counter(&ticks); + return ticks - g_init_ticks; +} + +double timer::ticks_to_secs(timer_ticks ticks) +{ + if (!g_inv_freq) + init(); + + return ticks * g_inv_freq; +} + diff --git a/3rdparty/miniz/tests/timer.h b/3rdparty/miniz/tests/timer.h new file mode 100644 index 0000000..df6e01f --- /dev/null +++ b/3rdparty/miniz/tests/timer.h @@ -0,0 +1,40 @@ +// File: timer.h +#pragma once + +typedef unsigned long long timer_ticks; + +class timer +{ +public: + timer(); + timer(timer_ticks start_ticks); + + void start(); + void start(timer_ticks start_ticks); + + void stop(); + + double get_elapsed_secs() const; + inline double get_elapsed_ms() const { return get_elapsed_secs() * 1000.0f; } + timer_ticks get_elapsed_us() const; + + static void init(); + static inline timer_ticks get_ticks_per_sec() { return g_freq; } + static timer_ticks get_init_ticks(); + static timer_ticks get_ticks(); + static double ticks_to_secs(timer_ticks ticks); + static inline double ticks_to_ms(timer_ticks ticks) { return ticks_to_secs(ticks) * 1000.0f; } + static inline double get_secs() { return ticks_to_secs(get_ticks()); } + static inline double get_ms() { return ticks_to_ms(get_ticks()); } + +private: + static timer_ticks g_init_ticks; + static timer_ticks g_freq; + static double g_inv_freq; + + timer_ticks m_start_time; + timer_ticks m_stop_time; + + bool m_started : 1; + bool m_stopped : 1; +}; diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d2b939e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.12) + +# Pull in PICO SDK (must be before project) +include(pico_sdk_import.cmake) + +# We also need PICO EXTRAS +include(pico_extras_import.cmake) + +project(khan C CXX) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Add pico targets to the build +pico_sdk_init() + +add_subdirectory(stream) +add_subdirectory(khan) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ae5234 --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +# Overview + +This README is specific to the "pico" fork of unrealspeccyp. See [here](README_USP) for the original unrealspeccyp readme. + +## Features + +* Spectrum 48K and 128K emulator based on [unrealspeccyp](https://bitbucket.org/djdron/unrealspeccyp/wiki/Home). +* VGA output. +* Beeper output pin for 48K spectrum (i.e. just wiggling the pin like the real Spectrum!). +* AY sound output for 128K spectrum. +* .TAP, .SNA, .Z80 support (embedded in binary). +* This port was development during pre-silicon RP2040 development. Therefore, it had to run at 48Mhz, and indeed. + both 48K and 128K emulators still run by default at a 48Mhz system clock. +* Read/Write on RP2040 GPIO pins - because why not? (see bitbang app). + +## Some caveats + +* The sole reason for this project's existence was as a system test/bit of fun during early development of the RP2040, it is + not intended to be the best, full-featured emulator. I have open sourced it as people keep asking! +* 'Khan' is the codename I used to keep what I was doing secret :-) +* No doubt these terse instructions will prove frustrating for some; feel free to submit PRs! +* You cannot currently use beeper/tape noise and AY sound at the same time (as they use different mechanisms) - + actually I think there is support for using one PWM channel for each, but IDK if it still works. +* I did some recent cleanup in preparation for open-source: + * Untangled the horrible mess of symlinks that had this building within another makefile project prior to the Pico + SDK. + * Added USB keyboard support (this seems fine - though I had to hunt for more RAM, so it is possible this may + break something - you can run with or without). + * Hopefully got a license on everything important. + * Added some I2S support - this is only for AY sound as the beeper just wiggles a pin. Sadly my I2S decoder + doesn't like 22050Hz, so I had to up it to 24000Hz. I don't necessarily recommend using this if you can use PWM + (the default). + +# Running + +## Pin Outs + +If you want to know about pin usage and features, the best thing to do is run: + +e.g. +```bash +$ picotool info -a khan128_usb.elf +File khan128_usb.elf: + +Program Information + name: khan128_usb + features: SDL event forwarder support + USB keyboard support + UART stdin / stdout + binary start: 0x10000000 + binary end: 0x1003b2ec + +Fixed Pin Information + 0-4: Red 0-5 + 6-10: Green 0-5 + 11-15: Blue 0-5 + 16: HSync + 17: VSync + 20: UART1 TX + 21: UART1 RX + 28: AY sound (PWM) + +Build Information + sdk version: 1.5.1-develop + pico_board: vgaboard + boot2_name: boot2_w25q080 + build date: May 29 2023 + build attributes: Release +``` + +These video/audio pins match for example the Pimoroni Pico VGA Demo Base which itself is based on the suggested +Raspberry Pi Documentation [here](https://datasheets.raspberrypi.com/rp2040/hardware-design-with-rp2040.pdf) +and the design files zipped [here](https://datasheets.raspberrypi.com/rp2040/VGA-KiCAD.zip). + +## Keyboard input + +* **USB Keyboard** - Depending on the version you have, you can use a real USB keyboard (if supported by TinyUSB on + RP2040). I know +RaspberryPi keyboards work, and my old Dell keyboard. If in doubt, the older and crappier the better! USB keyboard + support is enabled for binaries with names ending in `_usb`. + +* **SDL Event Forwader** - This is a simple app that tunnels input from a window on the host machine over UART to + the RP2040: https://github.com/kilograham/sdl_event_forwarder. Note however this sometimes seems to get keys stuck + on the spectrum. This is enabled by default in all builds. + +* **Raw UART** - Not very useful, but you can enable this, and allow typing a few keys on the spectrum (you need to + find this in the `CMakeLists.txt` to enable it). + +### Keys + +These are the same as regular unrealspeccyp. Good luck typing! +* Shift keys on your keyboard are "Caps Shift". +* Alt or Ctrl keys on your keyboard are "Symbol Shift". + +# Building + +You should use the latest `develop` branch of +[pico-sdk](https://github.com/raspberrypi/pico-sdk/tree/develop) and +[pico-extras](https://github.com/raspberrypi/pico-extras/tree/develop). + +## Building for RP2040 + +This should work on Linux and Mac (and possibly Windows; I haven't tried). + +To build, from the project root directory: +``` +mkdir build +cd build +cmake -DPICO_SDK_PATH=path/to/pico-sdk -DPICO_BOARD=vgaboard .. +make -j4 +``` + +## Building for RP2040 + +This should work on Linux and Mac (and possibly Windows; I haven't tried). + +To build: +``` +mkdir build +cd build +cmake -DPICO_SDK_PATH=path/to/pico-sdk -DPICO_BOARD=vgaboard .. +make -j4 +``` + +You need to specify `PICO_EXTRAS_PATH` as well if `pico-extras` isn't in a sibling directory of `pico-sdk`. + +Note the output binaries are in `build/khan`. + +## Building for PICO_PLATFORM=host + +You can run this on your Linux/macOS host if you have `libsdl2-dev` etc. installed using the `host` platform +mode of the SDK. + +You need [pico_host_sdl](https://github.com/raspberrypi/pico-host-sdl) to replace the video/audio support with native. + +To build: +``` +mkdir native_build +cd native_build +cmake -DPICO_SDK_PATH=path/to/pico-sdk -DPICO_PLATFORM=host -DPICO_SDK_PRE_LIST_DIRS=/path/to/pico_host_sdl .. +make -j4 +``` + +Note the output binaries are in `native_build/khan`. There is no different between `_usb` variants and non `_usb` +variants. + +# Implementation Notes + +Not much here atm, sorry: + +* We have A really quite small/fast Z80 emulator - this might be useful + * The generation is a bit batshit; I figured, at the time, that the simplest thing to do would be to use some C++ + hackery, to execute the code for each instruction, using crafted C++ classes, such that assignments etc. are + overloaded to print out ARM assembly. (shiver) + * If I recalled, the smallest version of the code is 2K, the slightly bigger one is 3K. +* The beeper/tape implementation just wiggles a pin, which it tries to do at exactly the right time by passing run + lengths to a PIO state machine. Note that to support volume, a short PWM pulse is repeated for each length of the run. +* The small AY emulator is also quite handy, though again is a bit crazy, because I thought it would be important to + do some oversampling, and frankly, I'm no longer sure that is necessary. Also, I wonder if it is missing some + feature or other (I don't recall), as the `down` demo in `kahn_turbo` sounds different on native. +* I didn't make a proper port in the platform/ directory, sorry. This is probably possible, but wasn't my focus. diff --git a/README b/README_USP similarity index 97% rename from README rename to README_USP index 3390135..da2eca1 100755 --- a/README +++ b/README_USP @@ -1,10 +1,10 @@ -Portable ZX-Spectrum emulator supports Z80 128K (Pentagon) AY/YM, Beeper, Beta Disk, Tape, Kempston Joystick/Mouse, Snapshots, Replays. - -Supported formats: sna, z80, szx, rzx, tap, tzx, csw, trd, scl, fdi, td0, udi, zip. - -Created to be ported to many platforms such as Windows/Linux/Mac/Symbian/WinMobile/Dingoo A320/Android/PSP/Raspberry Pi, ... - -Authors : SMT, Dexus, Alone Coder, deathsoft. -Portable version : djdron, scor. - -Homepage: https://bitbucket.org/djdron/unrealspeccyp +Portable ZX-Spectrum emulator supports Z80 128K (Pentagon) AY/YM, Beeper, Beta Disk, Tape, Kempston Joystick/Mouse, Snapshots, Replays. + +Supported formats: sna, z80, szx, rzx, tap, tzx, csw, trd, scl, fdi, td0, udi, zip. + +Created to be ported to many platforms such as Windows/Linux/Mac/Symbian/WinMobile/Dingoo A320/Android/PSP/Raspberry Pi, ... + +Authors : SMT, Dexus, Alone Coder, deathsoft. +Portable version : djdron, scor. + +Homepage: https://bitbucket.org/djdron/unrealspeccyp diff --git a/devices/device.cpp b/devices/device.cpp index 0bf3331..05e2c79 100644 --- a/devices/device.cpp +++ b/devices/device.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,16 +19,22 @@ along with this program. If not, see . #include "../std.h" #include "device.h" - +#ifdef USE_KHAN_GPIO +// hack +#include "khan_lib.h" +#endif //============================================================================= // eDevices::eDevices //----------------------------------------------------------------------------- eDevices::eDevices() { memset(items, 0, sizeof(items)); +#ifndef USE_HACKED_DEVICE_ABSTRACTION memset(items_io_read, 0, sizeof(items_io_read)); memset(items_io_write, 0, sizeof(items_io_write)); +#endif } +#ifndef NO_USE_DESTRUCTORS //============================================================================= // eDevices::~eDevices //----------------------------------------------------------------------------- @@ -38,11 +45,13 @@ eDevices::~eDevices() SAFE_DELETE(items[i]); } } +#endif //============================================================================= // eDevices::Init //----------------------------------------------------------------------------- void eDevices::Init() { +#ifndef USE_HACKED_DEVICE_ABSTRACTION int io_read_count = 0; for(; items_io_read[io_read_count]; ++io_read_count); int io_write_count = 0; @@ -89,6 +98,7 @@ void eDevices::Init() } *dl = NULL; } +#endif } //============================================================================= // eDevices::Reset @@ -138,6 +148,7 @@ void eDevices::_Add(eDeviceId id, eDevice* d) assert(d && !items[id]); d->Init(); items[id] = d; +#ifndef USE_HACKED_DEVICE_ABSTRACTION if(d->IoNeed()&eDevice::ION_READ) { eDevice** dl = items_io_read; @@ -152,4 +163,144 @@ void eDevices::_Add(eDeviceId id, eDevice* d) ++dl; *dl = d; } +#endif +} + +#ifdef USE_HACKED_DEVICE_ABSTRACTION +#include "../platform/platform.h" +#include "../speccy.h" +#ifndef NO_USE_TAPE +#include "input/tape.h" +#endif +#ifndef NO_USE_BEEPER +#include "sound/beeper.h" +#endif +#ifndef NO_USE_AY +#include "sound/ay.h" +#endif +#ifndef NO_USE_KEMPSTON +#include "input/kempston_joy.h" +#endif +#include "input/keyboard.h" +#include "ula.h" +#include "memory.h" + +static struct { + eUla *ula; + eKeyboard *keyboard; +#ifndef NO_USE_TAPE + eTape *tape; +#endif +#ifndef NO_USE_BEEPER + eBeeper *beeper; +#endif +#ifndef NO_USE_128K + eRom *rom; + eRam *ram; +#endif +#ifndef NO_USE_AY + eAY *ay; +#endif +#ifndef NO_USE_KEMPSTON + eKempstonJoy *kempston_joy; +#endif + bool cached; +} _devices; + +static void cache_devices() { + if (!_devices.cached) { + _devices.ula = xPlatform::Handler()->Speccy()->Devices().Get(); + _devices.keyboard = xPlatform::Handler()->Speccy()->Devices().Get(); +#ifndef NO_USE_TAPE + _devices.tape = xPlatform::Handler()->Speccy()->Devices().Get(); +#endif +#ifndef NO_USE_BEEPER + _devices.beeper = xPlatform::Handler()->Speccy()->Devices().Get(); +#endif +#ifndef NO_USE_AY + _devices.ay = xPlatform::Handler()->Speccy()->Devices().Get(); +#endif +#ifndef NO_USE_KEMPSTON + _devices.kempston_joy = xPlatform::Handler()->Speccy()->Devices().Get(); +#endif +#ifndef NO_USE_128K + _devices.rom = xPlatform::Handler()->Speccy()->Devices().Get(); + _devices.ram = xPlatform::Handler()->Speccy()->Devices().Get(); +#endif + _devices.cached = true; + } +} +extern "C" byte static_device_io_read(int port, int tact) { + byte v = 0xff; + cache_devices(); + if (port == 0xff) { + _devices.ula->IoRead(port, &v, tact); + } else if (!(port&1)) { + _devices.keyboard->IoRead(port, &v, tact); + // force press of enter +// if (port == 49150) { +// v = 190; +// } +#ifndef NO_USE_TAPE + _devices.tape->IoRead(port, &v, tact); +#endif +#ifdef USE_KHAN_GPIO + } else if (0b11101011 == (port & 0xff)) { + v = khan_gpio_read(port >> 8); +#endif + } +#ifndef NO_USE_AY + if ((port & 0xC0FF) == 0xC0FD) + { + v = _devices.ay->Read(); + } +#endif +#ifndef NO_USE_KEMPSTON + if (!(port&0x20)) { + // A13,A15 not used in decoding + uint p = port | 0xfa00; + if (!(p == 0xfadf || p == 0xfbdf || p == 0xffdf)) { + _devices.kempston_joy->IoRead(port, &v, tact); + } + } +#endif +#if !defined(NO_USE_FDD) || !defined(NO_USE_REPLAY) +#error todo +#endif + return v; +} + + +// order of args important for assembler +extern "C" void static_device_io_write(byte v, int port, int tact) { + cache_devices(); + if (!(port&1)) { + _devices.ula->IoWrite(port, v, tact); + } +#ifdef USE_KHAN_GPIO + if (0b11101011 == (port & 0xff)) { + khan_gpio_write(port >> 8, v); + } +#endif +#ifndef NO_USE_BEEPER + _devices.beeper->IoWrite(port, v, tact); +#endif +#if !defined(NO_USE_128K) + if(!(port & 2)) { + if (!(port & 0x8000)) // zx128 port + { + _devices.rom->IoWrite(port, v, tact); + _devices.ram->IoWrite(port, v, tact); + _devices.ula->SwitchScreen(!(v & 0x08), tact); + } +#ifndef NO_USE_AY + if ((port & 0xC0FF) == 0xC0FD) { + _devices.ay->Select(v); + } else if ((port & 0xC000) == 0x8000) { + _devices.ay->Write(tact, v); + } +#endif + } +#endif } +#endif diff --git a/devices/device.h b/devices/device.h index 7ef34fb..62878d0 100644 --- a/devices/device.h +++ b/devices/device.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,23 +29,55 @@ class eDevice { public: eDevice() {} +#ifndef NO_USE_DESTRUCTORS virtual ~eDevice() {} +#endif virtual void Init() {} virtual void Reset() {} virtual void FrameStart(dword tacts) {} virtual void FrameUpdate() {} virtual void FrameEnd(dword tacts) {} +#ifndef USE_HACKED_DEVICE_ABSTRACTION enum eIoNeed { ION_READ = 0x01, ION_WRITE = 0x02 }; virtual bool IoRead(word port) const { return false; } virtual bool IoWrite(word port) const { return false; } + virtual dword IoNeed() const { return 0; } +#endif virtual void IoRead(word port, byte* v, int tact) {} virtual void IoWrite(word port, byte v, int tact) {} - virtual dword IoNeed() const { return 0; } }; -enum eDeviceId { D_ROM, D_RAM, D_ULA, D_KEYBOARD, D_KEMPSTON_JOY, D_KEMPSTON_MOUSE, D_BEEPER, D_AY, D_WD1793, D_TAPE, D_COUNT }; +enum eDeviceId { + D_ROM, + D_RAM, + D_ULA, + D_KEYBOARD, +#ifndef NO_USE_KEMPSTON + D_KEMPSTON_JOY, +#ifndef USE_MU + D_KEMPSTON_MOUSE, +#endif +#endif +#ifndef NO_USE_BEEPER + D_BEEPER, +#endif +#ifndef NO_USE_AY + D_AY, +#endif +#ifndef NO_USE_FDD + D_WD1793, +#endif +#ifndef NO_USE_TAPE + D_TAPE, +#endif + D_COUNT +}; +#ifdef USE_HACKED_DEVICE_ABSTRACTION +extern "C" byte static_device_io_read(int port, int tact); +extern "C" void static_device_io_write(byte v, int port, int tact); +#endif //***************************************************************************** // eDevices //----------------------------------------------------------------------------- @@ -52,7 +85,10 @@ class eDevices { public: eDevices(); + +#ifndef NO_USE_DESTRUCTORS ~eDevices(); +#endif void Init(); void Reset(); @@ -62,17 +98,25 @@ class eDevices byte IoRead(word port, int tact) { +#ifndef USE_HACKED_DEVICE_ABSTRACTION byte v = 0xff; eDevice** dl = io_read_cache[io_read_map[port]]; while(*dl) (*dl++)->IoRead(port, &v, tact); return v; +#else + return static_device_io_read(port, tact); +#endif } void IoWrite(word port, byte v, int tact) { +#ifndef USE_HACKED_DEVICE_ABSTRACTION eDevice** dl = io_write_cache[io_write_map[port]]; while(*dl) (*dl++)->IoWrite(port, v, tact); +#else + static_device_io_write(v, port, tact); +#endif } void FrameStart(dword tacts); @@ -83,12 +127,14 @@ class eDevices void _Add(eDeviceId id, eDevice* d); eDevice* _Get(eDeviceId id) const { return items[id]; } eDevice* items[D_COUNT]; +#ifndef USE_HACKED_DEVICE_ABSTRACTION eDevice* items_io_read[D_COUNT + 1]; eDevice* items_io_write[D_COUNT + 1]; byte io_read_map[0x10000]; byte io_write_map[0x10000]; eDevice* io_read_cache[0x100][9]; eDevice* io_write_cache[0x100][9]; +#endif }; #endif//__DEVICE_H__ diff --git a/devices/input/kempston_joy.cpp b/devices/input/kempston_joy.cpp index c7898bd..6a26a8a 100644 --- a/devices/input/kempston_joy.cpp +++ b/devices/input/kempston_joy.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,17 +17,19 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#ifndef NO_USE_KEMPSTON #include "../../std.h" #include "kempston_joy.h" void eKempstonJoy::Init() { Reset(); } void eKempstonJoy::Reset() { state = 0; } -//============================================================================= -// eKempstonJoy::IoRead -//----------------------------------------------------------------------------- -bool eKempstonJoy::IoRead(word port) const -{ +#ifndef USE_MU +//============================================================================= +// eKempstonJoy::IoRead +//----------------------------------------------------------------------------- +bool eKempstonJoy::IoRead(word port) const +{ if(port&0x20) return false; // skip kempston mouse ports @@ -34,10 +37,11 @@ bool eKempstonJoy::IoRead(word port) const if((port == 0xfadf || port == 0xfbdf || port == 0xffdf)) return false; return true; -} -//============================================================================= -// eKempstonJoy::IoRead -//----------------------------------------------------------------------------- +} +#endif +//============================================================================= +// eKempstonJoy::IoRead +//----------------------------------------------------------------------------- void eKempstonJoy::IoRead(word port, byte* v, int tact) { *v = state; @@ -48,25 +52,26 @@ struct eButton char key; byte bit; }; +#ifndef USE_MU enum { BUTTONS_COUNT = 5 }; static const eButton buttons[BUTTONS_COUNT] = { - {'r', 0x01 }, - {'l', 0x02 }, - {'d', 0x04 }, - {'u', 0x08 }, - {'f', 0x10 }, + {'r', KEMPSTON_R }, + {'l', KEMPSTON_L }, + {'d', KEMPSTON_D }, + {'u', KEMPSTON_U }, + {'f', KEMPSTON_F }, }; -//============================================================================= -// eKempstonJoy::OnKey -//----------------------------------------------------------------------------- +//============================================================================= +// eKempstonJoy::OnKey +//----------------------------------------------------------------------------- void eKempstonJoy::OnKey(char _key, bool _down) { KeyState(_key, _down); } -//============================================================================= -// eKempstonJoy::KeyState -//----------------------------------------------------------------------------- +//============================================================================= +// eKempstonJoy::KeyState +//----------------------------------------------------------------------------- void eKempstonJoy::KeyState(char _key, bool _down) { for(int i = 0; i < BUTTONS_COUNT; ++i) @@ -82,3 +87,5 @@ void eKempstonJoy::KeyState(char _key, bool _down) } } } +#endif +#endif \ No newline at end of file diff --git a/devices/input/kempston_joy.h b/devices/input/kempston_joy.h index 8d516fc..c8eebcf 100644 --- a/devices/input/kempston_joy.h +++ b/devices/input/kempston_joy.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,19 +24,38 @@ along with this program. If not, see . #pragma once +#define KEMPSTON_R 0x01 +#define KEMPSTON_L 0x02 +#define KEMPSTON_D 0x04 +#define KEMPSTON_U 0x08 +#define KEMPSTON_F 0x10 + class eKempstonJoy : public eDevice { public: virtual void Init(); virtual void Reset(); - virtual bool IoRead(word port) const; +#ifndef USE_MU + virtual bool IoRead(word port) const; +#endif virtual void IoRead(word port, byte* v, int tact); - void OnKey(char key, bool down); +#ifndef USE_MU + void OnKey(char key, bool down); +#else + inline void setState(byte _state) { + state = _state; + } +#endif static eDeviceId Id() { return D_KEMPSTON_JOY; } + +#ifndef USE_MU virtual dword IoNeed() const { return ION_READ; } +#endif protected: +#ifndef USE_MU void KeyState(char key, bool down); +#endif byte state; }; diff --git a/devices/input/keyboard.cpp b/devices/input/keyboard.cpp index 8bafe94..aafd988 100644 --- a/devices/input/keyboard.cpp +++ b/devices/input/keyboard.cpp @@ -1,22 +1,23 @@ -/* -Portable ZX-Spectrum emulator. -Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -#include "../../std.h" +/* +Portable ZX-Spectrum emulator. +Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "../../std.h" #include "keyboard.h" //============================================================================= @@ -33,13 +34,15 @@ void eKeyboard::Reset() { memset(kbd, 0xff, sizeof(kbd)); } -//============================================================================= -// eKeyboard::IoRead -//----------------------------------------------------------------------------- -bool eKeyboard::IoRead(word port) const -{ - return !(port&1); -} +#ifndef USE_HACKED_DEVICE_ABSTRACTION +//============================================================================= +// eKeyboard::IoRead +//----------------------------------------------------------------------------- +bool eKeyboard::IoRead(word port) const +{ + return !(port&1); +} +#endif //============================================================================= // eKeyboard::IoRead //----------------------------------------------------------------------------- @@ -53,11 +56,11 @@ void eKeyboard::IoRead(word port, byte* v, int tact) byte eKeyboard::Read(byte scan) const { byte res = 0xbf; - for(int i = 0; i < 8; i++) - { - if(!(scan & (1<. #include "../../z80/z80.h" #include "../memory.h" #include "tape.h" +#include +#include "../../options_common.h" + +#ifndef NO_USE_TAPE //============================================================================= // eTape::Init @@ -28,6 +33,7 @@ along with this program. If not, see . void eTape::Init() { eInherited::Init(); +#ifdef USE_LEGACY_TAPE_COMPARISON max_pulses = 0; tape_err = 0; @@ -38,6 +44,10 @@ void eTape::Init() tape_infosize = 0; appendable = 0; + +#endif + tape_instance = NULL; + pulse_iterator = NULL; } //============================================================================= // eTape::Reset @@ -66,15 +76,22 @@ void eTape::Stop() //----------------------------------------------------------------------------- bool eTape::Started() const { - return tape.play_pointer != NULL; +#ifdef USE_LEGACY_TAPE_COMPARISON + assert((tape.play_pointer != NULL) == tape.playing); +#endif + return tape.playing; } //============================================================================= // eTape::Inserted //----------------------------------------------------------------------------- bool eTape::Inserted() const { - return tape_image != NULL; +#ifdef USE_LEGACY_TAPE_COMPARISON + assert((tape_image != NULL) == (tape_instance != NULL)); +#endif + return tape_instance != NULL; } +#ifndef USE_HACKED_DEVICE_ABSTRACTION //============================================================================= // eTape::IoRead //----------------------------------------------------------------------------- @@ -82,6 +99,7 @@ bool eTape::IoRead(word port) const { return !(port&1); } +#endif //============================================================================= // eTape::IoRead //----------------------------------------------------------------------------- @@ -89,6 +107,7 @@ void eTape::IoRead(word port, byte* v, int tact) { *v |= TapeBit(tact) & 0x40; } +#ifdef USE_LEGACY_TAPE_COMPARISON //============================================================================= // eTape::FindPulse //----------------------------------------------------------------------------- @@ -119,6 +138,7 @@ void eTape::FindTapeIndex() if(tape.play_pointer >= tape_image + tapeinfo[i].pos) tape.index = i; } +#ifndef USE_MU_SIMPLIFICATIONS //============================================================================= // eTape::FindTapeSizes //----------------------------------------------------------------------------- @@ -134,41 +154,63 @@ void eTape::FindTapeSizes() tapeinfo[i].t_size = sz; } } +#endif +#endif //============================================================================= // eTape::StopTape //----------------------------------------------------------------------------- void eTape::StopTape() { +#ifdef USE_LEGACY_TAPE_COMPARISON FindTapeIndex(); if(tape.play_pointer >= tape.end_of_tape) tape.index = 0; tape.play_pointer = 0; - tape.edge_change = 0x7FFFFFFFFFFFFFFFLL; - tape.tape_bit = -1; +#endif +// tape.edge_change = 0x7FFFFFFFFFFFFFFFLL; + tape.edge_change = std::numeric_limits::max(); + tape.playing = false; + tape.tape_bit = 0xff; +#ifndef NO_USE_FAST_TAPE speccy->CPU()->HandlerStep(NULL); +#endif } //============================================================================= // eTape::ResetTape //----------------------------------------------------------------------------- void eTape::ResetTape() { + pulse_iterator = tape_instance ? tape_instance->reset() : NULL; +#ifdef USE_LEGACY_TAPE_COMPARISON tape.index = 0; tape.play_pointer = 0; +#endif + tape.tape_bit = 0xff; tape.edge_change = 0x7FFFFFFFFFFFFFFFLL; - tape.tape_bit = -1; +#ifndef NO_USE_FAST_TAPE speccy->CPU()->HandlerStep(NULL); +#endif + if (tape_instance) { + pulse_iterator = tape_instance->reset(); + } else { + pulse_iterator = NULL; + } + tape.playing = false; } //============================================================================= // eTape::StartTape //----------------------------------------------------------------------------- void eTape::StartTape() { - if(!tape_image) + if(!Inserted()) return; +#ifdef USE_LEGACY_TAPE_COMPARISON tape.play_pointer = tape_image + tapeinfo[tape.index].pos; tape.end_of_tape = tape_image + tape_imagesize; +#endif tape.edge_change = speccy->T(); - tape.tape_bit = -1; + tape.tape_bit = 0xff; + tape.playing = true; // speccy->CPU()->FastEmul(FastTapeEmul); } //============================================================================= @@ -176,23 +218,29 @@ void eTape::StartTape() //----------------------------------------------------------------------------- void eTape::CloseTape() { +#ifdef USE_LEGACY_TAPE_COMPARISON if(tape_image) { - free(tape_image); + mu_free(tape_image); tape_image = 0; } if(tapeinfo) { - free(tapeinfo); + mu_free(tapeinfo); tapeinfo = 0; } tape.play_pointer = 0; // stop tape tape.index = 0; // rewind tape tape_err = max_pulses = tape_imagesize = tape_infosize = 0; +#endif tape.edge_change = 0x7FFFFFFFFFFFFFFFLL; - tape.tape_bit = -1; + tape.tape_bit = 0xff; + pulse_iterator = NULL; + delete tape_instance; + tape_instance = NULL; } +#ifdef USE_LEGACY_TAPE_COMPARISON #define align_by(a,b) (((dword)(a) + ((b)-1)) & ~((b)-1)) //============================================================================= @@ -239,6 +287,9 @@ void eTape::MakeBlock(const byte* data, dword size, dword pilot_t, dword s1_t, //----------------------------------------------------------------------------- void eTape::Desc(const byte* data, dword size, char* dst) { +#ifdef USE_MU_SIMPLIFICATIONS + dst[0] = 0; +#else byte crc = 0; char prg[10]; dword i; //Alone Coder 0.36.7 @@ -259,6 +310,7 @@ void eTape::Desc(const byte* data, dword size, char* dst) else sprintf(dst, "#%02X block, %d bytes", *data, size - 2); sprintf(dst + strlen(dst), ", crc %s", crc ? "bad" : "ok"); +#endif } //============================================================================= // eTape::AllocInfocell @@ -270,55 +322,53 @@ void eTape::AllocInfocell() tapeinfo[tape_infosize].pos = tape_imagesize; appendable = 0; } -//============================================================================= -// eTape::NamedCell -//----------------------------------------------------------------------------- -void eTape::NamedCell(const void* nm, dword sz) -{ - AllocInfocell(); - if(sz) - memcpy(tapeinfo[tape_infosize].desc, nm, sz), tapeinfo[tape_infosize].desc[sz] - = 0; - else - strcpy(tapeinfo[tape_infosize].desc, (const char*)nm); - tape_infosize++; -} +#endif //============================================================================= // eTape::Open //----------------------------------------------------------------------------- -bool eTape::Open(const char* type, const void* data, size_t data_size) +#ifdef USE_STREAM +bool eTape::Open(const char* type, struct stream *stream) { if(!strcmp(type, "tap")) - return ParseTAP(data, data_size); + return OpenTAP(stream); +#ifndef NO_USE_CSW else if(!strcmp(type, "csw")) - return ParseCSW(data, data_size); + return ParseCSW(stream); +#endif +#ifndef NO_USE_TZX else if(!strcmp(type, "tzx")) - return ParseTZX(data, data_size); + return ParseTZX(stream); +#endif return false; } +#endif //============================================================================= // eTape::ParseTAP //----------------------------------------------------------------------------- -bool eTape::ParseTAP(const void* data, size_t data_size) +bool eTape::OpenTAP(struct stream *stream) { - const byte* ptr = (const byte*)data; CloseTape(); - while(ptr < (const byte*)data + data_size) - { - dword size = Word(ptr); - ptr += 2; - if(!size) - break; - AllocInfocell(); - Desc(ptr, size, tapeinfo[tape_infosize].desc); - tape_infosize++; - MakeBlock(ptr, size, 2168, 667, 735, 855, 1710, (*ptr < 4) ? 8064 - : 3220, 1000); - ptr += size; - } - FindTapeSizes(); - return (ptr == (const byte*)data + data_size); + tape_instance = new eTapeInstanceTAP(stream); + +#ifndef NDEBUG + do { + if (!stream_peek(stream, 1)) break; // nothing left + const uint8_t *size_ptr = stream_peek(stream, 2); + if (!size_ptr) { + return false; + } + int size = Word(size_ptr); + if (!size) break; + if (!stream_skip(stream, size + 2)) { + return false; + } + } while (true); + stream_reset(stream); +#endif + return true; } + +#ifndef NO_USE_CSW //============================================================================= // eTape::ParseCSW //----------------------------------------------------------------------------- @@ -350,6 +400,8 @@ bool eTape::ParseCSW(const void* data, size_t data_size) FindTapeSizes(); return true; } +#endif +#ifndef NO_USE_TZX //============================================================================= // eTape::CreateAppendableBlock //----------------------------------------------------------------------------- @@ -361,197 +413,6 @@ void eTape::CreateAppendableBlock() appendable = 1; } //============================================================================= -// eTape::ParseHardware -//----------------------------------------------------------------------------- -void eTape::ParseHardware(const byte* ptr) -{ - dword n = *ptr++; - if(!n) - return; - NamedCell("- HARDWARE TYPE "); - static const char ids[] = "computer\0" - "ZX Spectrum 16k\0" - "ZX Spectrum 48k, Plus\0" - "ZX Spectrum 48k ISSUE 1\0" - "ZX Spectrum 128k (Sinclair)\0" - "ZX Spectrum 128k +2 (Grey case)\0" - "ZX Spectrum 128k +2A, +3\0" - "Timex Sinclair TC-2048\0" - "Timex Sinclair TS-2068\0" - "Pentagon 128\0" - "Sam Coupe\0" - "Didaktik M\0" - "Didaktik Gama\0" - "ZX-81 or TS-1000 with 1k RAM\0" - "ZX-81 or TS-1000 with 16k RAM or more\0" - "ZX Spectrum 128k, Spanish version\0" - "ZX Spectrum, Arabic version\0" - "TK 90-X\0" - "TK 95\0" - "Byte\0" - "Elwro\0" - "ZS Scorpion\0" - "Amstrad CPC 464\0" - "Amstrad CPC 664\0" - "Amstrad CPC 6128\0" - "Amstrad CPC 464+\0" - "Amstrad CPC 6128+\0" - "Jupiter ACE\0" - "Enterprise\0" - "Commodore 64\0" - "Commodore 128\0" - "\0" - "ext. storage\0" - "Microdrive\0" - "Opus Discovery\0" - "Disciple\0" - "Plus-D\0" - "Rotronics Wafadrive\0" - "TR-DOS (BetaDisk)\0" - "Byte Drive\0" - "Watsford\0" - "FIZ\0" - "Radofin\0" - "Didaktik disk drives\0" - "BS-DOS (MB-02)\0" - "ZX Spectrum +3 disk drive\0" - "JLO (Oliger) disk interface\0" - "FDD3000\0" - "Zebra disk drive\0" - "Ramex Millenia\0" - "Larken\0" - "\0" - "ROM/RAM type add-on\0" - "Sam Ram\0" - "Multiface\0" - "Multiface 128k\0" - "Multiface +3\0" - "MultiPrint\0" - "MB-02 ROM/RAM expansion\0" - "\0" - "sound device\0" - "Classic AY hardware\0" - "Fuller Box AY sound hardware\0" - "Currah microSpeech\0" - "SpecDrum\0" - "AY ACB stereo; Melodik\0" - "AY ABC stereo\0" - "\0" - "joystick\0" - "Kempston\0" - "Cursor, Protek, AGF\0" - "Sinclair 2\0" - "Sinclair 1\0" - "Fuller\0" - "\0" - "mice\0" - "AMX mouse\0" - "Kempston mouse\0" - "\0" - "other controller\0" - "Trickstick\0" - "ZX Light Gun\0" - "Zebra Graphics Tablet\0" - "\0" - "serial port\0" - "ZX Interface 1\0" - "ZX Spectrum 128k\0" - "\0" - "parallel port\0" - "Kempston S\0" - "Kempston E\0" - "ZX Spectrum 128k +2A, +3\0" - "Tasman\0" - "DK'Tronics\0" - "Hilderbay\0" - "INES Printerface\0" - "ZX LPrint Interface 3\0" - "MultiPrint\0" - "Opus Discovery\0" - "Standard 8255 chip with ports 31,63,95\0" - "\0" - "printer\0" - "ZX Printer, Alphacom 32 & compatibles\0" - "Generic Printer\0" - "EPSON Compatible\0" - "\0" - "modem\0" - "VTX 5000\0" - "T/S 2050 or Westridge 2050\0" - "\0" - "digitaiser\0" - "RD Digital Tracer\0" - "DK'Tronics Light Pen\0" - "British MicroGraph Pad\0" - "\0" - "network adapter\0" - "ZX Interface 1\0" - "\0" - "keyboard / keypad\0" - "Keypad for ZX Spectrum 128k\0" - "\0" - "AD/DA converter\0" - "Harley Systems ADC 8.2\0" - "Blackboard Electronics\0" - "\0" - "EPROM Programmer\0" - "Orme Electronics\0" - "\0" - "\0"; - for(dword i = 0; i < n; i++) - { - byte type_n = *ptr++; - byte id_n = *ptr++; - byte value_n = *ptr++; - const char* type = ids, *id, *value; - dword j; //Alone Coder 0.36.7 - for(/*dword*/j = 0; j < type_n; j++) - { - if(!*type) - break; - while(Word((byte*)type)) - type++; - type += 2; - } - if(!*type) - type = id = "??"; - else - { - id = type + strlen(type) + 1; - for(j = 0; j < id_n; j++) - { - if(!*id) - { - id = "??"; - break; - } - id += strlen(id) + 1; - } - } - switch(value_n) - { - case 0: - value = "compatible with"; - break; - case 1: - value = "uses"; - break; - case 2: - value = "compatible, but doesn't use"; - break; - case 3: - value = "incompatible with"; - break; - default: - value = "??"; - } - char bf[512]; - sprintf(bf, "%s %s: %s", value, type, id); - NamedCell(bf); - } - NamedCell("-"); -} -//============================================================================= // eTape::ParseTZX //----------------------------------------------------------------------------- bool eTape::ParseTZX(const void* data, size_t data_size) @@ -567,69 +428,78 @@ bool eTape::ParseTZX(const void* data, size_t data_size) { switch(*ptr++) { - case 0x10: // normal block - AllocInfocell(); - size = Word(ptr + 2); - pause = Word(ptr); - ptr += 4; - Desc(ptr, size, tapeinfo[tape_infosize].desc); - tape_infosize++; - MakeBlock(ptr, size, 2168, 667, 735, 855, 1710, (*ptr < 4) ? 8064 - : 3220, pause); - ptr += size; - break; - case 0x11: // turbo block - AllocInfocell(); - size = 0xFFFFFF & Dword(ptr + 0x0F); - Desc(ptr + 0x12, size, tapeinfo[tape_infosize].desc); - tape_infosize++; - MakeBlock(ptr + 0x12, size, Word(ptr), Word(ptr + 2), - Word(ptr + 4), Word(ptr + 6), Word(ptr + 8), - Word(ptr + 10), Word(ptr + 13), ptr[12]); - // todo: test used bits - ptr+12 - ptr += size + 0x12; - break; - case 0x12: // pure tone - CreateAppendableBlock(); - pl = FindPulse(Word(ptr)); - n = Word(ptr + 2); - Reserve(n); - for(i = 0; i < n; i++) - tape_image[tape_imagesize++] = pl; - ptr += 4; - break; - case 0x13: // sequence of pulses of different lengths - CreateAppendableBlock(); - n = *ptr++; - Reserve(n); - for(i = 0; i < n; i++, ptr += 2) - tape_image[tape_imagesize++] = FindPulse(Word(ptr)); - break; - case 0x14: // pure data block - CreateAppendableBlock(); - size = 0xFFFFFF & Dword(ptr + 7); - MakeBlock(ptr + 0x0A, size, 0, 0, 0, Word(ptr), - Word(ptr + 2), -1, Word(ptr + 5), ptr[4]); - ptr += size + 0x0A; - break; - case 0x15: // direct recording - size = 0xFFFFFF & Dword(ptr + 5); - t0 = Word(ptr); - pause = Word(ptr + 2); - last = ptr[4]; - NamedCell("direct recording"); - ptr += 8; - pl = 0; - n = 0; - for(i = 0; i < size; i++) // count number of pulses - for(j = 0x80; j; j >>= 1) - if((ptr[i] ^ pl) & j) - n++, pl ^= -1; - t = 0; - pl = 0; - Reserve(n + 2); - for(i = 1; i < size; i++, ptr++) // find pulses - for(j = 0x80; j; j >>= 1) + case 0x10: // normal block + AllocInfocell(); + size = Word(ptr + 2); + pause = Word(ptr); + ptr += 4; + tape_infosize++; + MakeBlock(ptr, size, 2168, 667, 735, 855, 1710, (*ptr < 4) ? 8064 + : 3220, pause); + ptr += size; + break; + case 0x11: // turbo block + AllocInfocell(); + size = 0xFFFFFF & Dword(ptr + 0x0F); + tape_infosize++; + MakeBlock(ptr + 0x12, size, Word(ptr), Word(ptr + 2), + Word(ptr + 4), Word(ptr + 6), Word(ptr + 8), + Word(ptr + 10), Word(ptr + 13), ptr[12]); + // todo: test used bits - ptr+12 + ptr += size + 0x12; + break; + case 0x12: // pure tone + CreateAppendableBlock(); + pl = FindPulse(Word(ptr)); + n = Word(ptr + 2); + Reserve(n); + for(i = 0; i < n; i++) + tape_image[tape_imagesize++] = pl; + ptr += 4; + break; + case 0x13: // sequence of pulses of different lengths + CreateAppendableBlock(); + n = *ptr++; + Reserve(n); + for(i = 0; i < n; i++, ptr += 2) + tape_image[tape_imagesize++] = FindPulse(Word(ptr)); + break; + case 0x14: // pure data block + CreateAppendableBlock(); + size = 0xFFFFFF & Dword(ptr + 7); + MakeBlock(ptr + 0x0A, size, 0, 0, 0, Word(ptr), + Word(ptr + 2), -1, Word(ptr + 5), ptr[4]); + ptr += size + 0x0A; + break; + case 0x15: // direct recording + size = 0xFFFFFF & Dword(ptr + 5); + t0 = Word(ptr); + pause = Word(ptr + 2); + last = ptr[4]; + NamedCell("direct recording"); + ptr += 8; + pl = 0; + n = 0; + for(i = 0; i < size; i++) // count number of pulses + for(j = 0x80; j; j >>= 1) + if((ptr[i] ^ pl) & j) + n++, pl ^= -1; + t = 0; + pl = 0; + Reserve(n + 2); + for(i = 1; i < size; i++, ptr++) // find pulses + for(j = 0x80; j; j >>= 1) + { + t += t0; + if((*ptr ^ pl) & j) + { + tape_image[tape_imagesize++] = FindPulse(t); + pl ^= -1; + t = 0; + } + } + // find pulses - last byte + for(j = 0x80; j != (byte)(0x80 >> last); j >>= 1) { t += t0; if((*ptr ^ pl) & j) @@ -639,233 +509,215 @@ bool eTape::ParseTZX(const void* data, size_t data_size) t = 0; } } - // find pulses - last byte - for(j = 0x80; j != (byte)(0x80 >> last); j >>= 1) - { - t += t0; - if((*ptr ^ pl) & j) - { - tape_image[tape_imagesize++] = FindPulse(t); - pl ^= -1; - t = 0; + ptr++; + tape_image[tape_imagesize++] = FindPulse(t); // last pulse ??? + if(pause) + tape_image[tape_imagesize++] = FindPulse(pause * 3500); + break; + case 0x20: // pause (silence) or 'stop the tape' command + pause = Word(ptr); + sprintf(nm, pause ? "pause %d ms" : "stop the tape", pause); + NamedCell(nm); + Reserve(2); + ptr += 2; + if(!pause) + { // at least 1ms pulse as specified in TZX 1.13 + tape_image[tape_imagesize++] = FindPulse(3500); + pause = -1; } - } - ptr++; - tape_image[tape_imagesize++] = FindPulse(t); // last pulse ??? - if(pause) - tape_image[tape_imagesize++] = FindPulse(pause * 3500); - break; - case 0x20: // pause (silence) or 'stop the tape' command - pause = Word(ptr); - sprintf(nm, pause ? "pause %d ms" : "stop the tape", pause); - NamedCell(nm); - Reserve(2); - ptr += 2; - if(!pause) - { // at least 1ms pulse as specified in TZX 1.13 - tape_image[tape_imagesize++] = FindPulse(3500); - pause = -1; - } - else - pause *= 3500; - tape_image[tape_imagesize++] = FindPulse(pause); - break; - case 0x21: // group start - n = *ptr++; - NamedCell(ptr, n); - ptr += n; - appendable = 1; - break; - case 0x22: // group end - break; - case 0x23: // jump to block - NamedCell("* jump"); - ptr += 2; - break; - case 0x24: // loop start - loop_n = Word(ptr); - loop_p = tape_imagesize; - ptr += 2; - break; - case 0x25: // loop end - if(!loop_n) + else + pause *= 3500; + tape_image[tape_imagesize++] = FindPulse(pause); break; - size = tape_imagesize - loop_p; - Reserve((loop_n - 1) * size); - for(i = 1; i < loop_n; i++) - memcpy(tape_image + loop_p + i * size, tape_image + loop_p, - size); - tape_imagesize += (loop_n - 1) * size; - loop_n = 0; - break; - case 0x26: // call - NamedCell("* call"); - ptr += 2 + 2 * Word(ptr); - break; - case 0x27: // ret - NamedCell("* return"); - break; - case 0x28: // select block - sprintf(nm, "* choice: "); - n = ptr[2]; - p = ptr + 3; - for(i = 0; i < n; i++) - { - if(i) - strcat(nm, " / "); - char *q = nm + strlen(nm); - size = *(p + 2); - memcpy(q, p + 3, size); - q[size] = 0; - p += size + 3; - } - NamedCell(nm); - ptr += 2 + Word(ptr); - break; - case 0x2A: // stop if 48k - NamedCell("* stop if 48K"); - ptr += 4 + Dword(ptr); - break; - case 0x30: // text description - n = *ptr++; - NamedCell(ptr, n); - ptr += n; - appendable = 1; - break; - case 0x31: // message block - NamedCell("- MESSAGE BLOCK "); - end = ptr + 2 + ptr[1]; - pl = *end; - *end = 0; - for(p = ptr + 2; p < end; p++) - if(*p == 0x0D) - *p = 0; - for(p = ptr + 2; p < end; p += strlen((char*)p) + 1) - NamedCell(p); - *end = pl; - ptr = end; - NamedCell("-"); - break; - case 0x32: // archive info - NamedCell("- ARCHIVE INFO "); - p = ptr + 3; - for(i = 0; i < ptr[2]; i++) - { - const char *info; - switch(*p++) - { - case 0: - info = "Title"; - break; - case 1: - info = "Publisher"; - break; - case 2: - info = "Author"; - break; - case 3: - info = "Year"; - break; - case 4: - info = "Language"; - break; - case 5: - info = "Type"; - break; - case 6: - info = "Price"; - break; - case 7: - info = "Protection"; - break; - case 8: - info = "Origin"; - break; - case 0xFF: - info = "Comment"; - break; - default: - info = "info"; + case 0x21: // group start + n = *ptr++; + NamedCell(ptr, n); + ptr += n; + appendable = 1; + break; + case 0x22: // group end + break; + case 0x23: // jump to block + NamedCell("* jump"); + ptr += 2; + break; + case 0x24: // loop start + loop_n = Word(ptr); + loop_p = tape_imagesize; + ptr += 2; + break; + case 0x25: // loop end + if(!loop_n) break; + size = tape_imagesize - loop_p; + Reserve((loop_n - 1) * size); + for(i = 1; i < loop_n; i++) + memcpy(tape_image + loop_p + i * size, tape_image + loop_p, + size); + tape_imagesize += (loop_n - 1) * size; + loop_n = 0; + break; + case 0x26: // call + NamedCell("* call"); + ptr += 2 + 2 * Word(ptr); + break; + case 0x27: // ret + NamedCell("* return"); + break; + case 0x28: // select block + sprintf(nm, "* choice: "); + n = ptr[2]; + p = ptr + 3; + for(i = 0; i < n; i++) + { + if(i) + strcat(nm, " / "); + char *q = nm + strlen(nm); + size = *(p + 2); + memcpy(q, p + 3, size); + q[size] = 0; + p += size + 3; } - dword size = *p + 1; - char tmp = p[size]; - p[size] = 0; - sprintf(nm, "%s: %s", info, p + 1); - p[size] = tmp; - p += size; NamedCell(nm); - } - NamedCell("-"); - ptr += 2 + Word(ptr); - break; - case 0x33: // hardware type - ParseHardware(ptr); - ptr += 1 + 3 * *ptr; - break; - case 0x34: // emulation info - NamedCell("* emulation info"); - ptr += 8; - break; - case 0x35: // custom info - if(!memcmp(ptr, "POKEs ", 16)) - { - NamedCell("- POKEs block "); - NamedCell(ptr + 0x15, ptr[0x14]); - p = ptr + 0x15 + ptr[0x14]; - n = *p++; - for(i = 0; i < n; i++) + ptr += 2 + Word(ptr); + break; + case 0x2A: // stop if 48k + NamedCell("* stop if 48K"); + ptr += 4 + Dword(ptr); + break; + case 0x30: // text description + n = *ptr++; + NamedCell(ptr, n); + ptr += n; + appendable = 1; + break; + case 0x31: // message block + NamedCell("- MESSAGE BLOCK "); + end = ptr + 2 + ptr[1]; + pl = *end; + *end = 0; + for(p = ptr + 2; p < end; p++) + if(*p == 0x0D) + *p = 0; + for(p = ptr + 2; p < end; p += strlen((char*)p) + 1) + NamedCell(p); + *end = pl; + ptr = end; + NamedCell("-"); + break; + case 0x32: // archive info + NamedCell("- ARCHIVE INFO "); + p = ptr + 3; + for(i = 0; i < ptr[2]; i++) { - NamedCell(p + 1, *p); - p += *p + 1; - t = *p++; - strcpy(nm, "POKE "); - for(j = 0; j < t; j++) + const char *info; + switch(*p++) { - sprintf(nm + strlen(nm), "%d,", Word(p + 1)); - sprintf(nm + strlen(nm), *p & 0x10 ? "nn" : "%d", - *(byte*)(p + 3)); - if(!(*p & 0x08)) - sprintf(nm + strlen(nm), "(page %d)", *p & 7); - strcat(nm, "; "); - p += 5; + case 0: + info = "Title"; + break; + case 1: + info = "Publisher"; + break; + case 2: + info = "Author"; + break; + case 3: + info = "Year"; + break; + case 4: + info = "Language"; + break; + case 5: + info = "Type"; + break; + case 6: + info = "Price"; + break; + case 7: + info = "Protection"; + break; + case 8: + info = "Origin"; + break; + case 0xFF: + info = "Comment"; + break; + default: + info = "info"; + break; } + dword size = *p + 1; + char tmp = p[size]; + p[size] = 0; + sprintf(nm, "%s: %s", info, p + 1); + p[size] = tmp; + p += size; NamedCell(nm); } - nm[0] = '-'; - nm[1] = 0; - nm[2] = 0; - nm[3] = 0; - } - else - sprintf(nm, "* custom info: %s", ptr), nm[15 + 16] = 0; - NamedCell(nm); - ptr += 0x14 + Dword(ptr + 0x10); - break; - case 0x40: // snapshot - NamedCell("* snapshot"); - ptr += 4 + (0xFFFFFF & Dword(ptr + 1)); - break; - case 0x5A: // 'Z' - ptr += 9; - break; - default: - ptr += data_size; + NamedCell("-"); + ptr += 2 + Word(ptr); + break; + case 0x33: // hardware type + ptr += 1 + 3 * *ptr; + break; + case 0x34: // emulation info + NamedCell("* emulation info"); + ptr += 8; + break; + case 0x35: // custom info + if(!memcmp(ptr, "POKEs ", 16)) + { + NamedCell("- POKEs block "); + NamedCell(ptr + 0x15, ptr[0x14]); + p = ptr + 0x15 + ptr[0x14]; + n = *p++; + for(i = 0; i < n; i++) + { + NamedCell(p + 1, *p); + p += *p + 1; + t = *p++; + strcpy(nm, "POKE "); + for(j = 0; j < t; j++) + { + sprintf(nm + strlen(nm), "%d,", Word(p + 1)); + sprintf(nm + strlen(nm), *p & 0x10 ? "nn" : "%d", + *(byte*)(p + 3)); + if(!(*p & 0x08)) + sprintf(nm + strlen(nm), "(page %d)", *p & 7); + strcat(nm, "; "); + p += 5; + } + NamedCell(nm); + } + nm[0] = '-'; + nm[1] = 0; + nm[2] = 0; + nm[3] = 0; + } + else + sprintf(nm, "* custom info: %s", ptr), nm[15 + 16] = 0; + NamedCell(nm); + ptr += 0x14 + Dword(ptr + 0x10); + break; + case 0x40: // snapshot + NamedCell("* snapshot"); + ptr += 4 + (0xFFFFFF & Dword(ptr + 1)); + break; + case 0x5A: // 'Z' + ptr += 9; + break; + default: + ptr += data_size; } } - for(i = 0; i < tape_infosize; i++) - { - if(tapeinfo[i].desc[0] == '*' && tapeinfo[i].desc[1] == ' ') - strcat(tapeinfo[i].desc, " [UNSUPPORTED]"); - if(*tapeinfo[i].desc == '-') - while(strlen(tapeinfo[i].desc) < sizeof(tapeinfo[i].desc) - 1) - strcat(tapeinfo[i].desc, "-"); - } + if(tape_imagesize && tape_pulse[tape_image[tape_imagesize - 1]] < 350000) Reserve(1), tape_image[tape_imagesize++] = FindPulse(350000); // small pause [rqd for 3ddeathchase] FindTapeSizes(); return (ptr == (const byte*)data + data_size); } +#endif //============================================================================= // eTape::TapeBit //----------------------------------------------------------------------------- @@ -877,277 +729,348 @@ byte eTape::TapeBit(int tact) dword t = (dword)(tape.edge_change - speccy->T()); if((int)t >= 0) { - const short vol = 1000; - short mono = tape.tape_bit ? vol : 0; +// const short vol = 4096;//1000; // a little loader than the default +// short mono = tape.tape_bit ? vol : 0; +// mono = (mono * 182 * xPlatform::OpTapeVolume()) >> 16; //(0->255) + // max out at 160/256 + uint8_t mono = tape.tape_bit ? (xPlatform::OpTapeVolume() << 4) : 0; Update(tact, mono, mono); } dword pulse; - tape.tape_bit ^= -1; - if(tape.play_pointer >= tape.end_of_tape || - (pulse = tape_pulse[*tape.play_pointer++]) == (dword)-1) - StopTape(); - else + tape.tape_bit ^= 0xff; + if ((pulse = NextPulseOrStop()) != (dword)-1) { tape.edge_change += pulse; + } } return (byte)tape.tape_bit; } +dword eTape::NextPulseOrStop() +{ + dword pulse; + assert(pulse_iterator && tape.playing); + pulse = (pulse_iterator && tape.playing) ? pulse_iterator->next_pulse_length() : -1; +#ifdef USE_LEGACY_TAPE_COMPARISON + dword pulse2; + if(tape.play_pointer >= tape.end_of_tape || + (pulse2 = tape_pulse[*tape.play_pointer++]) == (dword)-1) { + assert(pulse == -1); + } + else + { + assert(pulse == pulse2); + } +#endif + if (pulse == (dword)-1) + { + StopTape(); + } + return pulse; +} + +void eTape::SkipPulse() { + assert(pulse_iterator); + pulse_iterator->next_pulse_length(); +#ifdef USE_LEGACY_TAPE_COMPARISON + tape.play_pointer++; +#endif +} + +#ifndef NO_USE_FAST_TAPE namespace xZ80 { //***************************************************************************** // eZ80_FastTape //----------------------------------------------------------------------------- -class eZ80_FastTape: public xZ80::eZ80 -{ -public: - void StepEdge(); - void StepTrap(); - void Step() + class eZ80_FastTape: public xZ80::eZ80 { - StepTrap(); - StepEdge(); - } -}; + public: + void StepEdge(); + void StepTrap(); + void Step() + { +// static int foo = 0; +// printf("Tape step %d %04x\n", foo++, get_caller_pc()); + StepTrap(); + StepEdge(); + } + void FindEdge(byte mask, int tstates) { + FindEdge(mask, tstates, 0xff, 1); + } + void FindEdge(byte mask, int tstates, byte endb, int delta_b); + bool CompareMemory(word addr, byte *cmp, int len) + { + while (len--) + { + if (*(cmp++) != memory->Read(addr++)) + return false; + } + return true; + } + }; + //============================================================================= // eZ80_FastTape::StepEdge //----------------------------------------------------------------------------- -void eZ80_FastTape::StepEdge() -{ - byte p0 = memory->Read(pc + 0); - byte p1 = memory->Read(pc + 1); - byte p2 = memory->Read(pc + 2); - byte p3 = memory->Read(pc + 3); - if(p0 == 0x3D && p1 == 0x20 && p2 == 0xFD && p3 == 0xA7) - { // dec a:jr nz,$-1 - t += ((byte)(a - 1)) * 16; - a = 1; - return; - } - if(p0 == 0x10 && p1 == 0xFE) - { // djnz $ - t += ((byte)(b - 1)) * 13; - b = 1; - return; - } - if(p0 == 0x3D && p1 == 0xC2 && pc == dword(p3) * 0x100 + p2) - { // dec a:jp nz,$-1 - t += ((byte)(a - 1)) * 14; - a = 1; - return; - } - if(p0 == 0x04 && p1 == 0xC8 && p2 == 0x3E) + void eZ80_FastTape::StepEdge() { - byte p04 = memory->Read(pc + 4); - byte p05 = memory->Read(pc + 5); - byte p06 = memory->Read(pc + 6); - byte p07 = memory->Read(pc + 7); - byte p08 = memory->Read(pc + 8); - byte p09 = memory->Read(pc + 9); - byte p10 = memory->Read(pc + 10); - byte p11 = memory->Read(pc + 11); - byte p12 = memory->Read(pc + 12); - if(p04 == 0xDB && p05 == 0xFE && p06 == 0x1F && p07 == 0xD0 && p08 - == 0xA9 && p09 == 0xE6 && p10 == 0x20 && p11 == 0x28 && p12 - == 0xF3) - { // find edge (rom routine) - eTape* tape = devices->Get(); - for(;;) - { - if(b == 0xFF) - return; - if((tape->TapeBit(T()) ^ c) & 0x20) - return; - b++; - t += 59; - } + const word pc = get_caller_pc(); + byte p0 = memory->Read(pc + 0); + byte p1 = memory->Read(pc + 1); + byte p2 = memory->Read(pc + 2); + byte p3 = memory->Read(pc + 3); + if(p0 == 0x3D && p1 == 0x20 && p2 == 0xFD && p3 == 0xA7) + { // dec a:jr nz,$-1 + delta_caller_t(((byte)(get_caller_a() - 1)) * 16); + set_caller_a(1); + return; } - if(p04 == 0xDB && p05 == 0xFE && p06 == 0xCB && p07 == 0x1F && p08 - == 0xA9 && p09 == 0xE6 && p10 == 0x20 && p11 == 0x28 && p12 - == 0xF3) - { // rra,ret nc => rr a (popeye2) - eTape* tape = devices->Get(); - for(;;) - { - if(b == 0xFF) - return; - if((tape->TapeBit(T()) ^ c) & 0x20) - return; - b++; - t += 58; - } + if(p0 == 0x10 && p1 == 0xFE) + { // djnz $ + delta_caller_t(((byte)(get_caller_b() - 1)) * 13); + set_caller_b(1); + return; } - if(p04 == 0xDB && p05 == 0xFE && p06 == 0x1F && p07 == 0x00 && p08 - == 0xA9 && p09 == 0xE6 && p10 == 0x20 && p11 == 0x28 && p12 - == 0xF3) - { // ret nc nopped (some bleep loaders) - eTape* tape = devices->Get(); - for(;;) - { - if(b == 0xFF) - return; - if((tape->TapeBit(T()) ^ c) & 0x20) - return; - b++; - t += 58; + if(p0 == 0x3D && p1 == 0xC2 && pc == dword(p3) * 0x100 + p2) + { // dec a:jp nz,$-1 + delta_caller_t(((byte)(get_caller_a() - 1)) * 14); + set_caller_a(1); + return; + } + if(p0 == 0x04 && p1 == 0xC8 && p2 == 0x3E) + { + byte p04 = memory->Read(pc + 4); + byte p05 = memory->Read(pc + 5); + byte p06 = memory->Read(pc + 6); + byte p07 = memory->Read(pc + 7); + byte p08 = memory->Read(pc + 8); + byte p09 = memory->Read(pc + 9); + byte p10 = memory->Read(pc + 10); + byte p11 = memory->Read(pc + 11); + byte p12 = memory->Read(pc + 12); + if(p04 == 0xDB && p05 == 0xFE && p06 == 0x1F && p07 == 0xD0 && p08 + == 0xA9 && p09 == 0xE6 && p10 == 0x20 && p11 == 0x28 && p12 + == 0xF3) + { // find edge (rom routine) + FindEdge(0x20, 59); + return; + } + if(p04 == 0xDB && p05 == 0xFE && p06 == 0xCB && p07 == 0x1F && p08 + == 0xA9 && p09 == 0xE6 && p10 == 0x20 && p11 == 0x28 && p12 + == 0xF3) + { // rra,ret nc => rr a (popeye2) + FindEdge(0x20, 58); + return; + } + if(p04 == 0xDB && p05 == 0xFE && p06 == 0x1F && p07 == 0x00 && p08 + == 0xA9 && p09 == 0xE6 && p10 == 0x20 && p11 == 0x28 && p12 + == 0xF3) + { // ret nc nopped (some bleep loaders) + FindEdge(0x20, 58); + return; + } + if(p04 == 0xDB && p05 == 0xFE && p06 == 0xA9 && p07 == 0xE6 && p08 + == 0x40 && p09 == 0xD8 && p10 == 0x00 && p11 == 0x28 && p12 + == 0xF3) + { // no rra, no break check (rana rama) + FindEdge(0x40, 59); + return; + } + if(p04 == 0xDB && p05 == 0xFE && p06 == 0x1F && p07 == 0xA9 && p08 + == 0xE6 && p09 == 0x20 && p10 == 0x28 && p11 == 0xF4) + { // ret nc skipped: routine without BREAK checking (ZeroMusic & JSW) + FindEdge(0x20, 54); + return; } } - if(p04 == 0xDB && p05 == 0xFE && p06 == 0xA9 && p07 == 0xE6 && p08 - == 0x40 && p09 == 0xD8 && p10 == 0x00 && p11 == 0x28 && p12 - == 0xF3) - { // no rra, no break check (rana rama) - eTape* tape = devices->Get(); - for(;;) - { - if(b == 0xFF) - return; - if((tape->TapeBit(T()) ^ c) & 0x40) - return; - b++; - t += 59; + if(p0 == 0x04 && p1 == 0x20 && p2 == 0x03) + { + byte p06 = memory->Read(pc + 6); + byte p08 = memory->Read(pc + 8); + byte p09 = memory->Read(pc + 9); + byte p10 = memory->Read(pc + 10); + byte p11 = memory->Read(pc + 11); + byte p12 = memory->Read(pc + 12); + byte p13 = memory->Read(pc + 13); + byte p14 = memory->Read(pc + 14); + if(p06 == 0xDB && p08 == 0x1F && p09 == 0xC8 && p10 == 0xA9 && p11 + == 0xE6 && p12 == 0x20 && p13 == 0x28 && p14 == 0xF1) + { // find edge from Donkey Kong + FindEdge(0x20, 59); + return; } } - if(p04 == 0xDB && p05 == 0xFE && p06 == 0x1F && p07 == 0xA9 && p08 - == 0xE6 && p09 == 0x20 && p10 == 0x28 && p11 == 0xF4) - { // ret nc skipped: routine without BREAK checking (ZeroMusic & JSW) - eTape* tape = devices->Get(); - for(;;) - { - if(b == 0xFF) - return; - if((tape->TapeBit(T()) ^ c) & 0x20) - return; - b++; - t += 54; + if(p0 == 0x3E && p2 == 0xDB && p3 == 0xFE) + { + byte p04 = memory->Read(pc + 4); + byte p05 = memory->Read(pc + 5); + byte p06 = memory->Read(pc + 6); + byte p07 = memory->Read(pc + 7); + byte p09 = memory->Read(pc + 9); + byte p10 = memory->Read(pc + 10); + byte p11 = memory->Read(pc + 11); + if(p04 == 0xA9 && p05 == 0xE6 && p06 == 0x40 && p07 == 0x20 && p09 + == 0x05 && p10 == 0x20 && p11 == 0xF4) + { // lode runner + FindEdge(0x40, 52, 1, -1); } } } - if(p0 == 0x04 && p1 == 0x20 && p2 == 0x03) + + void eZ80_FastTape::FindEdge(byte mask, int tstates, byte end_b, int delta_b) { - byte p06 = memory->Read(pc + 6); - byte p08 = memory->Read(pc + 8); - byte p09 = memory->Read(pc + 9); - byte p10 = memory->Read(pc + 10); - byte p11 = memory->Read(pc + 11); - byte p12 = memory->Read(pc + 12); - byte p13 = memory->Read(pc + 13); - byte p14 = memory->Read(pc + 14); - if(p06 == 0xDB && p08 == 0x1F && p09 == 0xC8 && p10 == 0xA9 && p11 - == 0xE6 && p12 == 0x20 && p13 == 0x28 && p14 == 0xF1) - { // find edge from Donkey Kong - eTape* tape = devices->Get(); - for(;;) - { - if(b == 0xFF) - return; - if((tape->TapeBit(T()) ^ c) & 0x20) - return; - b++; - t += 59; - } + eTape *tape = devices->Get(); + for (;;) + { + if (get_caller_b() == end_b) + return; + if ((tape->TapeBit(T()) ^ get_caller_c()) & mask) + return; + set_caller_b(get_caller_b() + delta_b); + delta_caller_t(tstates); } } - if(p0 == 0x3E && p2 == 0xDB && p3 == 0xFE) + +//============================================================================= +// eZ80_FastTape::StepTrap +//----------------------------------------------------------------------------- + void eZ80_FastTape::StepTrap() { - byte p04 = memory->Read(pc + 4); - byte p05 = memory->Read(pc + 5); - byte p06 = memory->Read(pc + 6); - byte p07 = memory->Read(pc + 7); - byte p09 = memory->Read(pc + 9); - byte p10 = memory->Read(pc + 10); - byte p11 = memory->Read(pc + 11); - if(p04 == 0xA9 && p05 == 0xE6 && p06 == 0x40 && p07 == 0x20 && p09 - == 0x05 && p10 == 0x20 && p11 == 0xF4) - { // lode runner - eTape* tape = devices->Get(); - for(;;) +#if 1 + const word pc = get_caller_pc(); + if ((pc & 0xFFFF) != 0x056B) return; + eTape* tape = devices->Get(); + dword pulse; + do + { + if ((pulse = tape->NextPulseOrStop()) == (dword)-1) { + return; + } + } + while(pulse > 770); + tape->SkipPulse(); + + // loading header + byte l = 0; + for(dword bit = 0x80; bit; bit >>= 1) + { + if ((pulse = tape->NextPulseOrStop()) == (dword)-1) { + set_caller_l(l); + set_caller_pc(0x05E2); + return; + } + l |= (pulse > 1240) ? bit : 0; + tape->SkipPulse(); + } + + // loading data + word de = get_caller_de(); + do + { + l = 0; + for(dword bit = 0x80; bit; bit >>= 1) { - if(b == 1) + if ((pulse = tape->NextPulseOrStop()) == (dword)-1) { + set_caller_l(l); + set_caller_pc(0x05E2); return; - if((tape->TapeBit(T()) ^ c) & 0x40) - return; - t += 52; - b--; + } + l |= (pulse > 1240) ? bit : 0; + tape->SkipPulse(); } + word ix = get_caller_ix(); + memory->Write(ix++, l); + set_caller_ix(ix); + --de; + set_caller_de(de); } - } -} -//============================================================================= -// eZ80_FastTape::StepTrap -//----------------------------------------------------------------------------- -void eZ80_FastTape::StepTrap() -{ + while(de & 0xFFFF); + + // loading CRC + l = 0; + for(dword bit = 0x80; bit; bit >>= 1) + { + if ((pulse = tape->NextPulseOrStop()) == (dword)-1) { + set_caller_l(l); + set_caller_pc(0x05E2); + return; + } + l |= (pulse > 1240) ? bit : 0; + tape->SkipPulse(); + } + set_caller_pc(0x05DF); + set_caller_flag(CF); + set_caller_bc(0xB001); + set_caller_h(0); + set_caller_l(l); +#endif +#if 0 + const word pc = get_caller_pc(); if((pc & 0xFFFF) != 0x056B) return; eTape* tape = devices->Get(); dword pulse; do { - if(tape->tape.play_pointer >= tape->tape.end_of_tape || - (pulse = tape->tape_pulse[*tape->tape.play_pointer++]) == (dword)-1) - { - tape->Stop(); + if ((pulse = tape->NextPulseOrStop()) == (dword)-1) { return; } } while(pulse > 770); - ++tape->tape.play_pointer; + tape->SkipPulse(); // loading header - l = 0; + set_caller_l(0); for(dword bit = 0x80; bit; bit >>= 1) { - if(tape->tape.play_pointer >= tape->tape.end_of_tape || - (pulse = tape->tape_pulse[*tape->tape.play_pointer++]) == (dword)-1) - { - tape->Stop(); - pc = 0x05E2; + if ((pulse = tape->NextPulseOrStop()) == (dword)-1) { + set_caller_pc(0x05E2); return; } - l |= (pulse > 1240) ? bit : 0; - ++tape->tape.play_pointer; + set_caller_l(get_caller_l() | (pulse > 1240) ? bit : 0); + tape->SkipPulse(); } // loading data do { - l = 0; + set_caller_l(0); for(dword bit = 0x80; bit; bit >>= 1) { - if(tape->tape.play_pointer >= tape->tape.end_of_tape || - (pulse = tape->tape_pulse[*tape->tape.play_pointer++]) == (dword)-1) - { - tape->Stop(); - pc = 0x05E2; + if ((pulse = tape->NextPulseOrStop()) == (dword)-1) { + set_caller_pc(0x05E2); return; } - l |= (pulse > 1240) ? bit : 0; - ++tape->tape.play_pointer; + set_caller_l(get_caller_l() | (pulse > 1240) ? bit : 0); + tape->SkipPulse(); } - memory->Write(ix++, l); - --de; + word ix = get_caller_ix(); + memory->Write(ix, get_caller_l()); + set_caller_ix(ix + 1); + set_caller_de(get_caller_de()-1); } - while(de & 0xFFFF); + while(get_caller_de() & 0xFFFF); // loading CRC - l = 0; + set_caller_l(0); for(dword bit = 0x80; bit; bit >>= 1) { - if(tape->tape.play_pointer >= tape->tape.end_of_tape || - (pulse = tape->tape_pulse[*tape->tape.play_pointer++]) == (dword)-1) - { - tape->Stop(); - pc = 0x05E2; + if ((pulse = tape->NextPulseOrStop()) == (dword)-1) { + set_caller_pc(0x05E2); return; } - l |= (pulse > 1240) ? bit : 0; - ++tape->tape.play_pointer; + set_caller_l(get_caller_l() | (pulse > 1240) ? bit : 0); + tape->SkipPulse(); } - pc = 0x05DF; - f |= CF; - bc = 0xB001; - h = 0; -} + set_caller_pc(0x05DF); + set_caller_f(get_caller_f() | CF); + set_caller_bc(0xB001); + set_caller_h(0); + +#endif + } + } //namespace xZ80 @@ -1161,3 +1084,5 @@ static class eFastTapeEmul : public xZ80::eZ80::eHandlerStep } fte; xZ80::eZ80::eHandlerStep* fast_tape_emul = &fte; +#endif +#endif \ No newline at end of file diff --git a/devices/input/tape.h b/devices/input/tape.h index 7e692c4..5419f2f 100644 --- a/devices/input/tape.h +++ b/devices/input/tape.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,74 +20,310 @@ along with this program. If not, see . #ifndef __TAPE_H__ #define __TAPE_H__ +#ifndef NO_USE_TAPE +#include "../../std.h" #include "../sound/device_sound.h" #include "../../z80/z80.h" +#include "stream.h" #pragma once +//#define USE_LEGACY_TAPE_COMPARISON class eSpeccy; namespace xZ80 { class eZ80_FastTape; } +class eTapePulseIterator { +public: + // return -1 for none + virtual int32_t next_pulse_length() = 0; +}; + +class eTapeBlockPulseIterator : public eTapePulseIterator { +public: + static const int CACHE_SIZE = 32; + eTapeBlockPulseIterator() { set_needs_reset(); } + + void set_needs_reset() { + state = DONE; + } + + inline void reset(struct stream *stream, dword size, dword pilot_t, dword s1_t, + dword s2_t, dword zero_t, dword one_t, dword pilot_len, dword pause, + byte last = 8) { + this->stream = stream; + this->size = this->remaining_bytes = size; + this->pilot_t = pilot_t; + this->s1_t = s1_t; + this->s2_t = s2_t; + this->zero_t = zero_t; + this->one_t = one_t; + this->pilot_len = pilot_len; + this->pause = pause; + this->last = last; + this->state = RESET; + recache(); + } + virtual int32_t next_pulse_length() { + while (true) { + switch (state) { + case RESET: + if (pilot_len != (dword)-1) { + counter = pilot_len; + state = PILOT; + } else { + state = DATA; + counter = 0; + } + break; + case DONE: + return -1; + case PILOT: + if (counter--) { + return pilot_t; + } else { + state = S1; + } + break; + case S1: + state = S2; + return s1_t; + case S2: + state = DATA; + counter = 0; + intra_byte = 0x180; + return s2_t; + case DATA: + if (counter < size) { + int32_t len = data_cache[data_cache_pos] & intra_byte ? one_t : zero_t; + if (intra_byte & 0x100) { + // need second half of pulse + intra_byte = intra_byte & 0xff; + } else + { + intra_byte >>= 1; + if (counter == size -1) { + if (intra_byte == (0x80 >> last)) { + state = END_PAUSE; + } + } + if (!intra_byte) { + intra_byte = 0x180; + counter++; + data_cache_pos++; + if (data_cache_pos == data_cache_size) { + recache(); + } + } else + { + intra_byte |= 0x100; + } + } + return len; + } else { + state = END_PAUSE; + } + break; + case END_PAUSE: + state = DONE; + if (pause) + { + return pause * 3500; + } + break; + default: + assert(false); + } + } + } +protected: + struct stream *stream; + dword size; + dword remaining_bytes; + dword data_cache_size; + dword data_cache_pos; + dword pilot_t; + dword s1_t; + dword s2_t; + dword zero_t; + dword one_t; + dword pilot_len; + dword pause; + byte data_cache[CACHE_SIZE]; + byte last; + + void recache() { + data_cache_size = MIN(remaining_bytes, CACHE_SIZE); + stream_read(stream, data_cache, data_cache_size, true); + data_cache_pos = 0; + remaining_bytes -= data_cache_size; + assert(data_cache); + } + + enum State { + RESET, + PILOT, + S1, + S2, + DATA, + DONE, + END_PAUSE + } state; + // for use by state + dword counter; + int intra_byte; // +/i +}; + +class eTapeInstance { +public: + eTapeInstance(struct stream *stream) : stream(stream) {} + + // start at beginning of tape (note the instance owns the iterator) + virtual eTapePulseIterator *reset() = 0; + virtual ~eTapeInstance() { + stream_close(stream); + } +protected: + struct stream *stream; +}; + +class eTapeInstanceTAP : public eTapeInstance, eTapePulseIterator { +public: + eTapeInstanceTAP(struct stream *stream) : eTapeInstance(stream) {} + eTapePulseIterator *reset() override { + stream_reset(stream); + done = false; + block_pulse_iterator.set_needs_reset(); + return this; + } +protected: + eTapeBlockPulseIterator block_pulse_iterator; + bool done; + + int32_t next_pulse_length() override + { + while (!done) { + int32_t r = block_pulse_iterator.next_pulse_length(); + if (r != -1) { + return r; + } + next_block(); + } + return -1; + } + + void next_block() { + const uint8_t *size_ptr = stream_peek(stream, 2); + if (!size_ptr) { + done = true; + } else { + dword size = Word(size_ptr); + if (size) + { + stream_skip(stream, 2); + const uint8_t *ptr = stream_peek(stream, 1); + // this should read the full contents of the block + block_pulse_iterator.reset(stream, size, 2168, 667, 735, 855, 1710, (*ptr < 4) ? 8064 : 3220, + 1000); + } else { + done = true; + } + } + } +}; + class eTape : public eDeviceSound { typedef eDeviceSound eInherited; +#ifndef NO_USE_FAST_TAPE friend class xZ80::eZ80_FastTape; +#endif public: eTape(eSpeccy* s) : speccy(s) {} +#ifndef NO_USE_DESTRUCTORS virtual ~eTape() { CloseTape(); } +#endif virtual void Init(); virtual void Reset(); - virtual bool IoRead(word port) const; - virtual void IoRead(word port, byte* v, int tact); - bool Open(const char* type, const void* data, size_t data_size); +#ifndef USE_HACKED_DEVICE_ABSTRACTION + virtual bool IoRead(word port) const final; + virtual dword IoNeed() const { return ION_READ; } +#endif + virtual void IoRead(word port, byte* v, int tact) final; + +#ifndef USE_STREAM + bool Open(const char* type, const void* data, size_t data_size) { + assert(false); + return false; + } +#else + bool Open(const char* type, struct stream *stream); +#endif void Start(); void Stop(); + void Rewind() { ResetTape(); } bool Started() const; bool Inserted() const; static eDeviceId Id() { return D_TAPE; } - virtual dword IoNeed() const { return ION_READ; } byte TapeBit(int tact); protected: - bool ParseTAP(const void* data, size_t data_size); - bool ParseCSW(const void* data, size_t data_size); - bool ParseTZX(const void* data, size_t data_size); + bool OpenTAP(struct stream *stream); +#ifndef NO_USE_CSW + bool ParseCSW(struct stream *stream); +#endif +#ifndef NO_USE_TZX + bool ParseTZX(struct stream *stream); +#endif - dword FindPulse(dword t); - void FindTapeIndex(); - void FindTapeSizes(); void StopTape(); void ResetTape(); void StartTape(); +public: void CloseTape(); +protected: + dword NextPulseOrStop(); + void SkipPulse(); + +#ifdef USE_LEGACY_TAPE_COMPARISON + dword FindPulse(dword t); + void FindTapeIndex(); +#ifndef USE_MU_SIMPLIFICATIONS + void FindTapeSizes(); +#endif void Reserve(dword datasize); void MakeBlock(const byte* data, dword size, dword pilot_t, - dword s1_t, dword s2_t, dword zero_t, dword one_t, - dword pilot_len, dword pause, byte last = 8); + dword s1_t, dword s2_t, dword zero_t, dword one_t, + dword pilot_len, dword pause, byte last = 8); void Desc(const byte* data, dword size, char* dst); void AllocInfocell(); - void NamedCell(const void *nm, dword sz = 0); - void CreateAppendableBlock(); - void ParseHardware(const byte* ptr); - +#endif protected: eSpeccy* speccy; + eTapeInstance *tape_instance; + eTapePulseIterator *pulse_iterator; + struct eTapeState { qword edge_change; +#ifdef USE_LEGACY_TAPE_COMPARISON byte* play_pointer; // or NULL if tape stopped byte* end_of_tape; // where to stop tape dword index; // current tape block - dword tape_bit; +#endif + byte tape_bit; + bool playing; }; eTapeState tape; +#ifdef USE_LEGACY_TAPE_COMPARISON struct TAPEINFO { +#ifndef NO_USE_TAPEINFO_DESC char desc[280]; +#endif dword pos; dword t_size; }; @@ -102,8 +339,12 @@ class eTape : public eDeviceSound dword tape_infosize; dword appendable; +#endif }; +#ifndef NO_USE_FAST_TAPE extern xZ80::eZ80::eHandlerStep* fast_tape_emul; +#endif +#endif #endif//__TAPE_H__ diff --git a/devices/memory.cpp b/devices/memory.cpp index dd2dfa0..62f0a7d 100644 --- a/devices/memory.cpp +++ b/devices/memory.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,11 +22,15 @@ along with this program. If not, see . #include "memory.h" #ifdef USE_EMBEDDED_RESOURCES +#ifndef NO_USE_128K #include "res/rom/sos128_0.h" #include "res/rom/sos128_1.h" -#include "res/rom/sos48.h" #include "res/rom/service.h" +#endif +#ifndef NO_USE_DOS #include "res/rom/dos513f.h" +#endif +#include "res/rom/sos48.h" #endif//USE_EMBEDDED_RESOURCES #ifdef USE_EXTERN_RESOURCES @@ -36,35 +41,77 @@ extern byte service[]; extern byte dos513f[]; #endif//USE_EXTERN_RESOURCES +#ifdef NO_FLASH +#define USE_COMPRESSED_ROM +#endif + +#ifdef USE_COMPRESSED_ROM +#include "stream.h" +#include "roms/roms.h" +#endif + +#ifdef USE_SINGLE_64K_MEMORY +#ifndef USE_COMPRESSED_ROM +#include "ram64k.h" +#endif + +#ifndef NO_USE_128K +#error cant handle 128K ROM with single 64K memory buffer +#endif +#endif + //============================================================================= // eMemory::eMemory //----------------------------------------------------------------------------- eMemory::eMemory() : memory(NULL) { +#ifndef USE_SINGLE_64K_MEMORY memory = new byte[SIZE]; memset(memory, 0, SIZE); +#else +#ifdef USE_COMPRESSED_ROM + memory =(uint8_t *) calloc(1, 65536); +#else + memory = ram64k; +#endif + memset(memory + PAGE_SIZE, 0, 3 * PAGE_SIZE); +#endif } //============================================================================= // eMemory::~eMemory -//----------------------------------------------------------------------------- +//----------------- +// ------------------------------------------------------------ +#ifndef NO_USE_DESTRUCTORS eMemory::~eMemory() { +#ifndef USE_SINGLE_64K_MEMORY SAFE_DELETE_ARRAY(memory); +#endif } +#endif //============================================================================= // eMemory::SetPage //----------------------------------------------------------------------------- void eMemory::SetPage(int idx, int page) { +#ifdef USE_BANKED_MEMORY_ACCESS byte* addr = Get(page); + addr -= 0x4000 * idx; bank_read[idx] = addr; - bank_write[idx] = idx ? addr : NULL; + if (idx) + { + bank_write[idx] = addr; + } +#else + assert(false); +#endif } //============================================================================= // eMemory::Page //----------------------------------------------------------------------------- int eMemory::Page(int idx) { +#ifdef USE_BANKED_MEMORY_ACCESS byte* addr = bank_read[idx]; for(int p = 0; p < P_AMOUNT; ++p) { @@ -73,6 +120,10 @@ int eMemory::Page(int idx) } assert(false); return -1; +#else + assert(false); + return 0; +#endif } //============================================================================= @@ -80,38 +131,126 @@ int eMemory::Page(int idx) //----------------------------------------------------------------------------- void eRom::LoadRom(int page, const char* rom) { +#ifdef USE_SINGLE_64K_MEMORY + panic("Warning should not be loading ROM for 64K\n"); +#endif +#if !defined(USE_COMPRESSED_ROM) && !defined(USE_EMBEDDED_RESOURCES) && !defined(USE_EXTERN_RESOURCES) && !defined(USE_EMBEDDED_FILES) FILE* f = fopen(rom, "rb"); assert(f); size_t s = fread(memory->Get(page), 1, eMemory::PAGE_SIZE, f); assert(s == eMemory::PAGE_SIZE); fclose(f); +#else + assert(false); +#endif } + +#ifdef USE_COMPRESSED_ROM +static void decompress_rom_to_memory(uint8_t *dest, const char *name) +{ + for(int i=0;iGet(ROM_128_0), rom128_0_z, rom128_0_z_len, rom128_0_len); + decompress_rom_to_memory(memory->Get(ROM_128_1), rom128_1_z, rom128_1_z_len, rom128_1_len); +#else + init_rom_contents((eMemory::ePage)ROM_128_0, ROM_128_0); + init_rom_contents((eMemory::ePage)ROM_128_1, ROM_128_1); +#endif +#endif +#endif +#else #if defined(USE_EMBEDDED_RESOURCES) || defined(USE_EXTERN_RESOURCES) +#ifndef NO_USE_128K memcpy(memory->Get(ROM_128_0), sos128_0, eMemory::PAGE_SIZE); memcpy(memory->Get(ROM_128_1), sos128_1, eMemory::PAGE_SIZE); +#endif memcpy(memory->Get(ROM_48), sos48, eMemory::PAGE_SIZE); +#ifndef NO_USE_128K memcpy(memory->Get(ROM_SYS), service, eMemory::PAGE_SIZE); +#endif +#ifndef NO_USE_DOS memcpy(memory->Get(ROM_DOS), dos513f, eMemory::PAGE_SIZE); +#endif #else//USE_EMBEDDED_RESOURCES +#ifndef NO_USE_128K LoadRom(ROM_128_0, xIo::ResourcePath("res/rom/sos128_0.rom")); LoadRom(ROM_128_1, xIo::ResourcePath("res/rom/sos128_1.rom")); +#endif LoadRom(ROM_48, xIo::ResourcePath("res/rom/sos48.rom")); +#ifndef NO_USE_128K +#ifndef NO_USE_SERVICE_ROM LoadRom(ROM_SYS, xIo::ResourcePath("res/rom/service.rom")); +#endif +#endif +#ifndef NO_USE_DOS LoadRom(ROM_DOS, xIo::ResourcePath("res/rom/dos513f.rom")); +#endif #endif//USE_EMBEDDED_RESOURCES +#endif } + +#ifdef USE_BANKED_MEMORY_ACCESS //============================================================================= // eRom::Reset //----------------------------------------------------------------------------- void eRom::Reset() { +#ifndef NO_USE_128K +#ifndef NO_USE_SERVICE_ROM SelectPage(mode_48k ? ROM_48 : ROM_SYS); +#else + SelectPage(ROM_SOS()); +#endif +#else + SelectPage(ROM_48); +#endif } +#endif + +#ifdef USE_MU +void eRom::init_rom_contents(eMemory::ePage memory_page, ePage rom_page) { +#if USE_COMPRESSED_ROM + switch (rom_page) { + case ROM_48: + decompress_rom_to_memory(memory->Get(memory_page), "ROM_48"); + break; +#ifndef NO_USE_128K + case ROM_128_0: + decompress_rom_to_memory(memory->Get(memory_page), "ROM_128_0"); + break; + case ROM_128_1: + decompress_rom_to_memory(memory->Get(memory_page), "ROM_128_1"); + break; +#endif + default: + assert(false); + } +#endif +} +#endif +#ifndef NO_USE_128K +#ifndef USE_HACKED_DEVICE_ABSTRACTION //============================================================================= // eRom::IoWrite //----------------------------------------------------------------------------- @@ -120,13 +259,23 @@ bool eRom::IoWrite(word port) const return !mode_48k && !(port & 2) && !(port & 0x8000); // zx128 port } //============================================================================= +// eRam::IoWrite +//----------------------------------------------------------------------------- +bool eRam::IoWrite(word port) const +{ + return !mode_48k && !(port & 2) && !(port & 0x8000); // zx128 port +} +#endif +//============================================================================= // eRom::IoWrite //----------------------------------------------------------------------------- void eRom::IoWrite(word port, byte v, int tact) { - SelectPage((page_selected & ~1) + ((v >> 4) & 1)); + SelectPage((ePage)((page_selected & ~1) + ((v >> 4) & 1))); } +#endif +#ifdef USE_BANKED_MEMORY_ACCESS //============================================================================= // eRam::Reset //----------------------------------------------------------------------------- @@ -136,13 +285,8 @@ void eRam::Reset() memory->SetPage(2, eMemory::P_RAM2); memory->SetPage(3, eMemory::P_RAM0); } -//============================================================================= -// eRam::IoWrite -//----------------------------------------------------------------------------- -bool eRam::IoWrite(word port) const -{ - return !mode_48k && !(port & 2) && !(port & 0x8000); // zx128 port -} +#endif +#ifndef NO_USE_128K //============================================================================= // eRam::IoWrite //----------------------------------------------------------------------------- @@ -151,3 +295,4 @@ void eRam::IoWrite(word port, byte v, int tact) int page = eMemory::P_RAM0 + (v & 7); memory->SetPage(3, page); } +#endif diff --git a/devices/memory.h b/devices/memory.h index 0a09354..5ddd5e6 100644 --- a/devices/memory.h +++ b/devices/memory.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,6 +26,15 @@ along with this program. If not, see . #undef PAGE_SIZE +#if !defined(NO_USE_128K) +#ifdef USE_SINGLE_64K_MEMORY +#error cant use 64k memory for 128 +#endif +#if !defined(USE_BANKED_MEMORY_ACCESS) +#define USE_BANKED_MEMORY_ACCESS +#endif +#endif + //***************************************************************************** // eMemory //----------------------------------------------------------------------------- @@ -32,36 +42,71 @@ class eMemory { public: eMemory(); +#ifndef NO_USE_DESTRUCTORS virtual ~eMemory(); +#endif byte Read(word addr) const { - byte* a = bank_read[(addr >> 14) & 3] + (addr & (PAGE_SIZE - 1)); +#ifndef USE_SINGLE_64K_MEMORY + byte* a = bank_read[(addr >> 14) & 3] + (addr & 0xffffu); return *a; +#else + return memory[addr]; +#endif } void Write(word addr, byte v) { +#ifndef USE_SINGLE_64K_MEMORY byte* a = bank_write[(addr >> 14) & 3]; if(!a) //rom write prevent return; - a += (addr & (PAGE_SIZE - 1)); + a += addr & 0xffffu; *a = v; +#else + if (addr>>14) { + memory[addr] = v; + } +#endif } byte* Get(int page) { return memory + page * PAGE_SIZE; } enum ePage { - P_ROM0 = 0, P_ROM1, P_ROM2, P_ROM3, P_ROM4, +#ifndef NO_USE_128K +#ifndef USE_OVERLAPPED_ROMS + P_ROM0 = 0, + P_ROM1, +#ifndef NO_USE_SERVICE_ROM + P_ROM2, +#endif +#ifndef NO_USE_DOS + P_ROM3, +#endif +#ifndef USE_OVERLAPPED_ROMS + P_ROM4, +#endif +#else + P_ROM_ALT0 = 0, P_ROM_ALT1, +#endif P_RAM0, P_RAM1, P_RAM2, P_RAM3, P_RAM4, P_RAM5, P_RAM6, P_RAM7, +#else + P_ROM4 = 0, P_RAM5, P_RAM2, P_RAM0, +#endif P_AMOUNT }; void SetPage(int idx, int page); int Page(int idx); - enum { BANKS_AMOUNT = 4, PAGE_SIZE = 0x4000, SIZE = P_AMOUNT * PAGE_SIZE }; +#ifdef USE_BANKED_MEMORY_ACCESS + byte **GetBankReads() { return bank_read; } + byte **GetBankWrites() { return bank_write; } +#endif protected: +#ifdef USE_BANKED_MEMORY_ACCESS byte* bank_read[BANKS_AMOUNT]; byte* bank_write[BANKS_AMOUNT]; +#endif byte* memory; }; @@ -71,13 +116,46 @@ class eMemory class eRom : public eDevice { public: - eRom(eMemory* m) : memory(m), page_selected(0), mode_48k(false) {} + eRom(eMemory* m) : memory(m), page_selected((ePage)-1), mode_48k(false) {} + virtual void Init(); +#ifdef USE_BANKED_MEMORY_ACCESS virtual void Reset(); +#endif +#ifndef NO_USE_128K +#ifndef USE_HACKED_DEVICE_ABSTRACTION virtual bool IoWrite(word port) const; + virtual dword IoNeed() const { return ION_WRITE; } +#endif virtual void IoWrite(word port, byte v, int tact); +#endif + + enum ePage + { +#ifndef USE_OVERLAPPED_ROMS +#if !defined(NO_USE_128K) + ROM_128_1 = eMemory::P_ROM0, + ROM_128_0 = eMemory::P_ROM1, +#endif +#ifndef NO_USE_128K +#ifndef NO_USE_SERVICE_ROM + ROM_SYS = eMemory::P_ROM2, +#endif +#endif +#ifndef NO_USE_DOS + ROM_DOS = eMemory::P_ROM3, +#endif + ROM_48 = eMemory::P_ROM4, +#else + ROM_128_1 = (eMemory::P_AMOUNT + 1) & ~1, // must be even + ROM_128_0, + ROM_48, +#endif + }; + void Read(word addr) { +#ifndef NO_USE_DOS byte pc_h = addr >> 8; if(page_selected == ROM_SOS() && (pc_h == 0x3d)) { @@ -87,28 +165,65 @@ class eRom : public eDevice { SelectPage(ROM_SOS()); } +#endif + } + bool DosSelected() const { +#ifndef NO_USE_DOS + return page_selected == ROM_DOS; +#else + return false; +#endif } - void SelectPage(int page) { page_selected = page; memory->SetPage(0, page_selected); } - bool DosSelected() const { return page_selected == ROM_DOS; } +#ifndef NO_USE_128K void Mode48k(bool on) { mode_48k = on; } - int ROM_SOS() const { return mode_48k ? ROM_48 : ROM_128_0; } + ePage ROM_SOS() const { return mode_48k ? ROM_48 : ROM_128_0; } +#else + ePage ROM_SOS() const { return ROM_48; } +#endif static eDeviceId Id() { return D_ROM; } - virtual dword IoNeed() const { return ION_WRITE; } - enum ePage - { - ROM_128_1 = eMemory::P_ROM0, - ROM_128_0 = eMemory::P_ROM1, - ROM_SYS = eMemory::P_ROM2, - ROM_DOS = eMemory::P_ROM3, - ROM_48 = eMemory::P_ROM4, - }; +#ifdef USE_MU + void init_rom_contents(eMemory::ePage memory_page, ePage rom_page); +#endif + void SelectPage(ePage page) { +#ifdef USE_BANKED_MEMORY_ACCESS +#ifdef USE_OVERLAPPED_ROMS + if (page != page_selected) { + if ((page & ~1) != (page_selected & ~1)) { +// printf("Switching ROM contents for %d\n", page); + switch (page) { + case ROM_128_1: + case ROM_128_0: + init_rom_contents(eMemory::ePage::P_ROM_ALT0, ROM_128_1); + init_rom_contents(eMemory::ePage::P_ROM_ALT1, ROM_128_0); + break; + case ROM_48: + init_rom_contents(eMemory::ePage::P_ROM_ALT0, ROM_48); + break; + default: + assert(false); + + } + } +// printf("Switching ROM page to %d\n", page); + page_selected = page; + memory->SetPage(0, page & 1 ? eMemory::ePage::P_ROM_ALT1 : eMemory::ePage::P_ROM_ALT0); + } +#else + assert(page < eMemory::P_RAM0); + page_selected = page; memory->SetPage(0, page_selected); +#endif +#else + assert(page == ROM_48); + page_selected = page; +#endif + } protected: void LoadRom(int page, const char* rom); protected: eMemory* memory; - int page_selected; + ePage page_selected; bool mode_48k; }; @@ -119,13 +234,20 @@ class eRam : public eDevice { public: eRam(eMemory* m) : memory(m), mode_48k(false) {} +#ifdef USE_BANKED_MEMORY_ACCESS virtual void Reset(); - virtual bool IoWrite(word port) const; - virtual void IoWrite(word port, byte v, int tact); +#endif +#ifndef NO_USE_128K void Mode48k(bool on) { mode_48k = on; } + virtual void IoWrite(word port, byte v, int tact); +#ifndef USE_HACKED_DEVICE_ABSTRACTION + virtual bool IoWrite(word port) const; + virtual dword IoNeed() const { return ION_WRITE; } +#endif +#endif bool Mode48k() const { return mode_48k; } static eDeviceId Id() { return D_RAM; } - virtual dword IoNeed() const { return ION_WRITE; } + protected: eMemory* memory; bool mode_48k; diff --git a/devices/sound/ay.cpp b/devices/sound/ay.cpp index d4243ba..fd2bb59 100644 --- a/devices/sound/ay.cpp +++ b/devices/sound/ay.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,25 +17,76 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#ifndef NO_USE_AY #include "../../std.h" #include "../../z80/z80.h" #include "ay.h" +#if KHAN128_I2S +#include "pico/audio_i2s.h" +#else +#include "pico/audio_pwm.h" +#endif +#include "hardware/gpio.h" +#include "../../options_common.h" + +CU_REGISTER_DEBUG_PINS(generate) + +// ---- select at most one --- +//CU_SELECT_DEBUG_PINS(generate) + +// set to 2 just to prove that 22050 is acceptable sounding +//#define HALVEIT 2 +#define HALVEIT 1 +#if PICO_ON_DEVICE +#define DOUBLEO_O 2 +#else +// 2 is more oversampling +#define DOUBLEO_O 2 +//#define DOUBLEO_O 1 +#endif + +#if HALVEIT == 2 +#define DOUBLEO (DOUBLEO_O + 1) +#else +#define DOUBLEO DOUBLEO_O +#endif +#ifdef USE_FAST_AY +static struct fast_ay_state ays; +#endif + +#define RATIO_MAJOR (SNDR_DEFAULT_AY_RATE / 8) +#define RATIO_MINOR ((SNDR_DEFAULT_SAMPLE_RATE << DOUBLEO) / HALVEIT) //============================================================================= // eAY::eAY //----------------------------------------------------------------------------- -eAY::eAY() : t(0), ta(0), tb(0), tc(0), tn(0), te(0), env(0), denv(0) - ,bitA(0), bitB(0), bitC(0), bitN(0), ns(0) +eAY::eAY() : t(0) +#ifndef USE_FAST_AY + ,ta(0), tb(0), tc(0), tn(0), te(0) + ,env(0), denv(0) + ,bitA(0), bitB(0), bitC(0), bitN(0),ns(0) ,bit0(0), bit1(0), bit2(0), bit3(0), bit4(0), bit5(0) ,ea(0), eb(0), ec(0), va(0), vb(0), vc(0) - ,fa(0), fb(0), fc(0), fn(0), fe(0) - ,activereg(0) +#endif + ,fa(0), fb(0), fc(0), fn(0), fe(0) + ,activereg(0) { +#ifdef USE_FAST_AY + ays.v10001 = 0x10001; + ays.frac_major = RATIO_MAJOR; + ays.frac_minor = RATIO_MINOR; + static_assert(RATIO_MAJOR > RATIO_MINOR); +#endif + SetChip(CHIP_AY); SetTimings(SNDR_DEFAULT_SYSTICK_RATE, SNDR_DEFAULT_AY_RATE, SNDR_DEFAULT_SAMPLE_RATE); +#ifndef USE_FAST_AY SetVolumes(0x7FFF, SNDR_VOL_AY, SNDR_PAN_ABC); +#endif + _Reset(); } +#ifndef USE_HACKED_DEVICE_ABSTRACTION //============================================================================= // eAY::IoRead //----------------------------------------------------------------------------- @@ -47,12 +99,12 @@ bool eAY::IoRead(word port) const //----------------------------------------------------------------------------- bool eAY::IoWrite(word port) const { - if(port&2) + if(port&2) return false; - if((port & 0xC0FF) == 0xC0FD) + if((port & 0xC0FF) == 0xC0FD) + return true; + if((port & 0xC000) == 0x8000) return true; - if((port & 0xC000) == 0x8000) - return true; return false; } //============================================================================= @@ -76,6 +128,7 @@ void eAY::IoWrite(word port, byte v, int tact) Write(tact, v); } } +#endif const dword MULT_C_1 = 14; // fixed point precision for 'system tick -> ay tick' // b = 1+ln2(max_ay_tick/8) = 1+ln2(max_ay_fq/8 / min_intfq) = 1+ln2(10000000/(10*8)) = 17.9 @@ -89,64 +142,226 @@ void eAY::FrameStart(dword tacts) t = tacts * chip_clock_rate / system_clock_rate; eInherited::FrameStart(t); } +#ifdef USE_MUx +#define mult_const ((dword)(((qword)SNDR_DEFAULT_SYSTICK_RATE << (MULT_C_1 - 4)) / SNDR_DEFAULT_SYSTICK_RATE)) +#endif + //============================================================================= // eAY::FrameEnd //----------------------------------------------------------------------------- void eAY::FrameEnd(dword tacts) { //adjusting 't' with whole history will fix accumulation of rounding errors - qword end_chip_tick = ((passed_clk_ticks + tacts) * chip_clock_rate) / system_clock_rate; - Flush((dword)(end_chip_tick - passed_chip_ticks)); +// qword end_chip_tick = ((passed_clk_ticks + tacts) * chip_clock_rate) / system_clock_rate; + + dword end_chip_tick = ((passed_clk_ticks + tacts) * mult_const) >> MULT_C_1; + Flush(end_chip_tick - passed_chip_ticks, true); eInherited::FrameEnd(t); passed_clk_ticks += tacts; passed_chip_ticks += t; } + +#define DOUBLEO_MASK ((1u<sample_count = 0; + } + int16_t *samples = (int16_t *) buffer->buffer->bytes; +#ifdef USE_FAST_AY + int count = chiptick - t; + if (count < 0 || count > 4434) { + static bool pants; + if (!pants) { + printf("warn out of range %d\n", count); + pants = true; + } + if (count < 0) count = 0; + else count = 4434; + } + + + uint v = xPlatform::OpSoundVolume(); +// printf("vol %d\n", v); + ays.master_volume = v * 13; // now 4 fractional bits in fast_ay.S) MIN(v, 8);// ? (1 + ((v*11)>>4)) : 0; + //ays.wakeups = 0; + bool print = false; + assert(count < 65531); + if (count > 0) + { + ays.tt = count; + if (print) printf("-> %d %d %d %d %d %d\n", count, ays.tt, (uint)buffer->sample_count, (uint)buffer->max_sample_count, (uint)(ays.over_c_value >> 30u), (uint)ays.over_rem); + DEBUG_PINS_SET(generate, 2); + // printf("count %d\n", count); +// assert(!ays.over_rem); // should not be any remaining samples +// assert(!buffer->sample_count); +#if 0 + ays.a_vol = 28; + ays.fa = 50; + if (!ays.ta) ays.ta = ays.fa + 1; + ays.bits_a03x |= 2; + ays.bits_a03x &= ~4; + static bool warble; +// if (warble) __breakpoint(); +#endif +#if 1 + uint32_t written = fast_ay(&ays, samples + buffer->sample_count, + buffer->max_sample_count - buffer->sample_count); +#else + uint32_t written = 0; + printf("and...\n"); + int e = ays.tt; + for(int j=0; jsample_count + written, + buffer->max_sample_count - buffer->sample_count - written); + printf("%d %d\n", j, written); + } +#endif + static_assert(PICO_AUDIO_BUFFER_SAMPLE_LENGTH >= (PICO_SAMPLE_RATE + 49) / 50, ""); + if (ays.over_rem) { + printf("OOPS overflow %d\n", ays.over_rem); + } + assert(!ays.over_rem); // should not be any remaining samples + DEBUG_PINS_CLR(generate, 2); + buffer->sample_count += written; + assert(buffer->sample_count <= buffer->max_sample_count); + if (print) printf(" %d %d %d %d %d\n", (int)written, ays.tt, (uint)buffer->sample_count, (uint)ays.over_rem, ays.te); +#if 0 + printf("--\n"); + for(int i=0;isample_count;i += 16) { + for(int j=i; jsample_count, i+ 16); j++) { + //printf("%04x ", samples[j]); + printf("%d\n", samples[j]); + } + //printf("\n"); + } +#endif +// if (samples[0]) warble = true; + } else { + assert(count == 0); + assert(eof); // only time we should have no ticks + } + t = chiptick; + if (buffer->sample_count && eof) + { + if (print) printf("%d\n", (uint)buffer->sample_count); +// int x = buffer->sample_count; +// int y = buffer->max_sample_count; + DEBUG_PINS_SET(generate, 1); + give_audio_buffer(producer_pool, buffer); + DEBUG_PINS_CLR(generate, 1); +// printf("%d %d\n", x, y); + buffer = 0; + } + +#else + //const int32_t RATIO_MAJOR = chip_clock_rate; + int32_t ratio = RATIO_MAJOR / 2; + static bool flip; + if (!flip) { + printf("%f %d %d\n", ((float)(RATIO_MAJOR)) / RATIO_MINOR, RATIO_MAJOR, SNDR_DEFAULT_SAMPLE_RATE * 2); + flip = 1; + } + uint alt = DOUBLEO_MASK; + dword en, mix_l = 0, mix_r = 0; +// dword x = 100000; +// if (fa) x = MIN(x, fa); +// if (fb) x = MIN(x, fb); +// if (fc) x = MIN(x, fc); +// if (fn) x = MIN(x, fn); +// if (fe) x = MIN(x, fe); +// printf("%d %d %d %d\nn", x, wakeups, wobble, chiptick - t); + DEBUG_PINS_SET(generate, 1); while (t < chiptick) { t++; - if(++ta >= fa) ta = 0, bitA ^= -1; - if(++tb >= fb) tb = 0, bitB ^= -1; - if(++tc >= fc) tc = 0, bitC ^= -1; - if(++tn >= fn) + if (++ta >= fa) ta = 0, bitA ^= -1; + if (++tb >= fb) tb = 0, bitB ^= -1; + if (++tc >= fc) tc = 0, bitC ^= -1; + if (++tn >= fn) tn = 0, - ns = (ns*2+1) ^ (((ns>>16)^(ns>>13)) & 1), + ns = (ns * 2 + 1) ^ (((ns >> 16) ^ (ns >> 13)) & 1), bitN = 0 - ((ns >> 16) & 1); - if(++te >= fe) + if (++te >= fe) { te = 0, env += denv; - if(env & ~31) + if (env & ~31) { - dword mask = (1< DOUBLEO_MASK) + { + mix_l = mix_r = 0; + alt = 0; + } +#else + mix_l = mix_r = 0; +#endif en = ((ea & env) | va) & ((bitA | bit0) & (bitN | bit3)); - mix_l = vols[0][en]; mix_r = vols[1][en]; + mix_l += vols[0][en]; + mix_r += vols[1][en]; en = ((eb & env) | vb) & ((bitB | bit1) & (bitN | bit4)); - mix_l += vols[2][en]; mix_r += vols[3][en]; + mix_l += vols[2][en]; + mix_r += vols[3][en]; en = ((ec & env) | vc) & ((bitC | bit2) & (bitN | bit5)); - mix_l += vols[4][en]; mix_r += vols[5][en]; + mix_l += vols[4][en]; + mix_r += vols[5][en]; + - if((mix_l ^ eInherited::mix_l) | (mix_r ^ eInherited::mix_r)) // similar check inside update() - Update(t, mix_l, mix_r); + static_assert(RATIO_MAJOR >= RATIO_MINOR, ""); +// if((mix_l ^ eInherited::mix_l) | (mix_r ^ eInherited::mix_r)) // similar check inside update() +// Update(t, mix_l, mix_r); +#if DOUBLEO + if (alt == DOUBLEO_MASK) + { + ratio -= RATIO_MINOR; +#else + ratio -= SNDR_DEFAULT_SAMPLE_RATE; +#endif + if (ratio <= 0) + { + samples[buffer->sample_count++] = (mix_l + mix_r) / (2 << DOUBLEO); +#if HALVEIT == 2 + samples[buffer->sample_count++] = (mix_l + mix_r) / (2 << DOUBLEO); +#endif + assert(buffer->sample_count <= buffer->max_sample_count); + if (buffer->sample_count == buffer->max_sample_count) + { + give_audio_buffer(producer_pool, buffer); + buffer = take_audio_buffer(producer_pool, true); + samples = (int16_t *) buffer->buffer->bytes; + buffer->sample_count = 0; + } + ratio += RATIO_MAJOR; + } +#if DOUBLEO } +#endif + } + DEBUG_PINS_CLR(generate, 1); +#endif } //============================================================================= // eAY::Select @@ -155,7 +370,32 @@ void eAY::Select(byte nreg) { if(chiptype == CHIP_AY) nreg &= 0x0F; activereg = nreg; +#ifdef USE_FAST_AY + extern uint16_t *current_voltab; + extern uint16_t voltab_ay; + extern uint16_t voltab_ym; + current_voltab = chiptype == CHIP_AY ? &voltab_ay : &voltab_ym; +#endif +} + +#ifdef USE_FAST_AY +static inline uint16_t bswap_and_adjust(uint16_t v) { + v = SwapWord(v); + if (v < 3) v = 3; // seems like a reasonable minimum freq + return v; +} + +void adjust_tx(uint16_t new_fx, uint16_t &fx, uint16_t &tx) { + new_fx &= 0xfff; + if (!fx) tx = 1; // we should have been ready anyway + fx = new_fx; + if (!fx) tx = 0xffff; // hack to avoid waking up all the time } + +void update_channel_bits(uint8_t &cb, uint8_t x0, uint8_t x3) { + cb = (cb & 0x9) | (x0 << 2) | (x3 << 1); +} +#endif //============================================================================= // eAY::Write //----------------------------------------------------------------------------- @@ -173,8 +413,70 @@ void eAY::Write(dword timestamp, byte val) reg[activereg] = val; if(timestamp) - Flush((timestamp * mult_const) >> MULT_C_1); // cputick * ( (chip_clock_rate/8) / system_clock_rate ); + Flush((timestamp * mult_const) >> MULT_C_1, false); // cputick * ( (chip_clock_rate/8) / system_clock_rate ); +#ifdef USE_FAST_AY + switch(activereg) + { + case 0: + case 1: + adjust_tx(bswap_and_adjust(r.fA), ays.fa, ays.ta); + break; + case 2: + case 3: + adjust_tx(bswap_and_adjust(r.fB), ays.fb, ays.tb); + break; + case 4: + case 5: + adjust_tx(bswap_and_adjust(r.fC), ays.fc, ays.tc); + break; + case 6: + { + // todo noise freq needs more work (with oversampling) +// uint fn = ((val&0x1f) * 9)/4; + //if (fn == 1) fn = 2; + //uint fn = (val&0x1f)*2 + 1; + uint fn = (val&0x1f)*2; + adjust_tx(fn, ays.fn, ays.tn); + break; + } + case 7: + update_channel_bits(ays.bits_a03x, (val >> 0u) & 1u, (val >> 3u) & 1u); + update_channel_bits(ays.bits_b14x, (val >> 1u) & 1u, (val >> 4u) & 1u); + update_channel_bits(ays.bits_c25x, (val >> 2u) & 1u, (val >> 5u) & 1u); + break; + case 8: + ays.bits_a03x &= ~0x10; + ays.bits_a03x |= (val&0x10); + ays.a_vol = ((val & 0x0F)*2+1); + break; + case 9: + ays.bits_b14x &= ~0x10; + ays.bits_b14x |= (val&0x10); + ays.b_vol = ((val & 0x0F)*2+1); + break; + case 10: + ays.bits_c25x &= ~0x10; + ays.bits_c25x |= (val&0x10); + ays.c_vol = ((val & 0x0F)*2+1); + break; + case 11: + case 12: + adjust_tx( bswap_and_adjust(r.envT), ays.fe, ays.te); + break; + case 13: + { + ays.te = ays.fe; // reset timer + if (ays.te <= 1) ays.te = 0xffff; + if (r.env & 4) ays.e_vol = 0, ays.de_vol = 1; // attack + else ays.e_vol = 31, ays.de_vol = -1; // decay + extern uint32_t envelope_switch[]; + extern uint32_t envelope_handler; + envelope_handler = envelope_switch[r.env & 0xf]; + break; + } + } +#else switch(activereg) { case 0: @@ -222,6 +524,7 @@ void eAY::Write(dword timestamp, byte val) else env = 31, denv = -1; // decay break; } +#endif } //============================================================================= // eAY::Read @@ -237,16 +540,26 @@ byte eAY::Read() //----------------------------------------------------------------------------- void eAY::SetTimings(dword _system_clock_rate, dword _chip_clock_rate, dword _sample_rate) { - _chip_clock_rate /= 8; +#ifndef USE_MU + _chip_clock_rate /= 8; system_clock_rate = _system_clock_rate; chip_clock_rate = _chip_clock_rate; - mult_const = (dword)(((qword)chip_clock_rate << MULT_C_1) / system_clock_rate); +#endif + +#ifndef USE_MU eInherited::SetTimings(_chip_clock_rate, _sample_rate); +#endif passed_chip_ticks = passed_clk_ticks = 0; - t = 0; ns = 0xFFFF; + t = 0; +#ifndef USE_FAST_AY + ns = 0xFFFF; +#else + ays.ns = 0xffff; +#endif } +#ifndef USE_FAST_AY //============================================================================= // eAY::SetVolumes //----------------------------------------------------------------------------- @@ -256,6 +569,9 @@ void eAY::SetVolumes(dword global_vol, const SNDCHIP_VOLTAB *voltab, const SNDCH for (int i = 0; i < 32; i++) vols[j][i] = (dword)(((qword)global_vol * voltab->v[i] * stereo->raw[j])/(65535*100*3)); } +#endif +//#pragma GCC push_options +//#pragma GCC optimize("Os") //============================================================================= // eAY::Reset //----------------------------------------------------------------------------- @@ -265,6 +581,7 @@ void eAY::_Reset(dword timestamp) reg[i] = 0; ApplyRegs(timestamp); } +//#pragma GCC pop_options //============================================================================= // eAY::ApplyRegs //----------------------------------------------------------------------------- @@ -287,6 +604,7 @@ const char * const ay_chips[] = { "AY-3-8910", "YM2149F" }; const char* eAY::GetChipName(CHIP_TYPE i) { return ay_chips[i]; } +#ifndef USE_FAST_AY const SNDCHIP_VOLTAB SNDR_VOL_AY_S = { { 0x0000,0x0000,0x0340,0x0340,0x04C0,0x04C0,0x06F2,0x06F2,0x0A44,0x0A44,0x0F13,0x0F13,0x1510,0x1510,0x227E,0x227E, 0x289F,0x289F,0x414E,0x414E,0x5B21,0x5B21,0x7258,0x7258,0x905E,0x905E,0xB550,0xB550,0xD7A0,0xD7A0,0xFFFF,0xFFFF } }; @@ -312,3 +630,5 @@ const SNDCHIP_PANTAB* SNDR_PAN_BAC = &SNDR_PAN_BAC_S; const SNDCHIP_PANTAB* SNDR_PAN_BCA = &SNDR_PAN_BCA_S; const SNDCHIP_PANTAB* SNDR_PAN_CAB = &SNDR_PAN_CAB_S; const SNDCHIP_PANTAB* SNDR_PAN_CBA = &SNDR_PAN_CBA_S; +#endif +#endif \ No newline at end of file diff --git a/devices/sound/ay.h b/devices/sound/ay.h index e5f7b4b..42d07bd 100644 --- a/devices/sound/ay.h +++ b/devices/sound/ay.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,10 +20,14 @@ along with this program. If not, see . #ifndef __AY_H__ #define __AY_H__ +#ifndef NO_USE_AY #include "device_sound.h" - +#include "pico/audio.h" #pragma once + +extern struct audio_buffer_pool *producer_pool; + namespace xZ80 { class eZ80; } const dword SNDR_DEFAULT_AY_RATE = 1774400; // original ZX-Spectrum soundchip clock fq @@ -74,12 +79,16 @@ class eAY : public eDeviceSound typedef eDeviceSound eInherited; public: eAY(); +#ifndef NO_USE_DESTRUCTORS virtual ~eAY() {} +#endif +#ifndef USE_HACKED_DEVICE_ABSTRACTION virtual bool IoRead(word port) const; virtual bool IoWrite(word port) const; virtual void IoRead(word port, byte* v, int tact); virtual void IoWrite(word port, byte v, int tact); +#endif enum CHIP_TYPE { CHIP_AY, CHIP_YM, CHIP_MAX }; static const char* GetChipName(CHIP_TYPE i); @@ -95,24 +104,36 @@ class eAY : public eDeviceSound virtual void FrameEnd(dword tacts); static eDeviceId Id() { return D_AY; } +#ifndef USE_HACKED_DEVICE_ABSTRACTION virtual dword IoNeed() const { return ION_WRITE|ION_READ; } +#endif -protected: void Write(dword timestamp, byte val); byte Read(); +protected: private: - dword t, ta, tb, tc, tn, te, env; + dword t; +#ifndef USE_FAST_AY + dword ta, tb, tc, tn, te; + dword env; int denv; dword bitA, bitB, bitC, bitN, ns; dword bit0, bit1, bit2, bit3, bit4, bit5; dword ea, eb, ec, va, vb, vc; +#endif dword fa, fb, fc, fn, fe; +#ifndef USE_MU dword mult_const; +#else + const dword mult_const = 1013; +#endif byte activereg; +#ifndef USE_FAST_AY dword vols[6][32]; +#endif CHIP_TYPE chiptype; union { @@ -120,12 +141,74 @@ class eAY : public eDeviceSound struct AYREGS r; }; +#ifndef USE_MU dword chip_clock_rate, system_clock_rate; +#else + const dword chip_clock_rate = SNDR_DEFAULT_AY_RATE / 8; + const dword system_clock_rate = SNDR_DEFAULT_SYSTICK_RATE; +#endif qword passed_chip_ticks, passed_clk_ticks; void _Reset(dword timestamp = 0); // call with default parameter, when context outside start_frame/end_frame block - void Flush(dword chiptick); + void Flush(dword chiptick, bool eof); void ApplyRegs(dword timestamp = 0); }; +#ifdef USE_FAST_AY +extern "C" { +// do not change order of this, as it is used by fast_ay.S +struct fast_ay_state { + // note these are 5 bit values, with bit 4 being channel envelope flag + uint8_t bits_a03x; + uint8_t bits_b14x; + uint8_t bits_c25x; + uint8_t bit_n; + + uint8_t _pad[2]; + + uint16_t fa, fb, fc, fn, fe; + + uint16_t a_output; + uint16_t b_output; + uint16_t c_output; + uint16_t a_vol; + uint16_t b_vol; + uint16_t c_vol; + uint16_t e_vol; + uint16_t de_vol; + + uint32_t master_volume; + uint32_t ns; + uint32_t pad4[2]; + + // mutable channel state + // unlike original code, these tx count down not up, so must be adjust when fx changes + uint16_t ta; + uint16_t tb; + uint16_t tc; + uint16_t tn; + uint16_t te; + uint16_t tt; + + uint32_t v10001; // always 0x10001 + // sample state + uint32_t frac_major; + uint32_t frac_minor; + // mutable sample state + uint32_t frac_accum; + uint32_t over_c_value; + uint32_t over_rem; // number of over_samples remaining + uint32_t over_accum; + + uint32_t wakeups; +}; + +static_assert(offsetof(struct fast_ay_state, fe) == 14, ""); +static_assert(offsetof(struct fast_ay_state, ta) == 48, ""); +static_assert(offsetof(struct fast_ay_state, frac_major) == 64, ""); +static_assert(offsetof(struct fast_ay_state, wakeups) == 88, ""); +extern uint32_t fast_ay(struct fast_ay_state *state, int16_t *samples, uint32_t max_samples); +} +#endif +#endif #endif//__AY_H__ diff --git a/devices/sound/beeper.cpp b/devices/sound/beeper.cpp index b944180..75af9f9 100644 --- a/devices/sound/beeper.cpp +++ b/devices/sound/beeper.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,25 +17,59 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#ifndef NO_USE_BEEPER #include "../../std.h" #include "beeper.h" +#include "khan_lib.h" +#include "../../options_common.h" +eBeeper::eBeeper() { + // need this to create vtable +} //============================================================================= // eBeeper::IoWrite //----------------------------------------------------------------------------- +#ifndef USE_HACKED_DEVICE_ABSTRACTION bool eBeeper::IoWrite(word port) const { return !(port&1); } +#endif //============================================================================= // eBeeper::IoWrite //----------------------------------------------------------------------------- void eBeeper::IoWrite(word port, byte v, int tact) { - const short spk_vol = 8192; - const short mic_vol = 1000; - short spk = (v & 0x10) ? spk_vol : 0; - short mic = (v & 0x08) ? mic_vol : 0; - short mono = spk + mic; - Update(tact, mono, mono); +// const short spk_vol = 8192; +// const short mic_vol = 1000; +// short spk = (v & 0x10) ? spk_vol : 0; +// short mic = (v & 0x08) ? mic_vol : 0; +// short mono = spk + mic; +// mono = (mono * 182 * xPlatform::OpSoundVolume()) >> 16; //(0->255) + if (!(port&1)) { + uint8_t mono = (((v & 0x10) ? 21 : 0) + ((v & 0x08) ? 4 : 0)) * xPlatform::OpSoundVolume(); + Update(tact, mono, mono); + } +} + +void eBeeper::Reset() +{ + khan_beeper_reset(); +} +//============================================================================= +// eDeviceSound::FrameStart +//----------------------------------------------------------------------------- +void eBeeper::FrameStart(dword tacts) +{ + static int frame_number = 0; + khan_beeper_begin_frame(tacts, frame_number++); +} + +//============================================================================= +// eDeviceSound::FrameEnd +//----------------------------------------------------------------------------- +void eBeeper::FrameEnd(dword tacts) +{ + khan_beeper_end_frame(tacts); } +#endif diff --git a/devices/sound/beeper.h b/devices/sound/beeper.h index e02a733..4150f6a 100644 --- a/devices/sound/beeper.h +++ b/devices/sound/beeper.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,10 +30,16 @@ along with this program. If not, see . class eBeeper : public eDeviceSound { public: - virtual bool IoWrite(word port) const; - virtual void IoWrite(word port, byte v, int tact); + eBeeper(); + void IoWrite(word port, byte v, int tact) override; static eDeviceId Id() { return D_BEEPER; } + void Reset() override; +#ifndef USE_HACKED_DEVICE_ABSTRACTION + virtual bool IoWrite(word port) const; virtual dword IoNeed() const { return ION_WRITE; } +#endif + void FrameStart(dword tacts) override; + void FrameEnd(dword tacts) override; }; #endif//__BEEPER_H__ diff --git a/devices/sound/device_sound.cpp b/devices/sound/device_sound.cpp index 14c0dff..88f0c75 100644 --- a/devices/sound/device_sound.cpp +++ b/devices/sound/device_sound.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,28 +19,28 @@ along with this program. If not, see . #include "../../std.h" #include "device_sound.h" +#include "../../platform/platform.h" +#include "../../speccy.h" + +#include "khan_lib.h" + +#if defined(NO_USE_BEEPER) && defined(NO_USE_AY) +#define NO_USE_SOUND +#endif //============================================================================= // eDeviceSound::eDeviceSound //----------------------------------------------------------------------------- -eDeviceSound::eDeviceSound() : mix_l(0), mix_r(0), s1_l(0), s1_r(0), s2_l(0), s2_r(0) +eDeviceSound::eDeviceSound() : mix_l(0), mix_r(0) { - SetTimings(SNDR_DEFAULT_SYSTICK_RATE, SNDR_DEFAULT_SAMPLE_RATE); +// SetTimings(SNDR_DEFAULT_SYSTICK_RATE, SNDR_DEFAULT_SAMPLE_RATE); } -const dword TICK_FF = 6; // oversampling ratio: 2^6 = 64 -const dword TICK_F = (1< sound tick' -// b = 1+ln2(max_sndtick) = 1+ln2((max_sndfq*TICK_F)/min_intfq) = 1+ln2(48000*64/10) ~= 19.2; -// assert(b+MULT_C <= 32) - //============================================================================= // eDeviceSound::FrameStart //----------------------------------------------------------------------------- void eDeviceSound::FrameStart(dword tacts) { - dword endtick = (tacts * (qword)sample_rate * TICK_F) / clock_rate; //prev frame rest - base_tick = tick - endtick; } //============================================================================= // eDeviceSound::Update @@ -48,183 +49,27 @@ void eDeviceSound::Update(dword tact, dword l, dword r) { if(!((l ^ mix_l) | (r ^ mix_r))) return; - dword endtick = (tact * (qword)sample_rate * TICK_F) / clock_rate; - Flush(base_tick + endtick); mix_l = l; mix_r = r; + Flush(tact); + // note we flush with the new level } //============================================================================= // eDeviceSound::FrameEnd //----------------------------------------------------------------------------- void eDeviceSound::FrameEnd(dword tacts) { - dword endtick = (tacts * (qword)sample_rate * TICK_F) / clock_rate; - Flush(base_tick + endtick); -} -//============================================================================= -// eDeviceSound::AudioData -//----------------------------------------------------------------------------- -void* eDeviceSound::AudioData() -{ - return buffer; -} -//============================================================================= -// eDeviceSound::AudioDataReady -//----------------------------------------------------------------------------- -dword eDeviceSound::AudioDataReady() -{ - return (dstpos - buffer)*sizeof(SNDSAMPLE); +// dword endtick = (tacts * (qword)sample_rate * TICK_F) / clock_rate; +// Flush(base_tick + endtick); } -//============================================================================= -// eDeviceSound::AudioDataUse -//----------------------------------------------------------------------------- -void eDeviceSound::AudioDataUse(dword size) -{ - assert(size == AudioDataReady()); - dstpos = buffer; -} -//============================================================================= -// eDeviceSound::SetTimings -//----------------------------------------------------------------------------- -void eDeviceSound::SetTimings(dword _clock_rate, dword _sample_rate) -{ - clock_rate = _clock_rate; - sample_rate = _sample_rate; - - tick = base_tick = 0; - dstpos = buffer; -} - -static dword filter_diff[TICK_F*2]; -const double filter_sum_full = 1.0, filter_sum_half = 0.5; -const dword filter_sum_full_u = (dword)(filter_sum_full * 0x10000); -const dword filter_sum_half_u = (dword)(filter_sum_half * 0x10000); //============================================================================= // eDeviceSound::Flush //----------------------------------------------------------------------------- -void eDeviceSound::Flush(dword endtick) -{ - dword scale; - if(!((endtick ^ tick) & ~(TICK_F-1))) - { - //same discrete as before - scale = filter_diff[(endtick & (TICK_F-1)) + TICK_F] - filter_diff[(tick & (TICK_F-1)) + TICK_F]; - s2_l += mix_l * scale; - s2_r += mix_r * scale; - - scale = filter_diff[endtick & (TICK_F-1)] - filter_diff[tick & (TICK_F-1)]; - s1_l += mix_l * scale; - s1_r += mix_r * scale; - - tick = endtick; - } - else - { - scale = filter_sum_full_u - filter_diff[(tick & (TICK_F-1)) + TICK_F]; - - dword sample_value; - sample_value = ((mix_l*scale + s2_l) >> 16) + - ((mix_r*scale + s2_r) & 0xFFFF0000); - - dstpos->sample = sample_value; - dstpos++; - if(dstpos - buffer >= BUFFER_LEN) - { - dstpos = buffer; - } - - scale = filter_sum_half_u - filter_diff[tick & (TICK_F-1)]; - s2_l = s1_l + mix_l * scale; - s2_r = s1_r + mix_r * scale; - - tick = (tick | (TICK_F-1))+1; - - if((endtick ^ tick) & ~(TICK_F-1)) - { - // assume filter_coeff is symmetric - dword val_l = mix_l * filter_sum_half_u; - dword val_r = mix_r * filter_sum_half_u; - do - { - dword sample_value; - sample_value = ((s2_l + val_l) >> 16) + - ((s2_r + val_r) & 0xFFFF0000); // save s2+val - - dstpos->sample = sample_value; - dstpos++; - if(dstpos - buffer >= BUFFER_LEN) - { - dstpos = buffer; - } - - tick += TICK_F; - s2_l = val_l; - s2_r = val_r; // s2=s1, s1=0; - - } while ((endtick ^ tick) & ~(TICK_F-1)); - } - - tick = endtick; - - scale = filter_diff[(endtick & (TICK_F-1)) + TICK_F] - filter_sum_half_u; - s2_l += mix_l * scale; - s2_r += mix_r * scale; - - scale = filter_diff[endtick & (TICK_F-1)]; - s1_l = mix_l * scale; - s1_r = mix_r * scale; - } -} - -const double filter_coeff[TICK_F*2] = -{ - // filter designed with Matlab's DSP toolbox - 0.000797243121022152, 0.000815206499600866, 0.000844792477531490, 0.000886460636664257, - 0.000940630171246217, 0.001007677515787512, 0.001087934129054332, 0.001181684445143001, - 0.001289164001921830, 0.001410557756409498, 0.001545998595893740, 0.001695566052785407, - 0.001859285230354019, 0.002037125945605404, 0.002229002094643918, 0.002434771244914945, - 0.002654234457752337, 0.002887136343664226, 0.003133165351783907, 0.003391954293894633, - 0.003663081102412781, 0.003946069820687711, 0.004240391822953223, 0.004545467260249598, - 0.004860666727631453, 0.005185313146989532, 0.005518683858848785, 0.005860012915564928, - 0.006208493567431684, 0.006563280932335042, 0.006923494838753613, 0.007288222831108771, - 0.007656523325719262, 0.008027428904915214, 0.008399949736219575, 0.008773077102914008, - 0.009145787031773989, 0.009517044003286715, 0.009885804729257883, 0.010251021982371376, - 0.010611648461991030, 0.010966640680287394, 0.011314962852635887, 0.011655590776166550, - 0.011987515680350414, 0.012309748033583185, 0.012621321289873522, 0.012921295559959939, - 0.013208761191466523, 0.013482842243062109, 0.013742699838008606, 0.013987535382970279, - 0.014216593638504731, 0.014429165628265581, 0.014624591374614174, 0.014802262449059521, - 0.014961624326719471, 0.015102178534818147, 0.015223484586101132, 0.015325161688957322, - 0.015406890226980602, 0.015468413001680802, 0.015509536233058410, 0.015530130313785910, - 0.015530130313785910, 0.015509536233058410, 0.015468413001680802, 0.015406890226980602, - 0.015325161688957322, 0.015223484586101132, 0.015102178534818147, 0.014961624326719471, - 0.014802262449059521, 0.014624591374614174, 0.014429165628265581, 0.014216593638504731, - 0.013987535382970279, 0.013742699838008606, 0.013482842243062109, 0.013208761191466523, - 0.012921295559959939, 0.012621321289873522, 0.012309748033583185, 0.011987515680350414, - 0.011655590776166550, 0.011314962852635887, 0.010966640680287394, 0.010611648461991030, - 0.010251021982371376, 0.009885804729257883, 0.009517044003286715, 0.009145787031773989, - 0.008773077102914008, 0.008399949736219575, 0.008027428904915214, 0.007656523325719262, - 0.007288222831108771, 0.006923494838753613, 0.006563280932335042, 0.006208493567431684, - 0.005860012915564928, 0.005518683858848785, 0.005185313146989532, 0.004860666727631453, - 0.004545467260249598, 0.004240391822953223, 0.003946069820687711, 0.003663081102412781, - 0.003391954293894633, 0.003133165351783907, 0.002887136343664226, 0.002654234457752337, - 0.002434771244914945, 0.002229002094643918, 0.002037125945605404, 0.001859285230354019, - 0.001695566052785407, 0.001545998595893740, 0.001410557756409498, 0.001289164001921830, - 0.001181684445143001, 0.001087934129054332, 0.001007677515787512, 0.000940630171246217, - 0.000886460636664257, 0.000844792477531490, 0.000815206499600866, 0.000797243121022152 -}; - -//============================================================================= -// eFilterDiffInit -//----------------------------------------------------------------------------- -static struct eFilterDiffInit +void eDeviceSound::Flush(int64_t endtick) { - eFilterDiffInit() - { - double sum = 0; - for(int i = 0; i < (int)TICK_F*2; i++) - { - filter_diff[i] = (int)(sum * 0x10000); - sum += filter_coeff[i]; - } - } -} fdi; +#ifndef NO_USE_SOUND +#ifndef NO_USE_BEEPER + khan_beeper_level_change(endtick, (mix_l + mix_r)/2); +#endif +#endif +} \ No newline at end of file diff --git a/devices/sound/device_sound.h b/devices/sound/device_sound.h index 92323e8..2b43266 100644 --- a/devices/sound/device_sound.h +++ b/devices/sound/device_sound.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,47 +24,47 @@ along with this program. If not, see . #pragma once -union SNDSAMPLE -{ - dword sample; // left/right channels in low/high WORDs - struct { word left, right; } ch; // or left/right separately -}; - +//union SNDSAMPLE +//{ +// dword sample; // left/right channels in low/high WORDs +// struct { word left, right; } ch; // or left/right separately +//}; +// const dword SNDR_DEFAULT_SYSTICK_RATE = 71680 * 50; // ZX-Spectrum Z80 clock -const dword SNDR_DEFAULT_SAMPLE_RATE = 44100; -//============================================================================= -// eDeviceSound -//----------------------------------------------------------------------------- +#if defined(USE_MU) && PICO_ON_DEVICE +#if KHAN128_I2S +#define PICO_SAMPLE_RATE 24000 // some I2S chips seem unhappy with 22050 +#else +// PWM must be 22058 +#define PICO_SAMPLE_RATE 22058 +#endif +#else +#define PICO_SAMPLE_RATE 44100 +#endif + +const dword SNDR_DEFAULT_SAMPLE_RATE = PICO_SAMPLE_RATE; + +//============================================================================= +// eDeviceSound +//----------------------------------------------------------------------------- class eDeviceSound : public eDevice { public: eDeviceSound(); - void SetTimings(dword clock_rate, dword sample_rate); +// void SetTimings(dword clock_rate, dword sample_rate); virtual void FrameStart(dword tacts); virtual void FrameEnd(dword tacts); virtual void Update(dword tact, dword l, dword r); - enum { BUFFER_LEN = 16384 }; - - void* AudioData(); - dword AudioDataReady(); - void AudioDataUse(dword size); - protected: dword mix_l, mix_r; - SNDSAMPLE* dstpos; - dword clock_rate, sample_rate; - - SNDSAMPLE buffer[BUFFER_LEN]; +// dword clock_rate, sample_rate; private: - dword tick, base_tick; - dword s1_l, s1_r; - dword s2_l, s2_r; - - void Flush(dword endtick); + void Flush(int64_t endtick); }; #endif//__DEVICE_SOUND_H__ + diff --git a/devices/ula.cpp b/devices/ula.cpp index c1b70a7..26d23c0 100644 --- a/devices/ula.cpp +++ b/devices/ula.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,140 +25,63 @@ along with this program. If not, see . #define Max(o, p) (o > p ? o : p) #define Min(o, p) (o < p ? o : p) +#define line_tacts 224 +#define paper_start 17989 +#define mid_lines SZX_HEIGHT +#define buf_mid SZX_WIDTH +#define b_top ((S_HEIGHT - mid_lines) / 2) +#define b_left ((S_WIDTH - buf_mid) / 2) +#define first_line_t (paper_start - b_top * line_tacts - b_left / 2) + +// this produces very nice code on ARM! +#define hideous_divide_by_224(x) (((((x)*0x1249) + (((x)*0x1249)>>15)) + 255)>>20) + +#define speccy_y_to_normal(y) (((y) & 0xc0) + (((y)&7) << 3) + (((y) >> 3) & 7)) +// it is self inverse +#define normal_to_speccy_y(y) speccy_y_to_normal(y) //============================================================================= // eUla::eUla //----------------------------------------------------------------------------- -eUla::eUla(eMemory* m) : memory(m), border_color(0), first_screen(true), base(NULL) - , colortab(NULL), timing(NULL), prev_t(0), frame(0), mode_48k(false) +eUla::eUla(eMemory* m) : memory(m), border_color(0), first_screen(true), base(NULL), + prev_t(0), border_y(0), in_paper(false), frame(0) +#ifndef NO_USE_128K + ,mode_48k(false) +#endif { - screen = new byte[S_WIDTH * S_HEIGHT]; - memset(screen, 0, S_WIDTH * S_HEIGHT); } //============================================================================= // eUla::~eUla //----------------------------------------------------------------------------- +#ifndef NO_USE_DESTRUCTORS eUla::~eUla() { +#ifndef NO_USE_SCREEN SAFE_DELETE_ARRAY(screen); +#endif } +#endif //============================================================================= // eUla::Init //----------------------------------------------------------------------------- void eUla::Init() { - // pentagon timings - line_tacts = 224; - paper_start = 17989; - prev_t = 0; - timing = timings; + border_y = prev_t = 0; + in_paper = false; +#ifndef NO_USE_SCREEN colortab = colortab1; - CreateTables(); - CreateTimings(); +#endif base = memory->Get(eMemory::P_RAM5); } //============================================================================= -// eUla::CreateTables -//----------------------------------------------------------------------------- -void eUla::CreateTables() -{ - int i = 0; // calc screen addresses - for(int p = 0; p < 4; p++) - { - for(int y = 0; y < 8; y++) - { - for(int o = 0; o < 8; o++, i++) - { - scrtab[i] = p*0x800 + y*0x20 + o*0x100, - atrtab[i] = 0x1800 + (p*8+y)*32; - } - } - } - - // make colortab: zx-attr -> pc-attr - for(int a = 0; a < 0x100; a++) - { - byte ink = a & 7; - byte paper = (a >> 3) & 7; - byte bright = (a >> 6) & 1; - byte flash = (a >> 7) & 1; - if(ink) - ink |= bright << 3; // no bright for 0th color - if(paper) - paper |= bright << 3; // no bright for 0th color - byte c1 = (paper << 4) | ink; - if(flash) - { - byte t = ink; - ink = paper; - paper = t; - } - byte c2 = (paper << 4) | ink; - colortab1[a] = c1; - colortab2[a] = c2; - } -} -//============================================================================= -// eUla::CreateTimings -//----------------------------------------------------------------------------- -// each cpu tact ula painted 2pix -// while painted border ula read color value on each 2pix, on paper each 8pix -//----------------------------------------------------------------------------- -void eUla::CreateTimings() -{ - int b_bottom, b_top, b_left, b_right; - int mid_lines = SZX_HEIGHT, buf_mid = SZX_WIDTH; - b_top = b_bottom = (S_HEIGHT - mid_lines) / 2; - b_left = b_right = (S_WIDTH - buf_mid) / 2; - - int scr_width = S_WIDTH; - int idx = 0; - - timings[idx++].Set(0, eTiming::Z_SHADOW); // to skip non visible area - int line_t = paper_start - b_top * line_tacts - b_left / 2; - for(int i = 0; i < b_top; ++i) // top border - { - byte* dst = screen + scr_width * i; - timings[idx++].Set(Max(line_t, 0), eTiming::Z_BORDER, dst); - - int t = Max(line_t + (b_left + buf_mid + b_right) / 2, 0); - timings[idx++].Set(t, eTiming::Z_SHADOW); - line_t += line_tacts; - } - for(int i = 0; i < mid_lines; ++i) // screen + border - { - byte* dst = screen + scr_width * (i + b_top); - timings[idx++].Set(Max(line_t, 0), eTiming::Z_BORDER, dst); - - int t = Max(line_t + b_left / 2, 0); - dst = screen + scr_width * (i + b_top) + b_left; - timings[idx++].Set(t, eTiming::Z_PAPER, dst, scrtab[i], atrtab[i]); - - t = Max(line_t + (b_left + buf_mid) / 2, 0); - dst = screen + scr_width * (i + b_top) + b_left + buf_mid; - timings[idx++].Set(t, eTiming::Z_BORDER, dst); - - t = Max(line_t + (b_left + buf_mid + b_right) / 2, 0); - timings[idx++].Set(t, eTiming::Z_SHADOW); - line_t += line_tacts; - } - for(int i = 0; i < b_bottom; ++i) // bottom border - { - byte* dst = screen + scr_width * (i + b_top + mid_lines); - timings[idx++].Set(Max(line_t, 0), eTiming::Z_BORDER, dst); - - int t = Max(line_t + (b_left + buf_mid + b_right) / 2, 0); - timings[idx++].Set(t, eTiming::Z_SHADOW); - line_t += line_tacts; - } - timings[idx].Set(0x7fffffff, eTiming::Z_SHADOW); // shadow area rest -} -//============================================================================= // eUla::Reset //----------------------------------------------------------------------------- void eUla::Reset() { +#ifndef NO_USE_128K SwitchScreen(true, 0); +#endif } +#ifndef NO_USE_128K //============================================================================= // eUla::SwitchScreen //----------------------------------------------------------------------------- @@ -170,6 +94,8 @@ void eUla::SwitchScreen(bool first, int tact) int page = first_screen ? eMemory::P_RAM5: eMemory::P_RAM7; base = memory->Get(page); } +#endif +#ifndef USE_HACKED_DEVICE_ABSTRACTION //============================================================================= // eUla::IoRead //----------------------------------------------------------------------------- @@ -184,20 +110,19 @@ bool eUla::IoWrite(word port) const { return !(port&1) || (!mode_48k && !(port & 2) && !(port & 0x8000)); } +#endif //============================================================================= // eUla::IoRead //----------------------------------------------------------------------------- void eUla::IoRead(word port, byte* v, int tact) { UpdateRay(tact); - if(timing->zone != eTiming::Z_PAPER) // ray is not in paper + if(!in_paper) // ray is not in paper { *v = 0xff; return; } - int t = tact; - int offs = (t - timing->t) / 4; - byte* atr = base + timing->attr_offs + offs; + byte* atr = base + 0x1800 + (paper_y / 8) * 32 + paper_x / 4; *v = *atr; } //============================================================================= @@ -213,10 +138,12 @@ void eUla::IoWrite(word port, byte v, int tact) border_color = v & 7; } } +#ifndef NO_USE_128K if(!(port & 2) && !(port & 0x8000)) // zx128 port { SwitchScreen(!(v & 0x08), tact); } +#endif } //============================================================================= // eUla::FrameUpdate @@ -224,118 +151,55 @@ void eUla::IoWrite(word port, byte v, int tact) void eUla::FrameUpdate() { UpdateRay(0x7fff0000); - prev_t = 0; - timing = timings; + in_paper = false; + border_y = 0; if(++frame >= 15) { frame = 0; - colortab = colortab == colortab1 ? colortab2 : colortab1; } } + + + //============================================================================= // UpdateRay //----------------------------------------------------------------------------- void eUla::UpdateRay(int tact) { - int t = prev_t; - while(t < tact) - { - switch(timing->zone) - { - case eTiming::Z_SHADOW: - t = (timing + 1)->t; - break; - case eTiming::Z_BORDER: - UpdateRayBorder(t, tact); - break; - case eTiming::Z_PAPER: - UpdateRayPaper(t, tact); - break; - } - if(t == (timing + 1)->t) - { - timing++; - } - } - prev_t = t; -} -//============================================================================= -// eUla::UpdateRayBorder -//----------------------------------------------------------------------------- -void eUla::UpdateRayBorder(int& t, int last_t) -{ - int offs = (t - timing->t) * 2; - byte* dst = timing->dst + offs; - int end = Min(last_t, (timing + 1)->t); - for(; t < end; ++t) - { - *dst++ = border_color; - *dst++ = border_color; - } -} -//============================================================================= -// eUla::UpdateRayPaper -//----------------------------------------------------------------------------- -void eUla::UpdateRayPaper(int& t, int last_t) -{ - int offs = (t - timing->t) / 4; - byte* scr = base + timing->scr_offs + offs; - byte* atr = base + timing->attr_offs + offs; - byte* dst = timing->dst + offs * 8; - int end = Min(last_t, (timing + 1)->t); - for(int i = 0; t < end; ++i) - { - byte pix = scr[i]; - byte ink = colortab[atr[i]]; - byte paper = ink >> 4; - ink &= 0x0f; - for(int b = 0; b < 8; ++b) - { - *dst++ = ((pix << b) & 0x80) ? ink : paper; + int t = tact - first_line_t; + if (t > 0) { + // y = t / 224; + int32_t y = hideous_divide_by_224(t); + for(; border_y < Min(y, S_HEIGHT); border_y++) { + // todo left/right border or indeed changing mid line + border_colors[border_y] = border_color; } - t += 4; - } -} -//============================================================================= -// eUla::FlushScreen -//----------------------------------------------------------------------------- -void eUla::FlushScreen() -{ - int page = first_screen ? eMemory::P_RAM5: eMemory::P_RAM7; - byte* src = memory->Get(page); - byte* dst = screen; - - int border_half_width = (S_WIDTH - SZX_WIDTH) / 2; - int border_half_height = (S_HEIGHT - SZX_HEIGHT) / 2; - - for(int i = 0; i < border_half_height * S_WIDTH; ++i) - { - *dst++ = border_color; - } - for(int y = 0; y < SZX_HEIGHT; ++y) - { - for(int x = 0; x < border_half_width; ++x) - { - *dst++ = border_color; - } - for(int x = 0; x < SZX_WIDTH / 8; x++) - { - byte pix = *(src + scrtab[y] + x); - byte ink = colortab[*(src + atrtab[y] + x)]; - byte paper = ink >> 4; - ink &= 0x0f; - for(int b = 0; b < 8; ++b) - { - *dst++ = ((pix << b) & 0x80) ? ink : paper; + in_paper = y >= b_top && y < (b_top + mid_lines); + if (in_paper) { + paper_x = t - y * 224 - b_left / 2; + paper_x = paper_x * 2; + if (paper_x < 0 || paper_x >= buf_mid) { + in_paper = false; + } else { + y -= b_top; + paper_y = speccy_y_to_normal(y); } } - for(int x = 0; x < border_half_width; ++x) - { - *dst++ = border_color; - } } - for(int i = 0; i < border_half_height * S_WIDTH; ++i) - { - *dst++ = border_color; + prev_t = tact; +} + +// note l is 0 based for top of display area, negative for top border +bool eUla::GetLineInfo(int l, byte& border, const byte *& attr, const byte *& pixels) { + int sl = l + (S_HEIGHT - SZX_HEIGHT) / 2; + if (sl < 0) sl = 0; + if (sl >= S_HEIGHT) sl = S_HEIGHT - 1; + border = border_colors[sl]; + if (l >= 0 && l < SZX_HEIGHT) { + pixels = base + normal_to_speccy_y(l) * 32; + attr = base + 0x1800 + (l >> 3) * 32; + } else { + pixels = attr = nullptr; } -} + return true; +} \ No newline at end of file diff --git a/devices/ula.h b/devices/ula.h index d82a1ed..ef82c89 100644 --- a/devices/ula.h +++ b/devices/ula.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,52 +33,39 @@ class eUla : public eDevice { public: eUla(eMemory* m); +#ifndef NO_USE_DESTRUCTORS virtual ~eUla(); +#endif virtual void Init(); virtual void Reset(); virtual void FrameUpdate(); - virtual bool IoRead(word port) const; - virtual bool IoWrite(word port) const; - virtual void IoRead(word port, byte* v, int tact); - virtual void IoWrite(word port, byte v, int tact); - void Write(int tact) { if(prev_t < tact) UpdateRay(tact); } - void* Screen() const { return screen; } +#ifndef USE_HACKED_DEVICE_ABSTRACTION + virtual bool IoRead(word port) const final; + virtual bool IoWrite(word port) const final; + virtual dword IoNeed() const { return ION_WRITE|ION_READ; } +#endif + virtual void IoRead(word port, byte* v, int tact) final; + virtual void IoWrite(word port, byte v, int tact) final; + void Write(int tact) { + if(prev_t < tact) UpdateRay(tact); + } byte BorderColor() const { return border_color; } bool FirstScreen() const { return first_screen; } +#ifndef NO_USE_128K void Mode48k(bool on) { mode_48k = on; } +#endif static eDeviceId Id() { return D_ULA; } - virtual dword IoNeed() const { return ION_WRITE|ION_READ; } -protected: - void CreateTables(); - void CreateTimings(); + bool GetLineInfo(int l, byte& border, const byte *& attr, const byte *& pixels); +#ifndef NO_USE_128K void SwitchScreen(bool first, int tact); +#endif +protected: void UpdateRay(int tact); - void UpdateRayBorder(int& t, int last_t); - void UpdateRayPaper(int& t, int last_t); - void FlushScreen(); enum eScreen { S_WIDTH = 320, S_HEIGHT = 240, SZX_WIDTH = 256, SZX_HEIGHT = 192 }; - struct eTiming - { - enum eZone { Z_SHADOW, Z_BORDER, Z_PAPER }; - void Set(int _t, eZone _zone = Z_SHADOW, byte* _dst = NULL - , int _scr_offs = 0, int _attr_offs = 0) - { - dst = _dst; - t = _t; - zone = _zone; - scr_offs = _scr_offs; - attr_offs = _attr_offs; - } - byte* dst; // screen raster pointer - int t; // start zone tact - eZone zone; // what are drawing: shadow/border/screen - int scr_offs; - int attr_offs; - }; protected: eMemory* memory; int line_tacts; // t-states per line @@ -85,18 +73,19 @@ class eUla : public eDevice byte border_color; bool first_screen; byte* base; - byte* screen; - int scrtab[256]; // offset to start of line - int atrtab[256]; // offset to start of attribute line - byte colortab1[256]; // map zx attributes to pc attributes - byte colortab2[256]; - byte* colortab; - eTiming timings[4 * S_HEIGHT]; - eTiming* timing; - int prev_t; // last drawn pixel's tact + int prev_t; + int border_y; // last update ray y pos + bool in_paper; int frame; + // only valid if in_paper = true; + int paper_x, paper_y; +#ifndef NO_USE_128K bool mode_48k; +#else + static const bool mode_48k = false; +#endif + byte border_colors[S_HEIGHT]; }; #endif//__ULA_H__ diff --git a/embed_tool/CMakeLists.txt b/embed_tool/CMakeLists.txt new file mode 100644 index 0000000..93af8b5 --- /dev/null +++ b/embed_tool/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.13) +project(embed_tool C CXX) +set(CMAKE_CXX_STANDARD 17) + +set(SRC_ROOT ..) + +add_executable(embed_tool + main.cpp + ${SRC_ROOT}/3rdparty/miniz/miniz.c + ${SRC_ROOT}/3rdparty/miniz/miniz_tinfl.c + ${SRC_ROOT}/3rdparty/miniz/miniz_tdef.c + ) + +target_include_directories(embed_tool PRIVATE + ${SRC_ROOT} + ${SRC_ROOT}/3rdparty/miniz +) diff --git a/embed_tool/main.cpp b/embed_tool/main.cpp new file mode 100644 index 0000000..e2acd65 --- /dev/null +++ b/embed_tool/main.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MINIZ_NO_STDIO +#define MINIZ_NO_ARCHIVE_APIS +#define MINIZ_NO_TIME +#define MINIZ_NO_ZLIB_APIS +#define MINIZ_NO_MALLOC +#include "miniz.h" +#include "khan/games/games.h" + +#define ERROR_ARGS -1 +#define ERROR_UNKNOWN -2 +#define ERROR_SYNTAX -3 +#define ERROR_FILE -3 + +using std::string; +using std::vector; +using std::filesystem::path; + +string root_dir; +bool is_game; +bool is_rom; + +vector compress(const vector& raw); + +static string hex_string(int value, int width = 8, bool prefix = true) { + std::stringstream ss; + if (prefix) ss << "0x"; + ss << std::setfill('0') << std::setw(width) << std::hex << value; + return ss.str(); +} + +struct failure : std::exception { + failure(int code, string s) : c(code), s(std::move(s)) {} + + const char *what() const noexcept override { + return s.c_str(); + } + + int code() const { return c; } + +private: + int c; + string s; +}; + +static int usage() { + fprintf(stderr, "Usage: embed_tool [-d | -r] -p \n"); + return ERROR_ARGS; +} + +string trim_left(const string &str) { + const string pattern = " \f\n\r\t\v"; + auto n = str.find_first_not_of(pattern); + if (n == string::npos) return str; + return str.substr(n); +} + +string trim_right(const string &str) { + const string pattern = " \f\n\r\t\v"; + auto n = str.find_last_not_of(pattern); + if (n == string::npos) return str; + return str.substr(0, n+ 1); +} + +string trim(const string &str) { + return trim_left(trim_right(str)); +} + +void doit(std::ifstream &in, std::ofstream &out) { + string entity = is_game ? "game" : "rom"; + + string line; + auto dump = [&](vector data) { + for (uint i = 0; i < data.size(); i += 32) { + out << " "; + for (uint j = i; j < std::min(i + 32, (uint) data.size()); j++) { + out << hex_string(data[j], 2) << ", "; + } + out << "\n"; + } + }; + + std::vector> games; + + out << "#include \n"; + out << "#include \"../std.h\"\n"; + out << "#include \"" << entity << "s/" << entity << "s.h\"\n"; + + int default_index = -1; + for (int lnum = 1; std::getline(in, line); lnum++) { + line = trim(line); + if (!line.empty()) { + if (line.find_first_of('#') == 0) continue; + if (line.find_first_of('>') == 0) { + default_index = games.size(); + line = line.substr(1); + } + uint flags = 0; + auto starts_with_and_skip = [](string& line, string to_match) { + if (line.find(to_match) == 0) { + line = line.substr(to_match.length()); + return true; + } + return false; + }; + while (true) { + line = trim(line); + if (starts_with_and_skip(line, "SLOW_TAPE")) { + flags |= GF_SLOW_TAPE; + continue; + } + if (starts_with_and_skip(line, "NO_WAIT_VBLANK")) { + flags |= GF_NO_WAIT_VBLANK; + continue; + } + break; + } + size_t split = line.find_first_of('='); + if (split == string::npos) { + std::stringstream ss; + ss << "Expected to find = at line " << lnum << ": '" << line << "'"; + throw failure(ERROR_SYNTAX, ss.str()); + } + string name = trim(line.substr(0, split)); + string file = trim(line.substr(split + 1)); + if (name.empty() || file.empty()) { + std::stringstream ss; + ss << "Expected to find name = value at line " << lnum; + throw failure(ERROR_SYNTAX, ss.str()); + } + int index = games.size(); + if (!root_dir.empty()) { + path fs_path = root_dir; + fs_path /= file; + file = fs_path; + } + FILE *fp; + const char *fn = file.c_str(); + const char *ext; + ext = strrchr(fn, '.'); + if (!ext) { + std::stringstream ss; + ss << "Expected file extension on " << fn; + throw failure(ERROR_ARGS, ss.str()); + } + if ((fp = fopen(fn, "rb"))) { + const struct sdf_geometry *geo; + vector contents; + fseek(fp, 0, SEEK_END); + size_t size = ftell(fp); + fseek(fp, 0, SEEK_SET); + games.emplace_back(name, ext, size, flags); + contents.resize(size); + if (1 != fread(contents.data(), size, 1, fp)) { + throw failure(ERROR_FILE, "Failed to read file\n"); + } + auto compressed = compress(contents); + fclose(fp); + out << "static unsigned char __game_data " << entity << "_" << index << "_data_z[" << compressed.size() + << "] = {\n"; + dump(compressed); + out << "};\n"; + } else { + std::stringstream ss; + ss << "Can't open " << file; + throw failure(ERROR_FILE, ss.str()); + } + } + + } + + out << "embedded_" << entity << "_t embedded_" << entity << "s[" << games.size() << "] = {\n"; + int i = 0; + for (const auto &e : games) { + out << " { \"" << std::get<0>(e) << "\","; + if (is_game) out << " \"" << std::get<1>(e) << "\","; + out << entity << "_" << i << "_data_z, sizeof(" << entity << "_" << i << "_data_z), " << std::get<2>(e) << ""; + if (is_game) out << ", " << std::get<3>(e); + out << " },\n"; + i++; + } + out << "};\n"; + out << "int embedded_" << entity << "_count = " << games.size() << ";\n"; + out << "int embedded_" << entity << "_default = " << default_index << ";\n"; +} + +int main(int argc, char **argv) { + string in_filename, out_filename; + int rc = 0; + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'p': + if (i != argc - 1) { + root_dir = argv[++i]; + } + break; + case 'r': + is_rom = true; + break; + case 'g': + is_game = true; + break; + } + } else if (in_filename.empty()) { + in_filename = argv[i]; + } else if (out_filename.empty()) { + out_filename = argv[i]; + } else { + rc = ERROR_ARGS; + } + } + if (!is_rom && !is_game) { + printf("ERROR: must specify -r or -g\n"); + rc = ERROR_ARGS; + } + if (rc) { + usage(); + } else { + try { + std::ifstream ifile(in_filename); + if (!ifile.good()) { + std::stringstream ss; + ss << "Can't open " << in_filename; + throw failure(ERROR_FILE, ss.str()); + } + std::ofstream ofile(out_filename); + if (!ofile.good()) { + std::stringstream ss; + ss << "Can't open " << out_filename; + throw failure(ERROR_FILE, ss.str()); + } + doit(ifile, ofile); + } catch (failure &e) { + std::cerr << "ERROR: " << e.what(); + rc = e.code(); + } catch (std::exception &e) { + std::cerr << "ERROR: " << e.what(); + rc = ERROR_UNKNOWN; + } + if (rc) { + std::filesystem::remove(out_filename.c_str()); + } + } + return rc; +} + + +vector compress(const vector& raw) { + tdefl_status status; + static tdefl_compressor deflator; + + mz_uint comp_flags = TDEFL_WRITE_ZLIB_HEADER | 1500; + // Initialize the low-level compressor. + status = tdefl_init(&deflator, NULL, NULL, comp_flags); + if (status != TDEFL_STATUS_OKAY) { + throw failure(ERROR_UNKNOWN, "tdefl_init() failed!"); + } + + // just a hacky one pass compression without hsassling with growing buffers; + vector compressed(raw.size() * 2); + const void *next_in = raw.data(); + void *next_out = compressed.data(); + size_t in_bytes = raw.size(); + size_t out_bytes = compressed.size(); + status = tdefl_compress(&deflator, next_in, &in_bytes, next_out, &out_bytes, TDEFL_FINISH); + compressed.resize(out_bytes); + if (status != TDEFL_STATUS_DONE) { + throw failure(ERROR_UNKNOWN, "tdefl_compress failed!"); + } + return compressed; +} + diff --git a/file_type.cpp b/file_type.cpp index 3fb2e3e..44c77a8 100644 --- a/file_type.cpp +++ b/file_type.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2012 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,7 +33,8 @@ const eFileType* eFileType::FindByName(const char* name) while(ext >= name && *ext != '.') --ext; ++ext; - char type[xIo::MAX_PATH_LEN]; + assert(name + l + 1 - ext < 8); + char type[name + l + 1 - ext]; char* t = type; while(*ext) { @@ -47,6 +49,7 @@ const eFileType* eFileType::FindByName(const char* name) bool eFileType::Open(const char* name) const { +#ifndef USE_MU_SIMPLIFICATIONS for(const eFileType* t = First(); t; t = t->Next()) { char contain_path[xIo::MAX_PATH_LEN]; @@ -56,13 +59,15 @@ bool eFileType::Open(const char* name) const return t->Open(name); } } +#endif +#ifndef USE_EMBEDDED_FILES FILE* f = fopen(name, "rb"); if(f) { fseek(f, 0, SEEK_END); size_t size = ftell(f); fseek(f, 0, SEEK_SET); - byte* buf = new byte[size]; + byte* buf = (byte*)malloc(size); size_t r = fread(buf, 1, size, f); fclose(f); if(r != size) @@ -70,10 +75,18 @@ bool eFileType::Open(const char* name) const SAFE_DELETE_ARRAY(buf); return false; } +#ifndef USE_STREAM bool ok = Open(buf, size); - SAFE_DELETE_ARRAY(buf); +#else + struct stream *stream = memory_stream_open(buf, size, true); + bool ok = Open(stream); +#endif + free(buf); return ok; } +#else + assert(false); +#endif return false; } diff --git a/file_type.h b/file_type.h index 4ac5c40..bf2255c 100644 --- a/file_type.h +++ b/file_type.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2012 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +24,9 @@ along with this program. If not, see . #pragma once +#ifdef USE_STREAM +#include "stream.h" +#endif namespace xIo { class eFileSelect; @@ -35,7 +39,11 @@ namespace xPlatform struct eFileType : public eList { static const eFileType* First() { return _First(); } +#ifndef USE_STREAM virtual bool Open(const void* data, size_t data_size) const = 0; +#else + virtual bool Open(struct stream *stream) const = 0; +#endif virtual bool Open(const char* name) const; virtual bool Store(const char* name) const { return false; } virtual bool AbleOpen() const { return true; } diff --git a/khan/CMakeLists.txt b/khan/CMakeLists.txt new file mode 100644 index 0000000..5d2ca2e --- /dev/null +++ b/khan/CMakeLists.txt @@ -0,0 +1,306 @@ +add_library(khan_common INTERFACE) + +# this doesn't work +OPTION(KHAN128_I2S "(doesn't work) Use I2S rather than PWM for non beeper audio (khan128)") + +function(create_embed_file TARGET SOURCE_FILE TARGET_FILE) + if (NOT EmbedTool_FOUND) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR}) + find_package(EmbedTool) + endif() + + cmake_parse_arguments(embed "DISC;ROM" "PREFIX" "" ${ARGN} ) + + set(EMBED_TARGET "${TARGET}_embed") + + set(EMBED_ARGS "") + if (embed_ROM) + LIST(APPEND EMBED_ARGS "-r") + STRING(APPEND EMBED_TARGET "_roms") + else() + LIST(APPEND EMBED_ARGS "-g") + STRING(APPEND EMBED_TARGET "_games") + endif() + if (embed_PREFIX) + LIST(APPEND EMBED_ARGS "-p" "${embed_PREFIX}") + endif() + add_custom_target(${EMBED_TARGET} DEPENDS ${TARGET_FILE}) + + add_custom_command(OUTPUT ${TARGET_FILE} + DEPENDS ${SOURCE_FILE} + COMMAND EmbedTool ${EMBED_ARGS} ${SOURCE_FILE} ${TARGET_FILE} + ) + add_dependencies(${TARGET} ${EMBED_TARGET}) + + message("COMMAND EmbedTool ${EMBED_ARGS} ${SOURCE_FILE} ${TARGET_FILE}") + target_sources(${TARGET} PRIVATE ${TARGET_FILE}) + target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +endfunction() + +# NEED CUSTOM ALLOCATOR for USB memory + +pico_generate_pio_header(khan_common ${CMAKE_CURRENT_LIST_DIR}/khan.pio) + +target_sources(khan_common INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/khan.c + ${CMAKE_CURRENT_LIST_DIR}/khan_init.cpp + ${CMAKE_CURRENT_LIST_DIR}/khan_lib.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui_font.cpp + ${CMAKE_CURRENT_LIST_DIR}/../file_type.cpp + ${CMAKE_CURRENT_LIST_DIR}/../options_common.cpp + ${CMAKE_CURRENT_LIST_DIR}/../speccy.cpp + ${CMAKE_CURRENT_LIST_DIR}/../speccy_handler.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/device.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/memory.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/ula.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/input/kempston_joy.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/input/keyboard.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/input/tape.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/sound/ay.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/sound/beeper.cpp + ${CMAKE_CURRENT_LIST_DIR}/../devices/sound/device_sound.cpp + ${CMAKE_CURRENT_LIST_DIR}/../platform/io.cpp + ${CMAKE_CURRENT_LIST_DIR}/../platform/platform.cpp + ${CMAKE_CURRENT_LIST_DIR}/../snapshot/snapshot.cpp + ${CMAKE_CURRENT_LIST_DIR}/../tools/options.cpp + ) + +if (PICO_ON_DEVICE) + target_sources(khan_common INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/z80arm.cpp + ${CMAKE_CURRENT_LIST_DIR}/z80khan.S + ${CMAKE_CURRENT_LIST_DIR}/fast_ay.S + ) + target_link_libraries(khan_common INTERFACE + hardware_interp + ) +else() + target_sources(khan_common INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/spoon.cpp + ${CMAKE_CURRENT_LIST_DIR}/../z80/z80.cpp + ${CMAKE_CURRENT_LIST_DIR}/../z80/z80_opcodes.cpp + ${CMAKE_CURRENT_LIST_DIR}/../z80/z80_op_tables.cpp + ${CMAKE_CURRENT_LIST_DIR}/z80t.cpp + ) +endif() + +# ------------------------------------------------------------------------------- +# system wide definitions +# ------------------------------------------------------------------------------- +target_compile_definitions(khan_common INTERFACE + #COLLAPSE_IRQS + #NO_USE_SIM + PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT=4 + PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS=76 + PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS=48 + PICO_SCANVIDEO_48MHZ=1 + PICO_HEAP_SIZE=0x400 + PICO_STACK_SIZE=0x380 + ENABLE_COMPRESSED_STREAM + $<$:NDEBUG> + #PICO_DEBUG_MALLOC + PICO_USE_OPTIMISTIC_SBRK + + PICO_SCANVIDEO_PLANE_COUNT=2 + +) + +add_library(khan128_core INTERFACE) + +if (KHAN128_I2S) + target_compile_definitions(khan128_core INTERFACE + PICO_AUDIO_I2S_PIO=1 + PICO_AUDIO_I2S_DMA_IRQ=1 + PICO_AUDIO_I2S_MONO_INPUT=1 + PICO_AUDIO_I2S_MONO_OUTPUT=1 + KHAN128_I2S=1 + # this is a little over enough for 1/50s of audio at 24000Hz + PICO_AUDIO_BUFFER_SAMPLE_LENGTH=484 + ) +else() + target_compile_definitions(khan128_core INTERFACE + PICO_AUDIO_PWM_PIO=1 + PICO_AUDIO_PWM_INTERP_SAVE=1 + # todo trying to shrink stuff down a bit - may not work + PICO_AUDIO_BUFFERS_PER_CHANNEL=2 + # this is a little over enough for 1/50s of audio at 22058Hz + PICO_AUDIO_BUFFER_SAMPLE_LENGTH=448 + ) +endif() +target_compile_definitions(khan128_core INTERFACE + # for use without NO_USE_BEEPER + #PIO_PWM_IRQ_FUNC_NAME=delegate_pwm_irq + BLOCKING_GIVE_SYNCHRONIZE_BUFFERS + + PICO_PRINTF_SUPPORT_FLOAT=0 + PICO_PRINTF_SUPPORT_LONG_LONG=0 + PICO_PRINTF_SUPPORT_PTRDIFF_T=0 + + PICO_SCANVIDEO_MISSING_SCANLINE_COLOR=0 + + PICO_TIME_DEFAULT_ALARM_POOL_DISABLED=1 +) + +if (PICO_ON_DEVICE) + target_compile_definitions(khan128_core INTERFACE + PICO_BUFFER_USB_ALLOC_HACK=1 + ) +endif() + +# ------------------------------------------------------------------------------- +# khan specifics +# ------------------------------------------------------------------------------- +target_compile_definitions(khan_common INTERFACE + USE_MU + USE_KHAN_GPIO + USE_Z80_ARM_OFFSET_T + USE_STREAM + USE_MU_SIMPLIFICATIONS + USE_HACKED_DEVICE_ABSTRACTION + USE_LARGER_FASTER_CB + # we still want to be able to load off disk for native + $<$:USE_EMBEDDED_FILES> + +# ENABLE_BREAKPOINT_IN_DEBUG +# NO_USE_TAPE +# NO_USE_FAST_TAPE +# NO_UPDATE_RLOW_IN_FETCH + + NO_USE_DESTRUCTORS + # thse two have to be used together actually! + NO_USE_SCREEN + + NO_USE_FDD + #NO_USE_KEMPSTON + NO_USE_REPLAY + NO_USE_DOS + NO_USE_SERVICE_ROM + NO_USE_SAVE + NO_USE_CSW + NO_USE_TZX + NO_USE_SZX +# NO_USE_Z80 +# NO_USE_SNA + ) + +target_compile_definitions(khan128_core INTERFACE + #for now don't use both + NO_USE_BEEPER + #$<$:NO_USE_AY> + $<$:USE_FAST_AY> + USE_OVERLAPPED_ROMS + # this simply refers to use of banks by the z80 runtime; it is required for NO_USE_128K + $<$:USE_BANKED_MEMORY_ACCESS> + ) + +if (NOT PICO_NO_FLASH) + target_compile_definitions(khan_common INTERFACE + USE_COMPRESSED_ROM + USE_XIP_STREAM + ) +endif() + +#target_compile_options(khan_common INTERFACE -Wall -Werror) + +add_library(miniz INTERFACE) + +set(MINIZ_DIR ${CMAKE_CURRENT_LIST_DIR}/../3rdparty/miniz) +target_sources(miniz INTERFACE + ${MINIZ_DIR}/miniz_tinfl.c + ) + +target_include_directories(miniz INTERFACE ${MINIZ_DIR}) + +target_link_libraries(khan_common INTERFACE pico_stdlib pico_scanvideo_dpi miniz stream pico_multicore) + +function(add_khan_exe NAME GAME_FILE ROM_FILE USE_USB) + set(USE_USB_KEYBOARD ${USE_USB}) + add_executable(${NAME}) + set(OVERRIDE_GAME_FILE ${NAME}_games) + if (DEFINED ${OVERRIDE_GAME_FILE}) + set(GAME_FILE ${OVERRIDE_GAME_FILE}) + endif() + set(OVERRIDE_ROM_FILE ${NAME}_roms) + if (DEFINED ${OVERRIDE_ROM_FILE}) + set(ROM_FILE ${OVERRIDE_ROM_FILE}) + endif() + pico_set_float_implementation(${NAME} none) + pico_set_double_implementation(${NAME} none) + pico_enable_stdio_uart(${NAME} 1) # just use raw uart + + get_filename_component(GAME_FILE ${GAME_FILE} ABSOLUTE) + + # todo override prefix + create_embed_file(${NAME} ${GAME_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${NAME}_games.cpp PREFIX ${CMAKE_CURRENT_LIST_DIR}/../../unrealspeccyp/res) + get_filename_component(ROM_FILE ${ROM_FILE} ABSOLUTE) + create_embed_file(${NAME} ${ROM_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${NAME}_roms.cpp PREFIX ${CMAKE_CURRENT_LIST_DIR}/../../unrealspeccyp/res/rom ROM) + pico_add_extra_outputs(${NAME}) + + if (PICO_ON_DEVICE) + if (USE_USB_KEYBOARD OR USE_USB_MOUSE) + target_link_libraries(${NAME} PRIVATE tinyusb_host) + + target_compile_definitions(${NAME} PRIVATE + USB_DPRAM_MAX=768 # needs to be big + PICO_USB_HOST_INTERRUPT_ENDPOINTS=3 + ) + endif() + if (USE_USB_KEYBOARD) + target_compile_definitions(${NAME} PRIVATE USE_USB_KEYBOARD) + endif() + if (USE_USB_MOUSE) + target_compile_definitions(${NAME} PRIVATE USE_USB_MOUSE) + endif() + if (NOT PICO_NO_FLASH) + pico_set_linker_script(${NAME} ${CMAKE_CURRENT_LIST_DIR}/memmap_khan.ld) + endif() + endif() +endfunction() + +add_khan_exe(khan games/khan_games.txt roms/khan_roms.txt 0) +add_khan_exe(khan128 games/khan128_games.txt roms/khan128_roms.txt 0) +add_khan_exe(khan128_turbo games/khan128_turbo_games.txt roms/khan128_roms.txt 0) +add_khan_exe(khan_usb games/khan_games.txt roms/khan_roms.txt 1) +add_khan_exe(khan128_usb games/khan128_games.txt roms/khan128_roms.txt 1) +add_khan_exe(khan128_turbo_usb games/khan128_turbo_games.txt roms/khan128_roms.txt 1) + +target_compile_definitions(khan PRIVATE + USE_SINGLE_64K_MEMORY # note this is ignored with USE_COMPRESSED_ROM + NO_USE_128K + NO_USE_AY + ) +target_compile_definitions(khan_usb PRIVATE + USE_SINGLE_64K_MEMORY # note this is ignored with USE_COMPRESSED_ROM + NO_USE_128K + NO_USE_AY + ) + +if (PICO_ON_DEVICE) +# target_compile_definitions(khan_common INTERFACE USE_UART_KEYBOARD) # regular UART Input + target_compile_definitions(khan_common INTERFACE USE_SDL_EVENT_FORWARDER_INPUT) # support SDL event forwarder + target_compile_definitions(khan_common INTERFACE USE_USB_INPUT) # support SDL event forwarder + target_compile_definitions(khan_common INTERFACE USE_Z80_ARM) + # target_compile_definitions(khan_common INTERFACE USE_LEGACY_TAPE_COMPARISON) +endif() + +target_compile_options(khan_common INTERFACE -g) +target_link_libraries(khan PRIVATE khan_common) +target_link_libraries(khan_usb PRIVATE khan_common) + +if (KHAN128_I2S) + target_link_libraries(khan128_core INTERFACE khan_common pico_audio_i2s) +else() + target_link_libraries(khan128_core INTERFACE khan_common pico_audio_pwm) +endif() + +target_link_libraries(khan128 PRIVATE khan128_core) +target_link_libraries(khan128_turbo PRIVATE khan128_core) +target_link_libraries(khan128_usb PRIVATE khan128_core) +target_link_libraries(khan128_turbo_usb PRIVATE khan128_core) + +target_compile_definitions(khan128_turbo PRIVATE + SPEEDUP=3 +) +target_compile_definitions(khan128_turbo_usb PRIVATE + SPEEDUP=3 + ) diff --git a/khan/FindEmbedTool.cmake b/khan/FindEmbedTool.cmake new file mode 100644 index 0000000..7f5fde4 --- /dev/null +++ b/khan/FindEmbedTool.cmake @@ -0,0 +1,44 @@ +# Finds (or builds) the EmbedTool executable +# +# This will define the following variables +# +# EmbedTool_FOUND +# +# and the following imported targets +# +# EmbedTool +# + +if (NOT EmbedTool_FOUND) + # todo we would like to use pckgconfig to look for it first + # see https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/ + + include(ExternalProject) + + set(EmbedTool_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../embed_tool) + set(EmbedTool_BINARY_DIR ${CMAKE_BINARY_DIR}/embed_tool) + + set(EmbedTool_BUILD_TARGET EmbedToolBuild) + set(EmbedTool_TARGET EmbedTool) + + if (NOT TARGET ${EmbedTool_BUILD_TARGET}) + message("EmbedTool will need to be built") + ExternalProject_Add(${EmbedTool_BUILD_TARGET} + PREFIX embed_tool + SOURCE_DIR ${EmbedTool_SOURCE_DIR} + BINARY_DIR ${EmbedTool_BINARY_DIR} + BUILD_ALWAYS 1 # force dependency checking + INSTALL_COMMAND "" + ) + endif() + + set(EmbedTool_EXECUTABLE ${EmbedTool_BINARY_DIR}/embed_tool) + if(NOT TARGET ${EmbedTool_TARGET}) + add_executable(${EmbedTool_TARGET} IMPORTED) + endif() + set_property(TARGET ${EmbedTool_TARGET} PROPERTY IMPORTED_LOCATION + ${EmbedTool_EXECUTABLE}) + + add_dependencies(${EmbedTool_TARGET} ${EmbedTool_BUILD_TARGET}) + set(EmbedTool_FOUND 1) +endif() diff --git a/khan/fast_ay.S b/khan/fast_ay.S new file mode 100644 index 0000000..3120d6b --- /dev/null +++ b/khan/fast_ay.S @@ -0,0 +1,709 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +.cpu cortex-m0 +.thumb +.syntax divided +// todo corret volume ramps .. pretty cheap really + +#define OFFSET_BITS_A03X 0 +#define OFFSET_BITS_B14X 1 +#define OFFSET_BITS_C25X 2 +#define OFFSET_BIT_N 3 + +#define OFFSET_CH_F_A 6 +#define OFFSET_CH_F_B 8 +#define OFFSET_CH_F_C 10 +#define OFFSET_CH_F_N 12 +#define OFFSET_CH_F_E 14 + +#define OFFSET_A_OUTPUT 16 +#define OFFSET_B_OUTPUT 18 +#define OFFSET_C_OUTPUT 20 + +#define OFFSET_A_VOL 22 +#define OFFSET_B_VOL 24 +#define OFFSET_C_VOL 26 +#define OFFSET_E_VOL 28 +// must be next to e_vol +#define OFFSET_DE_VOL 30 + +#define OFFSET_MASTER_VOLUME 32 +#define OFFSET_NS 36 + +// counter state 16 bytes +#define OFFSET_COUNTER_STATE 48 +#define OFFSET_T_PAIRS 48 +#define OFFSET_TA 48 +#define OFFSET_TB 50 +#define OFFSET_TC 52 +#define OFFSET_TM 54 +#define OFFSET_TE 56 +#define OFFSET_TT 58 +#define OFFSET_10001 60 + +// sample state 16 bytes +#define OFFSET_SAMPLE_STATE 64 +#define OFFSET_FRAC_MAJOR 64 +#define OFFSET_FRAC_MINOR 68 +#define OFFSET_FRAC_ACCUM 72 +#define OFFSET_OVER_C_VALUE 76 +#define OFFSET_OVER_REM 80 +#define OFFSET_OVER_ACCUM 84 + +#define OFFSET_WAKEUPS 88 + +#define OFFSET_CH_VOL 0 +#define OFFSET_CH_VALUE 0 + + +#define r_state r0 + +#define r_t_ab r1 +#define r_sample_ptr r1 + +#define r_t_cn r2 +#define r_frac_major r2 + +#define r_t_et r3 +#define r_frac_minor r3 + +#define r_frac_accum r4 + +#define r_over_c_value r5 + +#define r_over_rem r6 + +#define r_hi_counter_loop_breakout r8 + +#define r_hi_saved_sample_ptr r10 +// r6, tmp +// r7 0x10001 +#.section .data_fast_ay,"x" + +//#define SANIT_AY + +.macro debug_break +// bkpt #0 +.endm + +.macro check_over_rem +#ifdef SANIT_AY + cmp r_over_rem, #0 + bge over_rem_ok\@ + bkpt #0 +over_rem_ok\@: +#endif +.endm + +buffer_full1: +#ifdef SANIT_AY + bkpt #0 // shouldn't happen any more (but still does very rarely atm :-( ) +#endif +// mov r10, r1 +// mov r1, r_state +// add r1, #OFFSET_SAMPLE_STATE + 8 +// stmia r1!, {r_frac_accum, r_over_c_value, r_over_rem} +buffer_full2: +#ifdef SANIT_AY + bkpt #0 // shouldn't happen any more (but still does very rarely atm :-( ) +#endif + + mov r0, #0 + str r0, [r1, #OFFSET_OVER_REM] // no more samples + +all_done: + debug_break + mov r0, r10 + pop {r1, r4-r7} + sub r0, r1 + lsr r0, #1 + mov r8, r4 + mov r9, r5 + mov r10, r6 + mov r11, r7 + pop {r4-r7, pc} + +.align 2 +.global fast_ay +.type fast_ay,%function +.thumb_func +fast_ay: + push {r4-r7, lr} + mov r4, r8 + mov r5, r9 + mov r6, r10 + mov r7, r11 + push {r1, r4-r7} + + add r2, r2 + add r2, r1 + mov r9, r2 + + // save original sample_ptr + mov r10, r1 + // note we now expect that over_rem is always 0 on entry +#ifdef SANIT_AY + ldr r7, [r_state, #OFFSET_OVER_REM] + cmp r7, #0 + beq 1f + bkpt #0 +1: +#endif + // so we jump straight to counter loop (kept code in + // this order because it makes branches in range) + b counter_loop_enter + +flush_samples: + debug_break + +// r0 - state +// r1 - sample_ptr +// r2 - frac_major +// r3 - frac_minor +// r4 - frac_accum +// r5 - oc_v (aa00 0000 0000 0000 bbbb bbbb bbbb bbbb - oc = oversample_count 0-3, v = value to write) +// r6 - over_rem +// r7 - +// r8 - next_value (or -1) +// r9 - end of sample buffer +// r10 - saved sample ptr +// r11 - saved done flag + + mov r7, r_state + add r7, #OFFSET_SAMPLE_STATE + ldmia r7!, {r_frac_major, r_frac_minor, r_frac_accum, r_over_c_value, r_over_rem} + check_over_rem +// bkpt #0 + + cmp r_over_rem, #0 + beq no_flush + +// debug_break + + lsr r7, r_over_c_value, #30 + beq sample_aligned + + + mov r8, r2 + ldr r2, [r_state, #OFFSET_OVER_ACCUM] + +2: + sub r_over_rem, #1 +1: + add r2, r_over_c_value + add r7, #1 + cmp r7, #4 + beq 2f + cmp r_over_rem, #0 + bne 2b +2: + lsl r_over_c_value, #2 + lsr r_over_c_value, #2 + lsl r7, #30 + orr r_over_c_value, r7 + cmp r7, #0 + bne no_full_sample + + lsr r2, #2 // (poorly) filtered sample + + check_over_rem + + sub r_frac_accum, r_frac_minor + bge 1f + // r2 is r_frac_major, so we need to reload into now spare reg r7 + mov r7, r8 + add r_frac_accum, r7 + strh r2, [r1] + add r1, #2 + cmp r1, r9 + bhs buffer_full1 +1: + + mov r2, #0 + str r2, [r_state, #OFFSET_OVER_ACCUM] + +no_full_sample: + mov r2, r8 +sample_aligned: +1: + sub r_over_rem, #4 + bmi 3f +2: + sub r_frac_accum, r_frac_minor + bge 1b + add r_frac_accum, r_frac_major + strh r_over_c_value, [r1] + add r1, #2 + check_over_rem + cmp r1, r9 + bhs buffer_full2 + sub r_over_rem, #4 + bpl 2b +3: + // save sample ptr + mov r10, r1 + + debug_break + + // save and mask off top 2 bits + lsr r7, r_over_c_value, #30 + lsl r7, r7, #30 + eor r_over_c_value, r7 + + // set r1 to % 4 (i.e. what is left) + lsl r_over_rem, #30 + lsr r1, r_over_rem, #30 + + beq no_fraction + +#ifdef SANIT_AY + push {r6} + uxth r6, r_over_c_value + cmp r6, r_over_c_value + beq 1f + bkpt #0 +1: + pop {r6} +#endif + // calculate accumulation based on remaining bits + mul r1, r_over_c_value + + // use r_over_c_value as temp here + ldr r_over_c_value, [r_state, #OFFSET_OVER_ACCUM] + add r1, r_over_c_value +#ifdef SANIT_AY + push {r6} + lsl r6, r1, #14 + lsr r6, r6, #14 + cmp r6, r1 + beq 1f + bkpt #0 +1: + pop {r6} +#endif + str r1, [r_state, #OFFSET_OVER_ACCUM] +no_fraction: + + ldr r3, =current_voltab + ldr r3, [r3] + + // recalculate sample value + ldr r1, [r_state, #OFFSET_A_OUTPUT] // AB + lsl r1, #1 + lsr r2, r1, #16 // B*2 + uxth r1, r1 // A*2 + ldrh r1, [r3, r1] + ldrh r2, [r3, r2] + add r1, r2 + ldrh r_over_c_value, [r_state, #OFFSET_C_OUTPUT] + lsl r_over_c_value, #1 + ldrh r_over_c_value, [r3, r_over_c_value] + add r_over_c_value, r1 + ldr r1, [r_state, #OFFSET_MASTER_VOLUME] + mul r_over_c_value, r1 + lsr r_over_c_value, #6 + 4 // todo could be part of volume table + + orr r_over_c_value, r7 + add r_over_c_value, r_over_rem +#ifdef SANIT_AY + bcc 1f + bkpt #0 // if there were enough remaining to fill a whole oversample bucket, we should have handled it at the top +1: +#endif + mov r_over_rem, #0 + + mov r1, r_state + add r1, #OFFSET_FRAC_ACCUM + check_over_rem + //bkpt #0 + stmia r1!, {r_frac_accum, r_over_c_value, r_over_rem} + +no_flush: + // tt + mov r6, r11 + cmp r6, #0 + beq all_done + + debug_break +counter_loop_enter: + // simple for now just load register bank + mov r6, r_state + add r6, #OFFSET_COUNTER_STATE + ldmia r6!, {r_t_ab, r_t_cn, r_t_et, r7} + + lsr r4, r_t_et, #16 +#ifdef SANIT_AY + bne 1f + bkpt #0 +1: +#endif + mov r_hi_counter_loop_breakout, r7 // set to 0x10001 which is bigger than samples + // r0 - state + // r1 - t_ab + // r2 - t_cn + // r3 - t_ey + // r4 - tt original + // r5 - scratch + // r6 - scratch + // r7 - 0x10001 + // r8 - breakout_flag + // r9 - end of sample buffer + // r10 - saved sample pltr + +counter_loop: + sub r_t_ab, r7 + lsl r6, r_t_ab, #16 + beq a_done +.thumb_func +check_b: + lsr r6, r_t_ab, #16 + beq b_done +.thumb_func +check_c: + sub r_t_cn, r7 + lsl r6, r_t_cn, #16 + beq c_done +.thumb_func +check_n: + lsr r6, r_t_cn, #16 + beq n_done +.thumb_func +check_e: + sub r_t_et, r7 + lsl r6, r_t_et, #16 + beq e_done +.thumb_func +check_t: + lsr r6, r_t_et, #16 + beq save_counter_state + cmp r6, r_hi_counter_loop_breakout + blo counter_loop + + // here we broke out because something changed + +save_counter_state: + debug_break + + // get number of cycles + lsr r7, r_t_et, #16 + sub r4, r7 +#ifdef SANIT_AY + bhs 1f + bkpt #0 +1: +#endif + + // save tt + mov r11, r6 + +#ifdef SANIT_AY + ldr r5, [r_state, #OFFSET_OVER_REM] + cmp r5, #0 + beq 1f + bkpt #0 // wrong +1: +#endif + str r4, [r_state, #OFFSET_OVER_REM] + + mov r5, r_state + add r5, #OFFSET_COUNTER_STATE + stmia r5!, {r_t_ab, r_t_cn, r_t_et} + + // load sample ptr + mov r1, r10 + + b flush_samples + +// add r4, r_state, #4 +// ldr r6, [r4, #OFFSET_WAKEUPS - 4] +// add r6, #1 +// str r6, [r4, #OFFSET_WAKEUPS - 4] + + +zero_fa: + // set ta to 0xffff + sub r6, r7, #2 + add r_t_ab, r6 + b check_b +a_done: + mov r8, r6 + ldrh r6, [r_state, #OFFSET_CH_F_A] + // if the frequency is 0 we do nothing - and we will wrap to + cmp r6, #0 + beq zero_fa + add r6, #1 + add r_t_ab, r6 + ldrb r6, [r_state, #OFFSET_BITS_A03X] + mov r5, #8 + eor r6, r5 + strb r6, [r_state, #OFFSET_BITS_A03X] + ldr r6, =check_b + mov lr, r6 + b update_a + +zero_fb: + // set tb to 0xffff + sub r_t_ab, r7 + add r_t_ab, #1 + b check_c +b_done: + mov r8, r6 + ldrh r6, [r_state, #OFFSET_CH_F_B] + // if the frequency is 0 we do nothing - and we will wrap to + lsl r6, #16 + beq zero_fb + add r_t_ab, r6 + add r_t_ab, r7 + sub r_t_ab, #1 + ldrb r6, [r_state, #OFFSET_BITS_B14X] + mov r5, #8 + eor r6, r5 + strb r6, [r_state, #OFFSET_BITS_B14X] + ldr r6, =check_c + mov lr, r6 + b update_b + +zero_fc: + // set tc to 0xffff + sub r6, r7, #2 + add r_t_cn, r6 + b check_n +c_done: + mov r8, r6 + ldrh r6, [r_state, #OFFSET_CH_F_C] + // if the frequency is 0 we do nothing - and we will wrap to + cmp r6, #0 + beq zero_fc + add r6, #1 + add r_t_cn, r6 + ldrb r6, [r_state, #OFFSET_BITS_C25X] + mov r5, #8 + eor r6, r5 + strb r6, [r_state, #OFFSET_BITS_C25X] + ldr r6, =check_n + mov lr, r6 + b update_c + +zero_fn: + // set tn to 0xffff + sub r_t_cn, r7 + add r_t_cn, #1 + b check_e +n_done: + mov r8, r6 + ldrh r6, [r_state, #OFFSET_CH_F_N] + // if the frequency is 0 we do nothing - and we will wrap to + lsl r6, #16 + beq zero_fn + add r_t_cn, r6 + add r_t_cn, r7 + sub r_t_cn, #1 + + // ns = (ns * 2 + 1) ^ (((ns >> 16) ^ (ns >> 13)) & 1), + // bitN = 0 - ((ns >> 16) & 1); + + ldr r5, [r_state, #OFFSET_NS] + lsl r6, r5, #3 + eor r6, r5 + lsl r6, #15 + lsr r6, #31 + add r5, r5 + add r5, #1 + eor r5, r6 + str r5, [r_state, #OFFSET_NS] + lsl r5, #15 + lsr r5, #31 + strb r5, [r_state, #OFFSET_BIT_N] + + // todo inline? + bl update_a + bl update_b + bl update_c + b check_e + +zero_fe: + // set te to 0xffff + sub r6, r7, #2 + add r_t_et, r6 + b check_t +e_done: + mov r8, r6 + ldrh r6, [r_state, #OFFSET_CH_F_E] + // if the frequency is 0 we do nothing - and we will wrap to + cmp r6, #0 + beq zero_fe + add r6, #1 + add r_t_et, r6 + + // update envelope + ldr r5, [r_state, #OFFSET_E_VOL] + lsr r6, r5, #16 // DE_VOL + add r5, r6 + lsl r5, #27 + lsr r5, #27 + beq 1f + ldr r6, =envelope_handler + ldr r6, [r6] + blx r6 + strh r5, [r_state, #OFFSET_E_VOL] +1: + + // todo inline? + bl update_a + bl update_b + bl update_c + b check_t + +// update net effect of channel a +update_a: + ldrb r6, [r_state, #OFFSET_BITS_A03X] + lsr r5, r6, #5 + bcs 1f + ldrb r5, [r_state, #OFFSET_BIT_N] + add r6, r5 + ldr r5, =channel_logic + ldrb r6, [r5, r6] + ldrh r5, [r_state, #OFFSET_A_VOL] + mul r6, r5 + strh r6, [r_state, #OFFSET_A_OUTPUT] + bx lr +1: + ldrb r5, [r_state, #OFFSET_BIT_N] + add r6, r5 + ldr r5, =channel_logic - 16 + ldrb r6, [r5, r6] + ldrh r5, [r_state, #OFFSET_E_VOL] + mul r6, r5 + strh r6, [r_state, #OFFSET_A_OUTPUT] + bx lr + +// update net effect of channel b +update_b: + ldrb r6, [r_state, #OFFSET_BITS_B14X] + lsr r5, r6, #5 + bcs 1f + ldrb r5, [r_state, #OFFSET_BIT_N] + add r6, r5 + ldr r5, =channel_logic + ldrb r6, [r5, r6] + ldrh r5, [r_state, #OFFSET_B_VOL] + mul r6, r5 + strh r6, [r_state, #OFFSET_B_OUTPUT] + bx lr +1: + ldrb r5, [r_state, #OFFSET_BIT_N] + add r6, r5 + ldr r5, =channel_logic - 16 + ldrb r6, [r5, r6] + ldrh r5, [r_state, #OFFSET_E_VOL] + mul r6, r5 + strh r6, [r_state, #OFFSET_B_OUTPUT] + bx lr + +// update net effect of channel c +update_c: + ldrb r6, [r_state, #OFFSET_BITS_C25X] + lsr r5, r6, #5 + bcs 1f + ldrb r5, [r_state, #OFFSET_BIT_N] + add r6, r5 + ldr r5, =channel_logic + ldrb r6, [r5, r6] + ldrh r5, [r_state, #OFFSET_C_VOL] + mul r6, r5 + strh r6, [r_state, #OFFSET_C_OUTPUT] + bx lr +1: + ldrb r5, [r_state, #OFFSET_BIT_N] + add r6, r5 + ldr r5, =channel_logic - 16 + ldrb r6, [r5, r6] + ldrh r5, [r_state, #OFFSET_E_VOL] + mul r6, r5 + strh r6, [r_state, #OFFSET_C_OUTPUT] + bx lr + +.thumb_func +envelope_wrap: + // already wrapped + bx lr + +.thumb_func +envelope_floor: + mov r5, #0 + strh r5, [r_state, #OFFSET_DE_VOL] + bx lr + +.thumb_func +envelope_ceil: + mov r5, #0 + strh r5, [r_state, #OFFSET_DE_VOL] + mov r5, #31 + bx lr + +.thumb_func +envelope_bounce: + ldrh r6, [r_state, #OFFSET_DE_VOL] + neg r6, r6 + strh r6, [r_state, #OFFSET_DE_VOL] + add r5, r6 + bx lr + + +.align 2 +channel_logic: + .byte 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1 + + // dword mask = (1 << r.env); + // if (mask & + // ((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 9) | + // (1 << 15))) + // env = denv = 0; + // else if (mask & ((1 << 8) | (1 << 12))) + // env &= 31; + // else if (mask & ((1 << 10) | (1 << 14))) + // denv = -denv, env = env + denv; + // else env = 31, denv = 0; //11,13 +.global envelope_switch +envelope_switch: + .word envelope_floor // 0 + .word envelope_floor // 1 + .word envelope_floor // 2 + .word envelope_floor // 3 + .word envelope_floor // 4 + .word envelope_floor // 5 + .word envelope_floor // 6 + .word envelope_floor // 7 + .word envelope_wrap // 8 + .word envelope_floor // 9 + .word envelope_bounce // 10 + .word envelope_ceil // 11 + .word envelope_wrap // 12 + .word envelope_ceil // 13 + .word envelope_bounce // 14 + .word envelope_floor // 15 + +.global voltab_ay +voltab_ay: + +.hword 0x0000,0x0000,0x0340,0x0340,0x04C0,0x04C0,0x06F2,0x06F2,0x0A44,0x0A44,0x0F13,0x0F13,0x1510,0x1510,0x227E,0x227E +.hword 0x289F,0x289F,0x414E,0x414E,0x5B21,0x5B21,0x7258,0x7258,0x905E,0x905E,0xB550,0xB550,0xD7A0,0xD7A0,0xFFFF,0xFFFF + +.global voltab_ym +voltab_ym: +.hword 0x0000,0x0000,0x00EF,0x01D0,0x0290,0x032A,0x03EE,0x04D2,0x0611,0x0782,0x0912,0x0A36,0x0C31,0x0EB6,0x1130,0x13A0 +.hword 0x1751,0x1BF5,0x20E2,0x2594,0x2CA1,0x357F,0x3E45,0x475E,0x5502,0x6620,0x7730,0x8844,0xA1D2,0xC102,0xE0A2,0xFFFF + +.global envelope_switch + +.section .data +.global current_voltab +current_voltab: +.word voltab_ay +.global envelope_handler +envelope_handler: +.word envelope_floor \ No newline at end of file diff --git a/khan/games/games.h b/khan/games/games.h new file mode 100644 index 0000000..0abe281 --- /dev/null +++ b/khan/games/games.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef GAMES_H +#define GAMES_H + +#define GF_SLOW_TAPE 1 +#define GF_NO_WAIT_VBLANK 2 + +typedef struct { + const char *name; + const char *ext; + const uint8_t *data_z; + const uint data_z_size; + const uint data_size; + uint8_t flags; +} embedded_game_t; + +extern embedded_game_t embedded_games[]; +extern int embedded_game_count; +extern int embedded_game_default; // can default to -1 +#endif diff --git a/khan/games/khan128_games.txt b/khan/games/khan128_games.txt new file mode 100644 index 0000000..810ea2f --- /dev/null +++ b/khan/games/khan128_games.txt @@ -0,0 +1,15 @@ +chron128 = CHRON128.sna +aykaramba = ay2.z80 +#doomdemo = DOOM_PD.Z80 +#tcats = thundercats.tap +#manic = Manic_Miner_1983_Software_Projects.z80 +#pac-3d = 3D_Pacman_1983_Freddy_Kristiansen.z80 +#k-lore = knightlore.sna +#jsw = JETSET.TAP +#starwars = Star_Wars_1987_Domark.z80 +hobbit = Hobbit_The_v1.2_1982_Melbourne_House_a.z80 +#aticatac = aticatac.tap +#dandare = dandare1.tap +bitbang = bitbang.tap + + diff --git a/khan/games/khan128_turbo_games.txt b/khan/games/khan128_turbo_games.txt new file mode 100644 index 0000000..09110e7 --- /dev/null +++ b/khan/games/khan128_turbo_games.txt @@ -0,0 +1,3 @@ +down = down_abc.tap +shock = shock.tap +NO_WAIT_VBLANK z80full = z80full.tap diff --git a/khan/games/khan_games.txt b/khan/games/khan_games.txt new file mode 100644 index 0000000..d3f6527 --- /dev/null +++ b/khan/games/khan_games.txt @@ -0,0 +1,13 @@ +# >, SLOW_TAPE, NO_WAIT_VBLANK +# todo no flash? +SLOW_TAPE zxpico = zxpico.tap +manic = Manic_Miner_1983_Software_Projects.z80 +pac-3d = 3D_Pacman_1983_Freddy_Kristiansen.z80 +k-lore = knightlore.sna +jsw = JETSET.TAP +starwars = Star_Wars_1987_Domark.z80 +hobbit = Hobbit_The_v1.2_1982_Melbourne_House_a.z80 +aticatac = aticatac.tap +dandare = dandare1.tap +bitbang = bitbang.tap + diff --git a/khan/khan.c b/khan/khan.c new file mode 100644 index 0000000..cb20f4a --- /dev/null +++ b/khan/khan.c @@ -0,0 +1,1767 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico/stdlib.h" +#include "pico/scanvideo.h" +#include "pico/multicore.h" +#include "khan_lib.h" +#if PICO_ON_DEVICE +#include "hardware/clocks.h" +#include "hardware/pio.h" +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "sdl_keys.h" +#endif +#include "khan_init.h" +#include "khan.pio.h" +#include "hardware/sync.h" +#include "pico/binary_info.h" + +#if defined(USE_USB_KEYBOARD) || defined(USE_USB_MOUSE) +#define USE_USB 1 +#define USB_ON_CORE1 1 +#include "tusb.h" +#include "hardware/irq.h" +#endif + +#include "pico/binary_info.h" +#ifdef USE_USB_KEYBOARD +bi_decl(bi_program_feature("USB keyboard support")); +#endif +#ifdef USE_USB_MOUSE +bi_decl(bi_program_feature("USB mouse support")); +#endif +#ifdef USE_SDL_EVENT_FORWARDER_INPUT +bi_decl(bi_program_feature("SDL event forwarder support")); +#endif +#ifdef USE_UART_KEYBOARD +bi_decl(bi_program_feature("Raw UART keyboard support (limited)")); +#endif +//#define HACK_NON_CIRCULAR +#define ENABLE_MENU_OVERLAY +#if defined(ENABLE_MENU_OVERLAY) && PICO_SCANVIDEO_PLANE_COUNT > 1 +#define ENABLE_MENU_PLANE_RENDERING +#endif + +#ifdef PICO_SCANVIDEO_48MHZ +#define CLOCK_MHZ 48000000 +#else +#define CLOCK_MHZ 50000000 +#endif +// todo yikes IRQs take so long that we run out of time with audio +// rendering doesn't take that much time, so use core 0 which has interrupts on it, leaving core1 100% time for z80 +#define RENDER_ON_CORE0 +//#define RENDER_ON_CORE1 + +// other IRQs are there... save time by having it on core1 +//#define BEEPER_IRQ_ON_RENDER_THREAD + +CU_REGISTER_DEBUG_PINS(khan_timing, beeper, menu) + +// ---- select at most one --- +//CU_SELECT_DEBUG_PINS(khan_timing) +//CU_SELECT_DEBUG_PINS(beeper) +//CU_SELECT_DEBUG_PINS(menu) + +//#ifdef NDEBUG +#define DISABLE_BEEPER_ASSERTIONS +//#endif +#ifndef DISABLE_BEEPER_ASSERTIONS +#define beeper_assert(x) assert(x) +#else +#define beeper_assert(x) ((void)0) +#endif + +#if defined(USE_UART_KEYBOARD) || defined(USE_SDL_EVENT_FORWARDER_INPUT) +void khan_check_uart(); +#endif +#ifdef USE_USB +void khan_check_usb(); +#endif +#ifndef NO_USE_BEEPER +void khan_beeper_setup(); +void khan_beeper_enable(); +#endif + +typedef bool (*render_scanline_func)(struct scanvideo_scanline_buffer *dest, int core); +bool render_scanline_khan(struct scanvideo_scanline_buffer *dest, int core); + +#define the_video_mode video_mode_khan +//#define the_video_mode video_mode_khan_tft + +//#define the_video_mode video_mode_khan2 +//#define the_video_mode video_mode_khan3 +//#define the_video_mode video_mode_khan_small_border_50 +//#define video_mode video_mode_320x240_60 + +render_scanline_func render_scanline = render_scanline_khan; + +int vspeed += 1*1; +int hspeed = 1*-1; + +// to make sure only one core updates the state when the frame number changes +// todo note we should actually make sure here that the other core isn't still rendering (i.e. all must arrive before either can proceed - a la barrier) +static struct mutex frame_logic_mutex; + +#ifdef ENABLE_MENU_OVERLAY +static uint32_t menu_text_color; +static uint32_t menu_highlight_bg_color; +static uint32_t menu_highlight_fg_color; +static uint32_t menu_highlight_fg_color2; +#endif + +static void khan_update_menu_visuals(); + +//void __isr isr_siob_proc0() { +// gpio_put(24, 1); +//} + +#ifndef NO_USE_AY +#if KHAN128_I2S +#include "pico/audio_i2s.h" +#else +#include "pico/audio_pwm.h" +#endif + +struct audio_buffer_pool *producer_pool; + +bool ay_init() { + const struct audio_format *output_format; + +#if KHAN128_I2S +#if !PICO_NO_BINARY_INFO + bi_decl(bi_3pins_with_names(PICO_AUDIO_I2S_DATA_PIN, "I2S DATA", PICO_AUDIO_I2S_CLOCK_PIN_BASE, "I2S BCLK", + PICO_AUDIO_I2S_CLOCK_PIN_BASE+1, "I2S LRCLK")); +#endif + struct audio_i2s_config ay_config = { + .data_pin = PICO_AUDIO_I2S_DATA_PIN, + .clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE, + .dma_channel = 4, + .pio_sm = 0, + }; +#else +#ifdef NO_USE_BEEPER + bi_decl(bi_1pin_with_name(PICO_AUDIO_PWM_L_PIN, "AY sound (PWM)")); +#else + bi_decl(bi_1pin_with_name(PICO_AUDIO_PWM_R_PIN, "AY sound (PWM)")); +#endif + static struct audio_pwm_channel_config ay_config = { +#ifdef NO_USE_BEEPER + .core.base_pin = PICO_AUDIO_PWM_L_PIN, +#else + .core.base_pin = PICO_AUDIO_PWM_R_PIN, +#endif + .core.dma_channel = 4, + .core.pio_sm = 0, + }; +#endif + + static struct audio_format producer_audio_format = { + .format = AUDIO_BUFFER_FORMAT_PCM_S16, + .sample_freq = 0, + .channel_count = 1, + }; + producer_audio_format.sample_freq = ay_sample_rate(); + + static struct audio_buffer_format producer_format = { + .format = &producer_audio_format, + .sample_stride = 2 + }; + + bool __unused ok = false; + + producer_pool = audio_new_producer_pool(&producer_format, 3, PICO_AUDIO_BUFFER_SAMPLE_LENGTH); // we need 441-443 so this seems reasonable + +#if KHAN128_I2S + output_format = audio_i2s_setup(&producer_audio_format, &ay_config); +#else + output_format = audio_pwm_setup(&producer_audio_format, -1, &ay_config); +#endif + if (!output_format) { + printf("MuAudio: Unable to open audio device.\n"); + return false; + } else + { +#if KHAN128_I2S + audio_i2s_connect(producer_pool); +#else + audio_pwm_set_correction_mode(fixed_dither); + audio_pwm_default_connect(producer_pool, false); +#endif +#if defined(SPEEDUP) && PICO_ON_DEVICE && !KHAN128_I2S + // hack to slow down PWM + pio_sm_set_clkdiv_int_frac(pio1, 0, SPEEDUP, 0); +#endif +#if KHAN128_I2S + audio_i2s_set_enabled(true); +#else + audio_pwm_set_enabled(true); +#endif + return true; + } +} +#endif + +void main_loop() { + +// gpio_init(PIN_DBG1); +// gpio_dir_out_mask(1 << PIN_DBG1); // steal debug pin 1 for this core +// gpio_init(PIN_DBG1+1); +// gpio_init(PIN_DBG1+2); +// gpio_dir_out_mask(6 << PIN_DBG1); // steal debug pin 2,3 for this core + int hz = (the_video_mode.default_timing->clock_freq + (the_video_mode.default_timing->h_total * the_video_mode.default_timing->v_total) / 2) / + (the_video_mode.default_timing->h_total * the_video_mode.default_timing->v_total); +#ifdef NO_USE_128K + puts("\n===============\nWelcome to Khan\n"); +#else + puts("\n===================\nWelcome to Khan128\nnote: currently beeper sound is disabled in favor of AY (wip)\n"); +#endif + printf("Hz %d\n", hz); +#ifndef NO_USE_BEEPER +#ifndef BEEPER_IRQ_ON_RENDER_THREAD + khan_beeper_enable(); +#endif +#endif +#ifndef NO_USE_AY + // todo on its own we need to hook up ISR + ay_init(); +#endif +#ifdef USE_USB +#if USE_USB && USB_ON_CORE1 + tusb_init(); + irq_set_priority(USBCTRL_IRQ, 0xc0); +#endif +#endif + khan_go(hz); +} + +int render_loop() { +// gpio_init(PIN_DBG1); +// gpio_dir_out_mask(1 << PIN_DBG1); // steal debug pin 1 for this core +// gpio_init(PIN_DBG1+1); +// gpio_init(PIN_DBG1+2); +// gpio_dir_out_mask(6 << PIN_DBG1); // steal debug pin 2,3 for this core + +#ifndef NO_USE_BEEPER +#ifdef BEEPER_IRQ_ON_RENDER_THREAD + khan_beeper_enable(); +#endif +#endif + static uint32_t last_frame_num = 0; + int core_num = get_core_num(); + assert(core_num >=0 && core_num < 2); +// printf("khan rendering on core %d\r\n", core_num); + while (true) { + struct scanvideo_scanline_buffer *scanvideo_scanline_buffer = scanvideo_begin_scanline_generation(true); +// if (scanvideo_scanline_buffer->data_used) { +// // validate the previous scanline to make sure noone corrupted it +// validate_scanline(scanvideo_scanline_buffer->data, scanvideo_scanline_buffer->data_used, video_mode.width, video_mode.width); +// } + // do any frame related logic + mutex_enter_blocking(&frame_logic_mutex); + uint32_t frame_num = scanvideo_frame_number(scanvideo_scanline_buffer->scanline_id); + // note that with multiple cores we may have got here not for the first scanline, however one of the cores will do this logic first before either does the actual generation + if (frame_num != last_frame_num) { + DEBUG_PINS_SET(khan_timing, 4); + khan_menu_selection_change(MK_NONE); +#if defined(USE_UART_KEYBOARD) || defined(USE_SDL_EVENT_FORWARDER_INPUT) + khan_check_uart(); +#endif +#ifdef USE_USB + khan_check_usb(); +#endif + khan_update_menu_visuals(); + khan_idle_blanking(); + DEBUG_PINS_CLR(khan_timing, 4); + // this could should be during vblank as we try to create the next line + // todo should we ignore if we aren't attempting the next line + last_frame_num = frame_num; + } + mutex_exit(&frame_logic_mutex); + DEBUG_PINS_SET(khan_timing, 2); + render_scanline(scanvideo_scanline_buffer, core_num); + DEBUG_PINS_CLR(khan_timing, 2); +#ifdef ENABLE_VIDEO_PLANE3 + assert(false); +#endif + // release the scanline into the wild + scanvideo_end_scanline_generation(scanvideo_scanline_buffer); + } +} + +#ifndef USE_CONST_CMD_LOOKUP +uint32_t cmd_lookup[256]; +#else +const uint32_t cmd_lookup[256] = { + 0xb0000000, 0xf0000000, 0xb5000000, 0xf5000000, 0xb0500000, 0xf0500000, 0xb5500000, 0xf5500000, + 0xb0050000, 0xf0050000, 0xb5050000, 0xf5050000, 0xb0550000, 0xf0550000, 0xb5550000, 0xf5550000, + 0xb0005000, 0xf0005000, 0xb5005000, 0xf5005000, 0xb0505000, 0xf0505000, 0xb5505000, 0xf5505000, + 0xb0055000, 0xf0055000, 0xb5055000, 0xf5055000, 0xb0555000, 0xf0555000, 0xb5555000, 0xf5555000, + 0xb0000500, 0xf0000500, 0xb5000500, 0xf5000500, 0xb0500500, 0xf0500500, 0xb5500500, 0xf5500500, + 0xb0050500, 0xf0050500, 0xb5050500, 0xf5050500, 0xb0550500, 0xf0550500, 0xb5550500, 0xf5550500, + 0xb0005500, 0xf0005500, 0xb5005500, 0xf5005500, 0xb0505500, 0xf0505500, 0xb5505500, 0xf5505500, + 0xb0055500, 0xf0055500, 0xb5055500, 0xf5055500, 0xb0555500, 0xf0555500, 0xb5555500, 0xf5555500, + 0xb0000050, 0xf0000050, 0xb5000050, 0xf5000050, 0xb0500050, 0xf0500050, 0xb5500050, 0xf5500050, + 0xb0050050, 0xf0050050, 0xb5050050, 0xf5050050, 0xb0550050, 0xf0550050, 0xb5550050, 0xf5550050, + 0xb0005050, 0xf0005050, 0xb5005050, 0xf5005050, 0xb0505050, 0xf0505050, 0xb5505050, 0xf5505050, + 0xb0055050, 0xf0055050, 0xb5055050, 0xf5055050, 0xb0555050, 0xf0555050, 0xb5555050, 0xf5555050, + 0xb0000550, 0xf0000550, 0xb5000550, 0xf5000550, 0xb0500550, 0xf0500550, 0xb5500550, 0xf5500550, + 0xb0050550, 0xf0050550, 0xb5050550, 0xf5050550, 0xb0550550, 0xf0550550, 0xb5550550, 0xf5550550, + 0xb0005550, 0xf0005550, 0xb5005550, 0xf5005550, 0xb0505550, 0xf0505550, 0xb5505550, 0xf5505550, + 0xb0055550, 0xf0055550, 0xb5055550, 0xf5055550, 0xb0555550, 0xf0555550, 0xb5555550, 0xf5555550, + 0xb0000005, 0xf0000005, 0xb5000005, 0xf5000005, 0xb0500005, 0xf0500005, 0xb5500005, 0xf5500005, + 0xb0050005, 0xf0050005, 0xb5050005, 0xf5050005, 0xb0550005, 0xf0550005, 0xb5550005, 0xf5550005, + 0xb0005005, 0xf0005005, 0xb5005005, 0xf5005005, 0xb0505005, 0xf0505005, 0xb5505005, 0xf5505005, + 0xb0055005, 0xf0055005, 0xb5055005, 0xf5055005, 0xb0555005, 0xf0555005, 0xb5555005, 0xf5555005, + 0xb0000505, 0xf0000505, 0xb5000505, 0xf5000505, 0xb0500505, 0xf0500505, 0xb5500505, 0xf5500505, + 0xb0050505, 0xf0050505, 0xb5050505, 0xf5050505, 0xb0550505, 0xf0550505, 0xb5550505, 0xf5550505, + 0xb0005505, 0xf0005505, 0xb5005505, 0xf5005505, 0xb0505505, 0xf0505505, 0xb5505505, 0xf5505505, + 0xb0055505, 0xf0055505, 0xb5055505, 0xf5055505, 0xb0555505, 0xf0555505, 0xb5555505, 0xf5555505, + 0xb0000055, 0xf0000055, 0xb5000055, 0xf5000055, 0xb0500055, 0xf0500055, 0xb5500055, 0xf5500055, + 0xb0050055, 0xf0050055, 0xb5050055, 0xf5050055, 0xb0550055, 0xf0550055, 0xb5550055, 0xf5550055, + 0xb0005055, 0xf0005055, 0xb5005055, 0xf5005055, 0xb0505055, 0xf0505055, 0xb5505055, 0xf5505055, + 0xb0055055, 0xf0055055, 0xb5055055, 0xf5055055, 0xb0555055, 0xf0555055, 0xb5555055, 0xf5555055, + 0xb0000555, 0xf0000555, 0xb5000555, 0xf5000555, 0xb0500555, 0xf0500555, 0xb5500555, 0xf5500555, + 0xb0050555, 0xf0050555, 0xb5050555, 0xf5050555, 0xb0550555, 0xf0550555, 0xb5550555, 0xf5550555, + 0xb0005555, 0xf0005555, 0xb5005555, 0xf5005555, 0xb0505555, 0xf0505555, 0xb5505555, 0xf5505555, + 0xb0055555, 0xf0055555, 0xb5055555, 0xf5055555, 0xb0555555, 0xf0555555, 0xb5555555, 0xf5555555, +}; +#endif + +int video_main(void) { + mutex_init(&frame_logic_mutex); + // set 18-22 to RIO for debugging +// for (int i = PIN_DBG1; i < 22; ++i) +// gpio_init(i); + +// gpio_init(24); +// gpio_init(22); +// // just from this core +// gpio_dir_out_mask(0x01380000); +// gpio_dir_in_mask( 0x00400000); +// +// //gpio_funcsel(22, 0); +// +// // debug pin +// gpio_put(24, 0); + + // go for launch (debug pin) +#ifndef NO_USE_BEEPER + khan_beeper_setup(); +#endif + +#if !PICO_ON_DEVICE + void simulate_video_pio_video_khan(const uint32_t *dma_data, uint32_t dma_data_size, + uint16_t *pixel_buffer, int32_t max_pixels, int32_t expected_width, bool overlay); + scanvideo_set_simulate_scanvideo_pio_fn(VIDEO_KHAN_PROGRAM_NAME, simulate_video_pio_video_khan); +#endif + //video_setup_with_timing(&the_video_mode, &video_timing_648x480_60_alt1); + scanvideo_setup(&the_video_mode); + +#ifndef USE_CONST_CMD_LOOKUP + for(int i=0;i<256;i++) { + uint32_t c = 0; + for(int j=0; j<8;j++) { + uint b; + if (i & (1<<(7-j))) { + b = j==7 ? video_khan_offset_ink_end_of_block : video_khan_offset_ink; + } else { + b = j==7 ? video_khan_offset_paper_end_of_block : video_khan_offset_paper; + } + c |= (b << (j*4)); + } + cmd_lookup[i] = c; + } +#endif +// printf("static uint32_t cmd_lookup[256] = {\n"); +// for(int i=0;i<0x100;i+=8) { +// printf("\t"); +// for(int j=0;j<8;j++) { +// printf("0x%08x, ", cmd_lookup[i+j]); +// } +// printf("\n"); +// } +// printf("};\n"); + + khan_init(); + // start video after the init which can take about 70ms when decompressing from flash + scanvideo_timing_enable(true); + + // button here so we have video running (albeit with red bar) +#if PICO_ON_DEVICE +#if 0 + printf("Press button to start\r\n"); + // todo NOTE THAT ON STARTUP RIGHT NOW WITH RESET ISSUES ON FPGA, THIS CURRENTLY DOES NOT STOP!!! if you make last_input static, then it never releases instead :-( + uint8_t last_input = 0; + while (true) { + uint8_t new_input = gpio_get(input_pin0); + if (last_input && !new_input) { + break; + } + last_input = new_input; + yield(); + } +#endif +#endif +// gpio_put(24, 1); +// gpio_funcsel(input_pin0, GPIO_FUNC_PROC); + + +#if USE_USB && !USB_ON_CORE1 + tusb_init(); + irq_set_priority(USBCTRL_IRQ, 0xc0); +#endif + +#ifdef RENDER_ON_CORE1 + // todo it seems that maybe we get into trouble if this is too slow... first time after FPGA + // is power up after a while, we get consistant dump everything red error scanlines + platform_launch_core1(render_loop); + main_loop(); +#else + multicore_launch_core1(main_loop); + render_loop(); +#endif + __builtin_unreachable(); +} + +bool khan_cb_is_button_down() { + // todo default button +//#if !PICO_ON_DEVICE +// return gpio_get(input_pin0); +//#else + return false; +//#endif +} + +void __maybe_in_ram measure_text(struct text_element *element, int max) { + if (!element->width) + { + element->width = 0; + if (element->text) + { + const char *p = element->text; + uint8_t c; + while ((c = *p++)) + { + c -= MENU_GLYPH_MIN; + if (c < MENU_GLYPH_MAX) + { + element->width += menu_glyph_widths[c] + MENU_GLYPH_ADVANCE; + } + } + if (element->width > max) + element->width = max; + } + } +} + +void khan_hide_menu() { + kms.appearing = false; + if (kms.opacity) + { + kms.disappearing = true; + } +} + +void khan_menu_key(enum menu_key key) { + if (key == MK_ESCAPE) { + if (kms.appearing || kms.opacity) { + if (!khan_menu_selection_change(MK_ESCAPE)) { + khan_hide_menu(); + } + } else if (kms.disappearing || !kms.opacity) { + kms.disappearing = false; + kms.appearing = true; + khan_fill_main_menu(); + } + } else { + if (kms.opacity && !kms.disappearing) { + switch (key) { + case MK_UP: + case MK_DOWN: + case MK_LEFT: + case MK_RIGHT: + case MK_ENTER: + khan_menu_selection_change(key); + break; + default: + break; + } + } + } +} + +//#define MAKE_RGB(r,g,b) (0x8000u|(((r)>>3))|(((g)>>3)<<5)|(((b)>>3)<<10)) +//#define MAKE_BGR(b,g,r) MAKE_RGB(r,g,b) + +#ifdef ENABLE_MENU_OVERLAY +// doesn't need to be this big really, given the big gap in the middle, but this is simplest (for one color menu anyway) +// note we don't store the empty lines between glyphs +static uint8_t menu_bitmap[MENU_WIDTH_IN_BLOCKS * MENU_GLYPH_HEIGHT * MENU_MAX_LINES]; +#endif + +int render_font_line(const struct text_element *element, uint8_t *out, const uint8_t *bitmaps, int pos, int bits); + +uint32_t mix_frac16(uint32_t a, uint32_t b, int level) { + int rr = (PICO_SCANVIDEO_R5_FROM_PIXEL(a) * (16 - level) + PICO_SCANVIDEO_R5_FROM_PIXEL(b) * level) / 16; + int gg = (PICO_SCANVIDEO_G5_FROM_PIXEL(a) * (16 - level) + PICO_SCANVIDEO_G5_FROM_PIXEL(b) * level) / 16; + int bb = (PICO_SCANVIDEO_B5_FROM_PIXEL(a) * (16 - level) + PICO_SCANVIDEO_B5_FROM_PIXEL(b) * level) / 16; + return PICO_SCANVIDEO_ALPHA_MASK | PICO_SCANVIDEO_PIXEL_FROM_RGB5(rr, gg, bb); +} + +// called once per frame +void khan_update_menu_visuals() { + bool hide; + bool fill; + mutex_enter_blocking(&kms.mutex); + hide = kms.do_hide_menu; kms.do_hide_menu = false; + fill = kms.do_fill_menu; kms.do_fill_menu = false; + mutex_exit(&kms.mutex); + if (hide) khan_hide_menu(); + if (fill) khan_fill_main_menu(); +#ifdef ENABLE_MENU_OVERLAY + DEBUG_PINS_SET(menu, 1); + if (kms.opacity > MENU_OPACITY_THRESHOLD) { + menu_text_color = PICO_SCANVIDEO_ALPHA_MASK + PICO_SCANVIDEO_PIXEL_FROM_RGB5(1, 1, 1) * (kms.opacity + MENU_OPACITY_OFFSET); + menu_highlight_fg_color = PICO_SCANVIDEO_ALPHA_MASK + PICO_SCANVIDEO_PIXEL_FROM_RGB5(1, 1, 1) * (kms.opacity + MENU_OPACITY_OFFSET + 7); + + if (kms.error_level) { + menu_highlight_fg_color = mix_frac16(menu_highlight_fg_color, PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x1f, 0, 0), kms.error_level); + } + menu_highlight_fg_color2 = menu_highlight_fg_color; + if (kms.flashing) { + int level; + if (kms.flash_pos < 8) + level = kms.flash_pos * 2; + else if ((MENU_FLASH_LENGTH - kms.flash_pos) < 8) + level = (MENU_FLASH_LENGTH - kms.flash_pos) * 2; + else + level = 16; + menu_highlight_fg_color2 = mix_frac16(PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x10, 0x10, 0x10), menu_highlight_fg_color2, level); + } +// menu_highlight_bg_color = kms.opacity > MENU_OPACITY_OFFSET + MENU_OPACITY_MAX - 6 ? MAKE_RGB(0x40, 0x40, 0xc0) : 0; + menu_highlight_bg_color = kms.opacity > MENU_OPACITY_OFFSET + MENU_OPACITY_MAX - 6 ? PICO_SCANVIDEO_ALPHA_MASK + PICO_SCANVIDEO_PIXEL_FROM_RGB8(48,138,208) : 0; + } else { + menu_text_color = menu_highlight_bg_color = menu_highlight_fg_color = 0; + } + static int foobar; + foobar++; + + if (kms.opacity) + { + static int refresh_line = 0; + measure_text(&kms.lines[refresh_line].left_text, MENU_LEFT_WIDTH_IN_BLOCKS * 8); + measure_text(&kms.lines[refresh_line].right_text, MENU_RIGHT_WIDTH_IN_BLOCKS * 8); + + uint8_t *out = menu_bitmap + refresh_line * MENU_GLYPH_HEIGHT * MENU_WIDTH_IN_BLOCKS; + const uint8_t *bitmaps = menu_glypth_bitmap; + for (int j = 0; j < MENU_GLYPH_HEIGHT; j++) + { + int pos = 0; + int bits = 0; + if (kms.lines[refresh_line].left_text.text) + { + pos = render_font_line(&kms.lines[refresh_line].left_text, out, bitmaps, pos, bits); + } + while (pos < MENU_LEFT_WIDTH_IN_BLOCKS) + out[pos++] = 0; + pos = MENU_LEFT_WIDTH_IN_BLOCKS; // truncate + if (kms.lines[refresh_line].right_text.text) + { + int xoff = MENU_RIGHT_WIDTH_IN_BLOCKS * 8 - kms.lines[refresh_line].right_text.width; + int post = pos + (xoff >> 3); + while (pos < post) + out[pos++] = 0; + bits = xoff & 7; + pos = render_font_line(&kms.lines[refresh_line].right_text, out, bitmaps, pos, bits); + } + while (pos < MENU_WIDTH_IN_BLOCKS) + out[pos++] = 0; + bitmaps++; + out += pos; + } + refresh_line++; + if (refresh_line == MENU_MAX_LINES) refresh_line = 0; + } + int expected_top_pixel = kms.selected_line * MENU_LINE_HEIGHT; + int delta = expected_top_pixel - kms.selection_top_pixel; + if (delta > 1) { + kms.selection_top_pixel += 1 + (((delta + MENU_LINE_HEIGHT / 2)* ((1 << 16) / MENU_LINE_HEIGHT))>>16); + } else if (delta < -1) { + kms.selection_top_pixel -= 1 + (((MENU_LINE_HEIGHT/2 - delta) * ((1 << 16) / MENU_LINE_HEIGHT))>>16); + } else { + kms.selection_top_pixel = expected_top_pixel; + } + DEBUG_PINS_CLR(menu, 1); +#endif +} + +int render_font_line(const struct text_element *element, uint8_t *out, const uint8_t *bitmaps, int pos, int bits) { + const char *p = element->text; + uint32_t acc = 0; + uint8_t c; + while ((c = *p++)) { + c -= MENU_GLYPH_MIN; + if (c < MENU_GLYPH_MAX) { + int cbits = menu_glyph_widths[c] + 1; + if (cbits <= 8) { + acc = (acc << cbits) | ((bitmaps[c * MENU_GLYPH_HEIGHT] >> (8 - cbits))); + } else { + acc = (acc << cbits) | ((bitmaps[c * MENU_GLYPH_HEIGHT] << (cbits - 8))); + } + bits += cbits; + if (bits >= 8) { + bits -= 8; + out[pos++] = acc >> bits; + acc &= ((1 << bits) - 1); + } + } + } + if (bits) { + out[pos++] = acc << (8 - bits); + } + return pos; +} + +#pragma GCC push_options +#ifdef __arm__ +#pragma GCC optimize("O3") +#endif + +uint32_t *render_menu_line(int l, uint32_t *buf2); + +bool __time_critical_func(render_scanline_khan)(struct scanvideo_scanline_buffer *dest, int core) { + // 1 + line_num red, then white + uint32_t *buf = dest->data; + uint l = scanvideo_scanline_number(dest->scanline_id); + const uint8_t *attr = 0; + const uint8_t *pixels = 0; + uint16_t border = 0; + const uint32_t *attr_colors = 0; + const uint32_t *dark_attr_colors = 0; + int border_top = (the_video_mode.height - 192) / 2; + khan_get_scanline_info(l - border_top, &border, &attr, &pixels, &attr_colors, &dark_attr_colors); + +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + uint32_t *buf2 = dest->data2; +#endif +#define MIN_COLOR_RUN 3 + if (attr) { + int border_width = (the_video_mode.width - 256) / 2; + if (border_width >= MIN_COLOR_RUN) { + *buf++ = border | ((border_width - MIN_COLOR_RUN) << 16u); + *buf++ = video_khan_offset_color_run; + } + l -= border_top + MENU_AREA_OFFSET_Y; + if (!(dark_attr_colors && l >= 0 && l <= MENU_LINE_HEIGHT * MENU_MAX_LINES + MENU_AREA_BORDER_HEIGHT * 2)) { + dark_attr_colors = attr_colors; + } +#define regular(n) *buf++ = attr_colors[attr[n]], *buf++ = cmd_lookup[pixels[n]] +#define maybe_dark(n) *buf++ = dark_attr_colors[attr[n]], *buf++ = cmd_lookup[pixels[n]] + + regular(0); + regular(1); + regular(2); + regular(3); + + maybe_dark(4); + maybe_dark(5); + maybe_dark(6); + maybe_dark(7); + + maybe_dark(8); + maybe_dark(9); + maybe_dark(10); + maybe_dark(11); + + maybe_dark(12); + maybe_dark(13); + maybe_dark(14); + maybe_dark(15); + + maybe_dark(16); + maybe_dark(17); + maybe_dark(18); + maybe_dark(19); + + maybe_dark(20); + maybe_dark(21); + maybe_dark(22); + maybe_dark(23); + + maybe_dark(24); + maybe_dark(25); + maybe_dark(26); + maybe_dark(27); + + regular(28); + regular(29); + regular(30); + regular(31); + + if (border_width >= MIN_COLOR_RUN) { + *buf++ = border | ((border_width - MIN_COLOR_RUN) << 16u); + *buf++ = video_khan_offset_color_run; + } +#ifdef ENABLE_MENU_PLANE_RENDERING + DEBUG_PINS_SET(menu, 2); + if (kms.opacity > MENU_OPACITY_THRESHOLD) { + l -= MENU_AREA_BORDER_HEIGHT; + if (l > 0 && l < MENU_LINE_HEIGHT * MENU_MAX_LINES) { + *buf2++ = 0 | ((border_width + ((256 - MENU_AREA_WIDTH) / 2) + MENU_LINE_BORDER_WIDTH - MIN_COLOR_RUN) + << 16); + *buf2++ = video_khan_offset_color_run; + buf2 = render_menu_line(l, buf2); + } + } + DEBUG_PINS_CLR(menu, 2); +#endif + } else { + *buf++ = border | ((the_video_mode.width - MIN_COLOR_RUN) << 16u); + *buf++ = video_khan_offset_color_run; + } + *buf++ = 0x0000u | (0x0000u << 16u); + *buf++ = video_khan_offset_end_of_line; + dest->status = SCANLINE_OK; + int pos = buf - dest->data; + assert(pos <= dest->data_max); + dest->data_used = (uint16_t) pos; +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + *buf2++ = 0x0000u | (0x0000u << 16u); + *buf2++ = video_khan_offset_end_of_line; + pos = buf2 - dest->data2; + assert(pos <= dest->data2_max); + dest->data2_used = (uint16_t)pos; +#endif +// printf("%d %d\n", dest->data_used, dest->data2_used); + return true; +} +#pragma GCC pop_options + +uint32_t *render_menu_line(int l, uint32_t *buf2) { + int menu_line = (l * ((1 << 16) / MENU_LINE_HEIGHT))>>16; + int menu_line_offset = l - menu_line * MENU_LINE_HEIGHT; + uint32_t bg_color, fg_color; + if (l >= kms.selection_top_pixel && l < kms.selection_top_pixel + MENU_LINE_HEIGHT + 1) { + fg_color = menu_highlight_fg_color; + bg_color = menu_highlight_bg_color; + } else { + fg_color = menu_text_color; + bg_color = 0; + } + uint32_t combined_colors = (fg_color << 16) | bg_color; + if (menu_line_offset >= MENU_GLYPH_Y_OFFSET && menu_line_offset < MENU_GLYPH_Y_OFFSET + MENU_GLYPH_HEIGHT) { + *buf2++ = bg_color | ((MENU_LINE_TEXT_INDENT - MIN_COLOR_RUN) << 16); + *buf2++ = video_khan_offset_color_run; + uint8_t *pixels = &menu_bitmap[MENU_WIDTH_IN_BLOCKS * (menu_line * MENU_GLYPH_HEIGHT + menu_line_offset - MENU_GLYPH_Y_OFFSET)]; + +#define regular2(n) *buf2++ = combined_colors, *buf2++ = cmd_lookup[pixels[n]] + + static_assert(MENU_LEFT_WIDTH_IN_BLOCKS == 10, ""); + regular2(0); + regular2(1); + regular2(2); + regular2(3); + regular2(4); + regular2(5); + regular2(6); + regular2(7); + regular2(8); + regular2(9); + + *buf2++ = bg_color | ((MENU_AREA_WIDTH - (MENU_LINE_BORDER_WIDTH + MENU_LINE_TEXT_INDENT) * 2 - MENU_WIDTH - MIN_COLOR_RUN) << 16); + *buf2++ = video_khan_offset_color_run; + + static_assert(MENU_RIGHT_WIDTH_IN_BLOCKS == 7, ""); + if (kms.flashing && menu_line == kms.selected_line) { + combined_colors = (menu_highlight_fg_color2 << 16) | bg_color; + } + + regular2(10); + regular2(11); + regular2(12); + regular2(13); + regular2(14); + regular2(15); + regular2(16); + *buf2++ = bg_color | ((MENU_LINE_TEXT_INDENT - MIN_COLOR_RUN) << 16); + *buf2++ = video_khan_offset_color_run; + } else { + *buf2++ = bg_color | ((MENU_AREA_WIDTH - MENU_LINE_BORDER_WIDTH * 2 - MIN_COLOR_RUN) << 16); + *buf2++ = video_khan_offset_color_run; + } + return buf2; +} + +void go_core1(void (*execute)()) { + multicore_launch_core1(execute); +} + +#if !PICO_ON_DEVICE +void simulate_video_pio_video_khan(const uint32_t *dma_data, uint32_t dma_data_size, + uint16_t *pixel_buffer, int32_t max_pixels, int32_t expected_width, bool overlay) { + const uint16_t *it = (uint16_t *)dma_data; + assert(!(3&(intptr_t)dma_data)); + const __unused uint16_t *const dma_data_end = (uint16_t *)(dma_data + dma_data_size); +// const uint16_t *const pixels_end = (uint16_t *)(pixel_buffer + max_pixels); + uint16_t *pixels = pixel_buffer; +// bool ok = false; + bool done = false; + const uint16_t display_enable_bit = PICO_SCANVIDEO_ALPHA_MASK; // for now + do { + uint16_t a = *it++; + uint16_t b = *it++; + // note we align checked above + uint32_t c = *(uint32_t *) it; + it += 2; + bool had_eob = false; + for (int n = 0; n < 8 && !done && !had_eob; n++) { + uint cmd = c & 0xf; + c >>= 4; + switch (cmd) { + case video_khan_offset_paper: + if (!overlay || (a & display_enable_bit)) { + *pixels++ = a; + } else { + pixels++; + } + break; + case video_khan_offset_end_of_line: + done = true; + break; + case video_khan_offset_ink: + if (!overlay || (b & display_enable_bit)) { + *pixels++ = b; + } else { + pixels++; + } + break; + case video_khan_offset_color_run: + if (!overlay || (a & display_enable_bit)) { + for (uint i = 0; i < b + 3; i++) { + *pixels++ = a; + } + } else { + pixels += b + 3; + } + had_eob = true; + break; + case video_khan_offset_paper_end_of_block: + if (!overlay || (a & display_enable_bit)) { + *pixels++ = a; + } else { + pixels++; + } + had_eob = true; + break; + case video_khan_offset_ink_end_of_block: + if (!overlay || (b & display_enable_bit)) { + *pixels++ = b; + } else { + pixels++; + } + had_eob = true; + break; + } + } + assert(done || had_eob); + } while (!done); +// assert(ok); + assert(it == dma_data_end); + assert(!(3&(intptr_t)(it))); // should end on dword boundary +// assert(!expected_width || pixels == pixel_buffer + expected_width); // with the correct number of pixels (one more because we stick a black pixel on the end) +} +#endif + +#if PICO_ON_DEVICE +void __cxa_pure_virtual() { + __breakpoint(); +} + +#if 0 +void _fini() { + __breakpoint(); +} +#endif +#endif + +#include "miniz_tinfl.h" + +int main(void) { +#if PICO_SCANVIDEO_48MHZ + int base_khz = 48000; +#else + int base_khz = 50000; +#endif + +#ifndef SPEEDUP + set_sys_clock_khz(base_khz, true); +#else + set_sys_clock_khz(base_khz * SPEEDUP, true); +#endif +// gpio_put(27, 0); + setup_default_uart(); + +// size_t in_bytes = 0;//avail_in; +// size_t out_bytes = 0;//avail_out; +// tinfl_init(&inflator); +// +// mz_uint8 *s_outbuf = NULL, *next_out = NULL, *next_in = NULL; +// bool infile_remaining = false; +// tinfl_decompress(&inflator, (const mz_uint8 *)next_in, &in_bytes, s_outbuf, (mz_uint8 *)next_out, &out_bytes, (infile_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0) | TINFL_FLAG_PARSE_ZLIB_HEADER); + + + return video_main(); +} + +void khan_cb_begin_frame() { + DEBUG_PINS_SET(khan_timing, 1); +} + +void khan_cb_end_frame() { + DEBUG_PINS_CLR(khan_timing, 1); +} + +#if PICO_ON_DEVICE +enum eKeyFlags { + KF_DOWN = 0x01, + KF_SHIFT = 0x02, + KF_CTRL = 0x04, + KF_ALT = 0x08, + + KF_KEMPSTON = 0x10, + KF_CURSOR = 0x20, + KF_QAOP = 0x40, + KF_SINCLAIR2 = 0x80, + +}; + +#ifdef USE_UART_KEYBOARD +static uint8_t UartTranslateKey(uint8_t _key, uint8_t esc, uint8_t *_flags) +{ + // todo lookup table + switch(_key) + { + case 27: + switch (esc) + { + case 'A': + *_flags = KF_CURSOR; + return 'u'; + case 'B': + *_flags = KF_CURSOR; + return 'd'; + case 'C': + *_flags = KF_CURSOR; + return 'r'; + case 'D': + *_flags = KF_CURSOR; + return 'l'; + } + break; + case '`': + return 'm'; +// case SDLK_LSHIFT: return 'c'; +// case SDLK_RSHIFT: return 'c'; +// case SDLK_LALT: return 's'; +// case SDLK_RALT: return 's'; + case '\r': + return 'e'; + case 8: + *_flags = KF_SHIFT; + return '0'; + case '\t': + *_flags = KF_ALT; + *_flags = KF_SHIFT; + return 0; +// case SDLK_LEFT: return 'l'; +// case SDLK_RIGHT: return 'r'; +// case SDLK_UP: return 'u'; +// case SDLK_DOWN: return 'd'; +// case SDLK_INSERT: +// case SDLK_RCTRL: +// case SDLK_LCTRL: return 'f'; + default: + break; + } + if(_key >= '0' && _key <= '9') + return _key; + if(_key >= 'A' && _key <= 'Z') { + *_flags = KF_SHIFT; + return _key; + } + if(_key >= 'a' && _key <= 'z') { + return _key + 'A' - 'a'; + } + if(_key == ' ') + return _key; + + const char alt_translate[] = "\"P'7T.M:Z;O/V?C-J_0=L+K"; + for(int i = 0; i < count_of(alt_translate); i+=2) { + if (_key == alt_translate[i]) { + *_flags |= KF_ALT; + return alt_translate[i+1]; + } + } + // todo escape codes + printf("Unknown key %d\n", _key); + *_flags = 0; + return 0; +} +#endif + +static bool skip_key; + +static uint16_t modifier_state; + +static const uint16_t mods[8] = { + KMOD_LCTRL, + KMOD_LSHIFT, + KMOD_LALT, + KMOD_LGUI, + KMOD_RCTRL, + KMOD_RSHIFT, + KMOD_RALT, + KMOD_RGUI, +}; + +bool update_modifiers(int scancode, bool down) { + if (scancode >= 224 && scancode < 224 + count_of(mods)) { + uint16_t mod = mods[scancode - 224]; + if (down) { + modifier_state |= mod; + } else { + modifier_state &= ~mod; + } + return true; + } + return false; +} + +#if defined(USE_UART_KEYBOARD) || defined(USE_SDL_EVENT_FORWARDER_INPUT) +void khan_check_uart() { +#ifdef USE_UART_KEYBOARD + static uint8_t last; + static uint8_t last_flags; + static int down_length = 0; +#endif + if (uart_is_readable(uart_default)) { + uint8_t flags = 0; + uint8_t b = uart_getc(uart_default); + if (skip_key) { + skip_key = false; + return; + } + uint8_t esc = 0; + bool menu_key = kms.opacity && !kms.disappearing; + if (b == 26 && uart_is_readable_within_us(uart_default, 80)) { + b = uart_getc(uart_default); + switch (b) { + case 0: + if (uart_is_readable_within_us(uart_default, 80)) { + uint scancode = (uint8_t)uart_getc(uart_default); + update_modifiers(scancode, true); + khan_key_down(scancode,0, modifier_state); + } + return; + case 1: + if (uart_is_readable_within_us(uart_default, 80)) { + uint scancode = (uint8_t)uart_getc(uart_default); + update_modifiers(scancode, false); + khan_key_up(scancode, 0, modifier_state); + } + return; + case 2: + case 3: + case 5: + if (uart_is_readable_within_us(uart_default, 80)) { + + uint state = (uint8_t)uart_getc(uart_default); +#ifndef NO_USE_KEMPSTON + uint8_t tstate = 0; + if (state & 0x03) tstate |= KEMPSTON_F; + if (state & 0x10) tstate |= KEMPSTON_U; + if (state & 0x20) tstate |= KEMPSTON_D; + if (state & 0x40) tstate |= KEMPSTON_L; + if (state & 0x80) tstate |= KEMPSTON_R; + //printf("Joystick state %02x\n", tstate); + khan_set_joystick_state(tstate); +#endif + } + return; + case 4: + if (uart_is_readable_within_us(uart_default, 80)) { + uint __unused scancode = (uint8_t)uart_getc(uart_default); + } + if (uart_is_readable_within_us(uart_default, 80)) { + uint __unused scancode = (uint8_t)uart_getc(uart_default); + } + return; + } + + uint8_t __unused state = uart_getc(uart_default); + return; + } +#if USE_UART_KEYBOARD + if (b == 27) { + if (uart_is_readable_within_us(uart_default, 80)) { + uint n = uart_getc(uart_default); + if (uart_is_readable_within_us(uart_default, 80)) { + if (n == 91) { + esc = uart_getc(uart_default); + if (menu_key) { + switch (esc) + { + case 'A': + khan_menu_key(MK_UP); + break; + case 'B': + khan_menu_key(MK_DOWN); + break; + case 'C': + khan_menu_key(MK_RIGHT); + break; + case 'D': + khan_menu_key(MK_LEFT); + break; + } + return; + } + } + } else { + skip_key = true; + } + } else { + khan_menu_key(MK_ESCAPE); + return; + } + } + if (!menu_key) + { + uint8_t translated = UartTranslateKey(b, esc, &flags); + if (translated) + { + if (down_length) + { + // can only have one key down + khan_zx_key_event(last, last_flags); + } + last = translated; + last_flags = flags; + down_length = 1; + flags |= KF_DOWN; + khan_zx_key_event(last, flags); + } + } else { + if (b == '\r') khan_menu_key(MK_ENTER); + } +#endif + } else { +#ifdef USE_UART_KEYBOARD + if (down_length) { + if (++down_length == 10) { + khan_zx_key_event(last, last_flags); + down_length = 0; + } + } +#endif + } +} +#endif +#endif + +#ifndef NO_USE_BEEPER +#if !PICO_ON_DEVICE +void khan_beeper_setup() {} +void khan_beeper_reset() {} +void khan_beeper_enable() {} +void khan_beeper_level_change(uint32_t t, uint8_t level) {} +void khan_beeper_begin_frame(uint32_t t, int32_t frame_num) {} +void khan_beeper_end_frame(uint32_t t) {} +#else + +#define BEEPER_ON_PIO1 + +#ifdef BEEPER_ON_PIO1 +#define beeper_pio pio1 +#define BEEPER_GPIO_FUNC GPIO_FUNC_PIO1 +// why not +#define BEEPER_SM 0 +#define BEEPER_DREQ_PIO_TX0 DREQ_PIO1_TX0 +#else +#define beeper_pio pio0 +#define BEEPER_GPIO_FUNC GPIO_FUNC_PIO0 +#define BEEPER_SM 2 +#define BEEPER_DREQ_PIO_TX0 DREQ_PIO0_TX0 +#endif + +#ifndef PICO_AUDIO_PWM_L_PIN +#error need a pin for the beeper +#endif +#define BEEPER_OUTPUT_PIN PICO_AUDIO_PWM_L_PIN +#define BEEPER_PATTERN_LENGTH 16 +#define BEEPER_DMA_CHANNEL 3 +bi_decl(bi_1pin_with_name(BEEPER_OUTPUT_PIN, "Beeper (PWM)")); + +#define BEEPER_MAKE_CYCLE_RUN(pattern, count) (((pattern)<<16)|((count)-1)) +void beeper_dma_complete(); + +#define BEEPER_FRAME_DELAY 2u +#define BEEPER_BUFFER_SIZE_LOG2 9u +#define BEEPER_BUFFER_SIZE (1u << BEEPER_BUFFER_SIZE_LOG2) +#define BEEPER_BUFFER_SIZE_MASK (BEEPER_BUFFER_SIZE - 1u) + +// we want two queued, one active, and some jitter due to 50/60 pulldown and one as a buffer +#define BEEPER_FRAMES 4u + +static struct { +#ifndef HACK_NON_CIRCULAR + uint32_t __attribute__ ((aligned (BEEPER_BUFFER_SIZE * 4))) circular_buffer[BEEPER_BUFFER_SIZE]; +#else + uint32_t __attribute__ ((aligned (BEEPER_BUFFER_SIZE * 2 * 4))) circular_buffer[BEEPER_BUFFER_SIZE*2]; +#endif + volatile spin_lock_t *spin_lock; + struct { + struct beeper_frame { + int32_t frame_number; + uint16_t start_pos; + uint16_t data_length; + } frames[BEEPER_FRAMES]; + struct beeper_frame *write_frame; // the frame being written, or about to be + struct beeper_frame *read_frame; // the frame being played, or the next to be + int32_t next_write_frame_number; + uint16_t write_limit_pos; // the location that may be written to (we need to start discarding data) + uint8_t queued_sequential_frame_count; // not including the playing frame + uint8_t playing_frame_count; // 0 or 1 for whether we have a frame in use + } locked; + struct { + uint32_t last_t; + uint32_t net_cycles_remainder; +#ifndef NDEBUG + int32_t frame_tstates; +#endif +#ifdef HACK_NON_CIRCULAR + int32_t hack_non_circular_offset; +#endif + uint16_t write_ptr; + // this is an unlocked copy + uint16_t write_limit_pos; // the location that may be written to (we need to start discarding data) + uint16_t last_level; + } gen_thread; +} beeper_state; + +inline static void khan_beeper_start_dma_locked() { + beeper_assert(beeper_state.locked.queued_sequential_frame_count > 0); + beeper_assert(!beeper_state.locked.playing_frame_count); + beeper_assert(beeper_state.locked.read_frame->data_length > 0); + DEBUG_PINS_SET(beeper, 4); + DEBUG_PINS_CLR(beeper, 1); +// printf("begin dma from read_buffer frame %d %p off %d len %d %d\n", beeper_state.locked.read_frame->frame_number, beeper_state.locked.read_frame, beeper_state.locked.read_frame->start_pos, beeper_state.locked.read_frame->data_length, CORE_NUM); + const uint32_t *buffer = beeper_state.circular_buffer + beeper_state.locked.read_frame->start_pos; +// printf("%08x %04x\n", (intptr_t)buffer, beeper_state.locked.read_frame->data_length); + dma_channel_transfer_from_buffer_now(BEEPER_DMA_CHANNEL, (void *)buffer, beeper_state.locked.read_frame->data_length); + beeper_state.locked.queued_sequential_frame_count--; + beeper_state.locked.playing_frame_count = 1; + DEBUG_PINS_SET(beeper, 1); + DEBUG_PINS_CLR(beeper, 4); +} + +void khan_beeper_reset() { + uint32_t save = spin_lock_blocking(beeper_state.spin_lock); + + beeper_state.locked.write_frame = beeper_state.locked.read_frame; + if (beeper_state.locked.playing_frame_count) { + beeper_state.locked.write_frame++; + if (beeper_state.locked.write_frame == beeper_state.locked.frames + BEEPER_FRAMES) { + beeper_state.locked.write_frame = beeper_state.locked.frames; + } + } + beeper_state.locked.queued_sequential_frame_count = 0; + spin_unlock(beeper_state.spin_lock, save); +} + +void khan_beeper_begin_frame(uint32_t t, int32_t frame_num) { + DEBUG_PINS_SET(beeper, 1); +// printf("Begin frame %d %d\n", frame_num, CORE_NUM); + uint32_t save = spin_lock_blocking(beeper_state.spin_lock); + // may be overkill, but right now if we are out of frames or out of order, we discard everything (except what is playing if anything) and start over + // removed this since it doesn't seem to make much difference, so why waste code... we shouldn't be out of whack anyway unless we are running unsynced + // note also that we only shift the wrong audio data a frame or two in either direction anyway +// if ((beeper_state.locked.queued_sequential_frame_count > 0 && frame_num != beeper_state.locked.next_write_frame_number) || (beeper_state.locked.queued_sequential_frame_count == BEEPER_FRAMES - beeper_state.locked.playing_frame_count)) { + if (beeper_state.locked.queued_sequential_frame_count == BEEPER_FRAMES - beeper_state.locked.playing_frame_count) { +// printf("Clearing frame queued = %d, wfn = %d\n", beeper_state.locked.queued_sequential_frame_count, beeper_state.locked.next_write_frame_number); + // discard the last frame we generated - this will give us the shortest gap + beeper_state.locked.write_frame--; + if (beeper_state.locked.write_frame < beeper_state.locked.frames) { + beeper_state.locked.write_frame += BEEPER_FRAMES; + } + beeper_assert(beeper_state.locked.queued_sequential_frame_count>0); + beeper_state.gen_thread.write_ptr = beeper_state.locked.write_frame->start_pos; + beeper_state.locked.queued_sequential_frame_count--; + } + beeper_state.locked.write_frame->frame_number = frame_num; + beeper_state.locked.write_frame->start_pos = beeper_state.gen_thread.write_ptr; +#ifdef HACK_NON_CIRCULAR + beeper_state.gen_thread.hack_non_circular_offset = 0; +#endif +#ifndef NDEBUG + beeper_state.locked.write_frame->data_length = 0; +#endif + // copy limit into gen thread + beeper_state.gen_thread.write_limit_pos = beeper_state.locked.write_limit_pos; + if (!beeper_state.locked.playing_frame_count && beeper_state.locked.queued_sequential_frame_count >= BEEPER_FRAME_DELAY) { + // dma underflowed (or hadn't started) and we now have enough frames worth of audio (again) + khan_beeper_start_dma_locked(); + } + spin_unlock(beeper_state.spin_lock, save); + DEBUG_PINS_CLR(beeper, 1); + beeper_state.gen_thread.last_t = t; +} + +void khan_beeper_end_frame(uint32_t t) { +#if 0 +// printf("End frame %d %d\n", t, CORE_NUM); + for(int i=1; i<16; i++) { + khan_beeper_level_change((t*i)/16, i&1?200:0); + } + khan_beeper_level_change(t, 0); +#else + khan_beeper_level_change(t, beeper_state.gen_thread.last_level); +#endif + uint32_t save = spin_lock_blocking(beeper_state.spin_lock); + // finish out the write frame + if (beeper_state.gen_thread.write_ptr != beeper_state.locked.write_frame->start_pos) { + beeper_state.locked.write_frame->data_length = + (BEEPER_BUFFER_SIZE + beeper_state.gen_thread.write_ptr - beeper_state.locked.write_frame->start_pos) & + BEEPER_BUFFER_SIZE_MASK; +// printf("%04x %04x %04x\n", beeper_state.gen_thread.write_ptr, beeper_state.locked.write_frame->start_pos, beeper_state.locked.write_frame->data_length); + beeper_state.locked.next_write_frame_number = beeper_state.locked.write_frame->frame_number + 1; + // and update + beeper_state.locked.write_frame++; + if (beeper_state.locked.write_frame == beeper_state.locked.frames + BEEPER_FRAMES) { + beeper_state.locked.write_frame = beeper_state.locked.frames; + } + beeper_state.locked.queued_sequential_frame_count++; + } else { + // we were totally full + } + spin_unlock(beeper_state.spin_lock, save); +} + +// called when dma is complete +void khan_beeper_next_frame_needed() { + DEBUG_PINS_SET(beeper, 2); + uint32_t save = spin_lock_blocking(beeper_state.spin_lock); +// printf("Begin DMA needed %d\n", CORE_NUM); + beeper_assert(beeper_state.locked.playing_frame_count); + beeper_state.locked.playing_frame_count = 0; + + // move past frame we just finished playing + beeper_state.locked.write_limit_pos = (beeper_state.locked.write_limit_pos + beeper_state.locked.read_frame->data_length) & BEEPER_BUFFER_SIZE_MASK; + beeper_state.locked.read_frame++; + if (beeper_state.locked.read_frame == beeper_state.locked.frames + BEEPER_FRAMES) { + beeper_state.locked.read_frame = beeper_state.locked.frames; + } + + // if we have any queued sequential frames, then play the next one + if (beeper_state.locked.queued_sequential_frame_count > 0) { + beeper_assert(beeper_state.locked.write_limit_pos == ((beeper_state.locked.read_frame->start_pos + BEEPER_BUFFER_SIZE_MASK) & BEEPER_BUFFER_SIZE_MASK)); + khan_beeper_start_dma_locked(); + } + spin_unlock(beeper_state.spin_lock, save); + DEBUG_PINS_CLR(beeper, 2); +} + +/** + * + * start of frame (play a buffered frame if we have two sequential) and we aren't playing... also discard any out of sequence frames + * dma complete (play a buffered frame if we have one in sequence) otherwise underflow + * end of frame ... finish book-keeping + */ + +void khan_beeper_level_change(uint32_t t, uint8_t level) { +// printf("Level %d %d %d\n", t, level, CORE_NUM); + // our thread is the only one writing into the circular buffer + // todo right now loading a .z80 may cause this - actually shouldn't any longer + int32_t delta_t = t - beeper_state.gen_thread.last_t; + beeper_assert(delta_t >= 0); + + // div 3200 because they are all multiples, and it makes 32bit math ok + static const uint32_t tstates_per_second_div_3200 = 71680 * 50 / 3200; + static const uint32_t net_clock_divisor = tstates_per_second_div_3200 * (BEEPER_PATTERN_LENGTH * 2 + 5); + static const uint32_t clocks_per_second_div_3200 = CLOCK_MHZ / 3200; + static_assert((CLOCK_MHZ / 3200) * (uint64_t)(CLOCK_MHZ / 3200) <= 0x100000000LL, ""); + + uint32_t net_cycles_dividend = clocks_per_second_div_3200 * delta_t + beeper_state.gen_thread.net_cycles_remainder; + int32_t net_cycles = net_cycles_dividend / net_clock_divisor; + // todo timing still a little off; we're betting off being a little short + beeper_state.gen_thread.net_cycles_remainder = net_cycles_dividend % net_clock_divisor; +// pantso = true; +// if (pantso) printf("%d %d %d %d %d %d\n", skinker, t, delta_t, net_cycles, level, beeper_state.gen_thread.last_level); + + beeper_state.gen_thread.last_t = t; +// uint32_t pattern = beeper_state.gen_thread.last_level ? 0xfff : 0; // todo volume 65535 >> (__builtin_clz(level)-16); + uint32_t pattern = (1 << (beeper_state.gen_thread.last_level >> 4)) - 1; + + while (net_cycles > 0) { + uint32_t count = (net_cycles >> 16) ? 0x10000 : net_cycles; + if (beeper_state.gen_thread.write_ptr != beeper_state.gen_thread.write_limit_pos) { +#ifndef HACK_NON_CIRCULAR + beeper_state.circular_buffer[beeper_state.gen_thread.write_ptr] = BEEPER_MAKE_CYCLE_RUN(pattern, count); + beeper_state.gen_thread.write_ptr = (beeper_state.gen_thread.write_ptr + 1) & BEEPER_BUFFER_SIZE_MASK; +#else + beeper_state.circular_buffer[beeper_state.gen_thread.write_ptr + beeper_state.gen_thread.hack_non_circular_offset] = BEEPER_MAKE_CYCLE_RUN(pattern, count); + beeper_state.gen_thread.write_ptr = beeper_state.gen_thread.write_ptr + 1; + if (beeper_state.gen_thread.write_ptr == BEEPER_BUFFER_SIZE) { + assert(!beeper_state.gen_thread.hack_non_circular_offset); + beeper_state.gen_thread.hack_non_circular_offset = BEEPER_BUFFER_SIZE; + beeper_state.gen_thread.write_ptr = 0; + } +#endif + net_cycles -= count; + } else { + // underflow + //printf("pants\n"); + break; + } + } + beeper_state.gen_thread.last_level = level; +#ifndef NDEBUG + beeper_state.gen_thread.frame_tstates += delta_t; +#endif +} + +void __maybe_in_ram khan_beeper_setup() { + beeper_state.spin_lock = spin_lock_init(spin_lock_claim_unused(true)); + gpio_set_function(BEEPER_OUTPUT_PIN, BEEPER_GPIO_FUNC); + + // todo did this need to be at 22? +// int beeper_load_offset = 22; + uint beeper_load_offset = pio_add_program(beeper_pio, &beeper_program); + + pio_sm_config smc = beeper_program_get_default_config(beeper_load_offset); + sm_config_set_out_shift(&smc, true, false, BEEPER_PATTERN_LENGTH); + // todo i don't think we need set pins, so I didn't but they were there before + sm_config_set_out_pins(&smc, BEEPER_OUTPUT_PIN, 1); + pio_sm_init(beeper_pio, BEEPER_SM, beeper_load_offset, &smc); + + pio_sm_set_consecutive_pindirs(beeper_pio, BEEPER_SM, BEEPER_OUTPUT_PIN, 1, true); + pio_sm_set_pins(beeper_pio, BEEPER_SM, 0); +// pio_sm_exec(beeper_pio, BEEPER_SM, pio_encode_set(pio_pindirs, 1)); // Raise the output enable on bottom pin +// pio_sm_exec(beeper_pio, BEEPER_SM, pio_encode_set(pio_pins, 0)); // clear pin +// pio_sm_exec(beeper_pio, BEEPER_SM, pio_encode_jmp(beeper_offset_entry_point + beeper_load_offset)); // jmp to ep + +// int ring_bits = 33 - __builtin_clz(BEEPER_BUFFER_SIZE); +#ifndef HACK_NON_CIRCULAR + int ring_bits = BEEPER_BUFFER_SIZE_LOG2 + 2; +#else + int ring_bits = 0; +#endif + + dma_channel_config dcc = dma_channel_get_default_config(BEEPER_DMA_CHANNEL); + channel_config_set_dreq(&dcc, BEEPER_DREQ_PIO_TX0 + BEEPER_SM); + channel_config_set_ring(&dcc, false, ring_bits); + dma_channel_set_write_addr(BEEPER_DMA_CHANNEL, &beeper_pio->txf[BEEPER_SM], false); + dma_channel_set_config(BEEPER_DMA_CHANNEL, &dcc, false); + + irq_set_priority(DMA_IRQ_1, 0x80); // lower priority by 2 + + beeper_state.locked.read_frame = beeper_state.locked.write_frame = beeper_state.locked.frames; + beeper_state.locked.write_limit_pos = BEEPER_BUFFER_SIZE_MASK; +} + +void khan_beeper_enable() { + dma_channel_set_irq1_enabled(BEEPER_DMA_CHANNEL, 1); + pio_sm_set_enabled(beeper_pio, BEEPER_SM, true); + irq_set_enabled(DMA_IRQ_1, true); +} + +extern void delegate_pwm_irq(); + +//void __isr __time_critical beeper_dma_complete() +void __maybe_in_ram __isr isr_dma_1() +{ + // note we don't loop so as not to steal time from video (we can certainly wait for another irq) + if (dma_hw->ints1 & (1u << BEEPER_DMA_CHANNEL)) { + dma_hw->ints1 = 1u << BEEPER_DMA_CHANNEL; + khan_beeper_next_frame_needed(); + } +#ifndef NO_USE_AY + delegate_pwm_irq(); +#endif +} +#endif +#endif + +#ifdef USE_KHAN_GPIO +bool gpio_allowed(int gpio) { + return gpio >= PICO_DEBUG_PIN_BASE; +} + +uint8_t khan_gpio_read(uint reg) { + int gpio = (reg&0x1f); + if (gpio_allowed(reg)) { + return gpio_get(gpio); + } + return 0; +} + +void khan_gpio_write(uint reg, uint8_t value) { + int gpio = (reg&0x1fu); + if (gpio_allowed(gpio)) { + if (reg & 0x80) { + gpio_init(gpio); + gpio_set_dir(gpio, GPIO_OUT); + gpio_set_function(gpio, GPIO_FUNC_SIO); + } else { + gpio_put(gpio, value); + } + } +} +#endif + +#if USE_USB + +#define MAX_REPORT 4 +#define debug_printf(fmt,...) ((void)0) +//#define debug_printf printf + +// Each HID instance can has multiple reports +static struct +{ + uint8_t report_count; + tuh_hid_report_info_t report_info[MAX_REPORT]; +}hid_info[CFG_TUH_HID]; + +static void process_kbd_report(hid_keyboard_report_t const *report); +static void process_mouse_report(hid_mouse_report_t const * report); +static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len); + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. tuh_hid_parse_report_descriptor() +// can be used to parse common/simple enough descriptor. +// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped +// therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) +{ + debug_printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + + // Interface protocol (hid_interface_protocol_enum_t) + const char* protocol_str[] = { "None", "Keyboard", "Mouse" }; + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + debug_printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]); +// printf("%d USB: device %d connected, protocol %s\n", time_us_32() - t0 , dev_addr, protocol_str[itf_protocol]); + + // By default host stack will use activate boot protocol on supported interface. + // Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser) + if ( itf_protocol == HID_ITF_PROTOCOL_NONE ) + { + hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len); + debug_printf("HID has %u reports \r\n", hid_info[instance].report_count); + } + + // request to receive report + // tuh_hid_report_received_cb() will be invoked when report is available + if ( !tuh_hid_receive_report(dev_addr, instance) ) + { + debug_printf("Error: cannot request to receive report\r\n"); + } +} + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) +{ + debug_printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); + printf("USB: device %d disconnected\n", dev_addr); +} + +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) +{ + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + switch (itf_protocol) + { + case HID_ITF_PROTOCOL_KEYBOARD: + TU_LOG2("HID receive boot keyboard report\r\n"); + process_kbd_report( (hid_keyboard_report_t const*) report ); + break; + +#ifdef USE_USB_MOUSE + case HID_ITF_PROTOCOL_MOUSE: + TU_LOG2("HID receive boot mouse report\r\n"); + process_mouse_report( (hid_mouse_report_t const*) report ); + break; +#endif + + default: + // Generic report requires matching ReportID and contents with previous parsed report info + process_generic_report(dev_addr, instance, report, len); + break; + } + + // continue to request to receive report + if ( !tuh_hid_receive_report(dev_addr, instance) ) + { + debug_printf("Error: cannot request to receive report\r\n"); + } +} + +//--------------------------------------------------------------------+ +// Keyboard +//--------------------------------------------------------------------+ + +// look up new key in previous keys +static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode) +{ + for(uint8_t i=0; i<6; i++) + { + if (report->keycode[i] == keycode) return true; + } + + return false; +} + +static void process_kbd_report(hid_keyboard_report_t const *report) +{ + static hid_keyboard_report_t prev_report = { 0, 0, {0} }; // previous report to check key released + + // ctrl seems not to be used, so assigning it to be ALT as well + modifier_state = (report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT) ? KMOD_LSHIFT : 0) | + (report->modifier & (KEYBOARD_MODIFIER_RIGHTSHIFT) ? KMOD_RSHIFT : 0) | + (report->modifier & (KEYBOARD_MODIFIER_LEFTALT | KEYBOARD_MODIFIER_LEFTCTRL) ? KMOD_LALT : 0) | + (report->modifier & (KEYBOARD_MODIFIER_RIGHTALT | KEYBOARD_MODIFIER_RIGHTCTRL) ? KMOD_RALT : 0); + //------------- example code ignore control (non-printable) key affects -------------// + for(uint8_t i=0; i<6; i++) + { + if ( report->keycode[i] ) + { + if ( find_key_in_report(&prev_report, report->keycode[i]) ) + { + // exist in previous report means the current key is holding + }else + { + // not existed in previous report means the current key is pressed + khan_key_down(report->keycode[i], 0, modifier_state); +// bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT); +// pico_key_down(report->keycode[i], 0, is_shift ? WITH_SHIFT : 0); + } + } + // Check for key depresses (i.e. was present in prev report but not here) + if (prev_report.keycode[i]) { + // If not present in the current report then depressed + if (!find_key_in_report(report, prev_report.keycode[i])) + { +// bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT); +// pico_key_up(prev_report.keycode[i], 0, is_shift ? WITH_SHIFT : 0); + khan_key_up(prev_report.keycode[i], 0, modifier_state); + } + } + } + prev_report = *report; +} + +//--------------------------------------------------------------------+ +// Mouse +//--------------------------------------------------------------------+ + +#ifdef USE_USB_MOUSE +static void process_mouse_report(hid_mouse_report_t const * report) +{ + static hid_mouse_report_t prev_report = { 0 }; + + uint8_t button_changed_mask = report->buttons ^ prev_report.buttons; + if ( button_changed_mask & report->buttons) + { + debug_printf(" %c%c%c ", + report->buttons & MOUSE_BUTTON_LEFT ? 'L' : '-', + report->buttons & MOUSE_BUTTON_MIDDLE ? 'M' : '-', + report->buttons & MOUSE_BUTTON_RIGHT ? 'R' : '-'); + } + +// cursor_movement(report->x, report->y, report->wheel); +} +#endif + +//--------------------------------------------------------------------+ +// Generic Report +//--------------------------------------------------------------------+ +static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) +{ + (void) dev_addr; + + uint8_t const rpt_count = hid_info[instance].report_count; + tuh_hid_report_info_t* rpt_info_arr = hid_info[instance].report_info; + tuh_hid_report_info_t* rpt_info = NULL; + + if ( rpt_count == 1 && rpt_info_arr[0].report_id == 0) + { + // Simple report without report ID as 1st byte + rpt_info = &rpt_info_arr[0]; + }else + { + // Composite report, 1st byte is report ID, data starts from 2nd byte + uint8_t const rpt_id = report[0]; + + // Find report id in the arrray + for(uint8_t i=0; iusage_page == HID_USAGE_PAGE_DESKTOP ) + { + switch (rpt_info->usage) + { +#ifdef USE_USB_KEYBOARD + case HID_USAGE_DESKTOP_KEYBOARD: + TU_LOG1("HID receive keyboard report\r\n"); + // Assume keyboard follow boot report layout + process_kbd_report( (hid_keyboard_report_t const*) report ); + break; +#endif +#ifdef USE_USB_MOUSE + case HID_USAGE_DESKTOP_MOUSE: + TU_LOG1("HID receive mouse report\r\n"); + // Assume mouse follow boot report layout + process_mouse_report( (hid_mouse_report_t const*) report ); + break; +#endif + + default: break; + } + } +} + +void khan_check_usb(void) { + // todo not needed every time perhaps + tuh_task(); +} +#endif \ No newline at end of file diff --git a/khan/khan.pio b/khan/khan.pio new file mode 100644 index 0000000..ab87a57 --- /dev/null +++ b/khan/khan.pio @@ -0,0 +1,84 @@ +; +; Copyright (c) 2023 Graham Sanderson +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; format || a | b || c0-7 || ... +; +; c0-c7 are each 4 bit +; +; main display: each 2 x 32 block displays 8 pixels at 4 cycles per pixel +; a is paper color +; b is ink color +; c0-c6 are "jmp paper" or "jmp ink" +; c7 is "jmp paper_end_of_block" or "jmp ink_end_of_block" +; +; border segments: 2 x 32 block does a single run of pixels +; a is color +; b is count + 3 +; c0 is "jmp color_run" +; +; end of line: 2 x 32 block ends (inefficiently) a scanline (and blanks the pins) +; c0 is "jmp end_of_line" +; c1-c7 must be 0 as they are outed onto pins (strictly c1-c4 hold the pixel value) +; + +.program video_khan +.origin 0 +public paper: +public delay1: + mov pins, x [2] + out pc, 4 + +public end_of_line: + out pins, 28 ; required to flush OSR and clear pins +public entry_point: + wait irq 4 + jmp new_block + +public ink: +public delay2: + mov pins, y [2] + out pc, 4 + +public color_run: ; of y + 3 +public delay3: + mov pins, x +color_run_loop: +public delay4: + jmp y-- color_run_loop [3] + out null, 28 ; // discard rest + jmp new_block [2] + +public paper_end_of_block: + mov pins, x +.wrap_target +new_block: +public delay5: + out x, 16 + out y, 16 + out pc, 4 + +public ink_end_of_block: + mov pins, y + .wrap + +// cannot have autopull +// runs of 1 bit audio where the 1 bit has volume!! it is output as is an N+2 bit pattern where N is the bit threshold of the SM ... +// the carrier frequency = CLK / (2 * (N+2)) +// note zeros are included beyond the pattern for N > 16 +.program beeper +cycle_loop: + nop [2] +.wrap_target + mov osr, y +bit_loop: + out pins, 1 + jmp !osre bit_loop + jmp x-- cycle_loop +public entry_point: + pull + out x, 16 // cycle count + out y, 16 // bit pattern +.wrap diff --git a/khan/khan_init.cpp b/khan/khan_init.cpp new file mode 100644 index 0000000..dda12d5 --- /dev/null +++ b/khan/khan_init.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "khan_init.h" +#include "khan.pio.h" +#include "../platform/platform.h" +#include "../tools/options.h" + +const struct scanvideo_pio_program video_khan = { +#if PICO_ON_DEVICE + .program = &video_khan_program, + .adapt_for_mode = video_khan_adapt_for_mode, + .configure_pio = video_khan_configure_pio +#else + .id = VIDEO_KHAN_PROGRAM_NAME +#endif +}; + +const struct scanvideo_pio_program video_khan54 = { +#if PICO_ON_DEVICE + .program = &video_khan_program, + .adapt_for_mode = video_khan_adapt_for_mode54, + .configure_pio = video_khan_configure_pio +#else + .id = VIDEO_KHAN_PROGRAM_NAME +#endif +}; + +const struct scanvideo_mode video_mode_khan = + { + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_khan, + .width = 320, + .height = 240, + .xscale = 2, + .yscale = 2, + }; + +const struct scanvideo_mode video_mode_khan_tft = + { + .default_timing = &vga_timing_wide_480_50, + .pio_program = &video_khan, + .width = 400, + .height = 240, + .xscale = 2, + .yscale = 2, + }; + + +// this one is good actually on dells for 640*576 (use 5/4 for 512x576) +const struct scanvideo_timing video_timing_khan2 = + { + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 579, + + .h_front_porch = 16, + .h_pulse = 64, + .h_total = 800, + .h_sync_polarity = 1, + + .v_front_porch = 3, + .v_pulse = 10, + .v_total = 600, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + + +const struct scanvideo_mode video_mode_khan2 = + { + .default_timing = &video_timing_khan2, + .pio_program = &video_khan54, + .width = 256, + .height = 193, + .xscale = 2, + .yscale = 3, + }; + +// this one is ok too, though may need vertical positioning (it is always cropped - presumably at 480 +const struct scanvideo_timing video_timing_khan3 = + { + + .clock_freq = 24000000, + + .h_active = 704, + .v_active = 526, + + .h_front_porch = 18, + .h_pulse = 70, + .h_total = 880, + .h_sync_polarity = 1, + + .v_front_porch = 3, + .v_pulse = 10, + .v_total = 545, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + + +const struct scanvideo_mode video_mode_khan3 = + { + .default_timing = &video_timing_khan3, + .pio_program = &video_khan, + .width = 352, + .height = 263, + .xscale = 2, + .yscale = 2, + }; + + +#if PICO_ON_DEVICE +static uint32_t missing_scanline_data[] = { + 0x801fu | (0x83ffu << 16u), + 0, // to be filled in + 0x0000u | (0x0000u << 16u), + video_khan_offset_end_of_line +}; + +pio_sm_config video_khan_configure_pio(PIO pio, uint sm, uint offset) { + const int SCANLINE_SM = 0; + pio_sm_config config = video_khan_program_get_default_config(offset); + scanvideo_default_configure_pio(pio, sm, offset, &config, sm != SCANLINE_SM); + return config; +} + +extern uint32_t cmd_lookup[256]; + +bool video_khan_adapt_for_mode(const struct scanvideo_pio_program *program, const struct scanvideo_mode *mode, + struct scanvideo_scanline_buffer *missing_scanvideo_scanline_buffer, uint16_t *modifiable_instructions) { + missing_scanline_data[1] = cmd_lookup[0xaa]; + missing_scanvideo_scanline_buffer->data = missing_scanline_data; + missing_scanvideo_scanline_buffer->data_used = missing_scanvideo_scanline_buffer->data_max = sizeof(missing_scanline_data) / 4; +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + missing_scanvideo_scanline_buffer->data2 = missing_scanline_data; + missing_scanvideo_scanline_buffer->data2_used = missing_scanvideo_scanline_buffer->data2_max = sizeof(missing_scanline_data) / 4; +#endif + return true; +} + +bool video_khan_adapt_for_mode54(const struct scanvideo_pio_program *program, const struct scanvideo_mode *mode, + struct scanvideo_scanline_buffer *missing_scanvideo_scanline_buffer, uint16_t *modifiable_instructions) { + assert(false);// we need the delay values + missing_scanline_data[1] = cmd_lookup[0xaa]; + missing_scanvideo_scanline_buffer->data = missing_scanline_data; + missing_scanvideo_scanline_buffer->data_used = missing_scanvideo_scanline_buffer->data_max = sizeof(missing_scanline_data) / 4; + return true; +} +#endif \ No newline at end of file diff --git a/khan/khan_init.h b/khan/khan_init.h new file mode 100644 index 0000000..95901ef --- /dev/null +++ b/khan/khan_init.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef KHAN_INIT_H +#define KHAN_INIT_H + +#include "pico/scanvideo.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//extern struct khan_image { +// const char *name; +// void (*worker)(); +//} images[]; +//extern const uint image_count; + +extern bool no_wait_for_vblank; +extern const struct scanvideo_mode video_mode_khan; + +#define VIDEO_KHAN_PROGRAM_NAME "video_khan" + +#if PICO_ON_DEVICE +pio_sm_config video_khan_configure_pio(PIO pio, uint sm, uint offset); +bool video_khan_adapt_for_mode(const struct scanvideo_pio_program *program, const struct scanvideo_mode *mode, + struct scanvideo_scanline_buffer *missing_scanvideo_scanline_buffer, + uint16_t *modifiable_instructions); +bool video_khan_adapt_for_mode54(const struct scanvideo_pio_program *program, const struct scanvideo_mode *mode, + struct scanvideo_scanline_buffer *missing_scanvideo_scanline_buffer, + uint16_t *modifiable_instructions); +#endif + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/khan/khan_lib.cpp b/khan/khan_lib.cpp new file mode 100644 index 0000000..21c93e8 --- /dev/null +++ b/khan/khan_lib.cpp @@ -0,0 +1,807 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "khan_lib.h" +#include "pico/scanvideo.h" +#if PICO_ON_DEVICE +#include "z80khan.h" +#endif +#include "khan_init.h" + +#if PICO_ON_DEVICE +#include "sdl_keys.h" +#else +#include +#endif + +struct menu_state kms; + +#include "../platform/platform.h" +#include "../platform/io.h" +#include "../options_common.h" +#include "../speccy.h" +#include "../devices/memory.h" +#include "../devices/ula.h" +#include "../z80/z80.h" +#include "../file_type.h" +#include "../tools/options.h" +#include "games/games.h" + +#ifndef NO_USE_AY +#include "../devices/sound/ay.h" +#endif + +#include "khan_lib.h" + +bool no_wait_for_vblank; + + +extern void spoono(); +//eMemory memory; +//eDevices devices; +//xZ80::eZ80* cpu; + +int frame_tacts; + +void khan_reset() { +} + +void khan_zx_key_event(uint8_t key, uint8_t flags) { + //printf("onkey %02x %02x\n", key, flags); + xPlatform::Handler()->OnKey(key, flags); +} + +bool last_button_state; +static __maybe_in_ram void khan_button_state(bool down) { + if (down && !last_button_state) { + xPlatform::Handler()->OnKey('e', xPlatform::KF_DOWN); + } else if (!down && last_button_state) { + xPlatform::Handler()->OnKey('e', 0); + } + last_button_state = down; +} + +//#define REGENERATE_COLORS +#ifdef REGENERATE_COLORS +static uint32_t colors[512]; +static uint32_t dark_colors[512]; +#else +// todo const - need space for hobbit atm +static const uint32_t colors[512] = { + 0x00200020, 0xc8200020, 0x00390020, 0xc8390020, 0x06600020, 0xce600020, 0x06790020, 0xce790020, + 0x0020c820, 0xc820c820, 0x0039c820, 0xc839c820, 0x0660c820, 0xce60c820, 0x0679c820, 0xce79c820, + 0x00200039, 0xc8200039, 0x00390039, 0xc8390039, 0x06600039, 0xce600039, 0x06790039, 0xce790039, + 0x0020c839, 0xc820c839, 0x0039c839, 0xc839c839, 0x0660c839, 0xce60c839, 0x0679c839, 0xce79c839, + 0x00200660, 0xc8200660, 0x00390660, 0xc8390660, 0x06600660, 0xce600660, 0x06790660, 0xce790660, + 0x0020ce60, 0xc820ce60, 0x0039ce60, 0xc839ce60, 0x0660ce60, 0xce60ce60, 0x0679ce60, 0xce79ce60, + 0x00200679, 0xc8200679, 0x00390679, 0xc8390679, 0x06600679, 0xce600679, 0x06790679, 0xce790679, + 0x0020ce79, 0xc820ce79, 0x0039ce79, 0xc839ce79, 0x0660ce79, 0xce60ce79, 0x0679ce79, 0xce79ce79, + 0x00200020, 0xf8200020, 0x003f0020, 0xf83f0020, 0x07e00020, 0xffe00020, 0x07ff0020, 0xffff0020, + 0x0020f820, 0xf820f820, 0x003ff820, 0xf83ff820, 0x07e0f820, 0xffe0f820, 0x07fff820, 0xfffff820, + 0x0020003f, 0xf820003f, 0x003f003f, 0xf83f003f, 0x07e0003f, 0xffe0003f, 0x07ff003f, 0xffff003f, + 0x0020f83f, 0xf820f83f, 0x003ff83f, 0xf83ff83f, 0x07e0f83f, 0xffe0f83f, 0x07fff83f, 0xfffff83f, + 0x002007e0, 0xf82007e0, 0x003f07e0, 0xf83f07e0, 0x07e007e0, 0xffe007e0, 0x07ff07e0, 0xffff07e0, + 0x0020ffe0, 0xf820ffe0, 0x003fffe0, 0xf83fffe0, 0x07e0ffe0, 0xffe0ffe0, 0x07ffffe0, 0xffffffe0, + 0x002007ff, 0xf82007ff, 0x003f07ff, 0xf83f07ff, 0x07e007ff, 0xffe007ff, 0x07ff07ff, 0xffff07ff, + 0x0020ffff, 0xf820ffff, 0x003fffff, 0xf83fffff, 0x07e0ffff, 0xffe0ffff, 0x07ffffff, 0xffffffff, + 0x00200020, 0xc8200020, 0x00390020, 0xc8390020, 0x06600020, 0xce600020, 0x06790020, 0xce790020, + 0x0020c820, 0xc820c820, 0x0039c820, 0xc839c820, 0x0660c820, 0xce60c820, 0x0679c820, 0xce79c820, + 0x00200039, 0xc8200039, 0x00390039, 0xc8390039, 0x06600039, 0xce600039, 0x06790039, 0xce790039, + 0x0020c839, 0xc820c839, 0x0039c839, 0xc839c839, 0x0660c839, 0xce60c839, 0x0679c839, 0xce79c839, + 0x00200660, 0xc8200660, 0x00390660, 0xc8390660, 0x06600660, 0xce600660, 0x06790660, 0xce790660, + 0x0020ce60, 0xc820ce60, 0x0039ce60, 0xc839ce60, 0x0660ce60, 0xce60ce60, 0x0679ce60, 0xce79ce60, + 0x00200679, 0xc8200679, 0x00390679, 0xc8390679, 0x06600679, 0xce600679, 0x06790679, 0xce790679, + 0x0020ce79, 0xc820ce79, 0x0039ce79, 0xc839ce79, 0x0660ce79, 0xce60ce79, 0x0679ce79, 0xce79ce79, + 0x00200020, 0xf8200020, 0x003f0020, 0xf83f0020, 0x07e00020, 0xffe00020, 0x07ff0020, 0xffff0020, + 0x0020f820, 0xf820f820, 0x003ff820, 0xf83ff820, 0x07e0f820, 0xffe0f820, 0x07fff820, 0xfffff820, + 0x0020003f, 0xf820003f, 0x003f003f, 0xf83f003f, 0x07e0003f, 0xffe0003f, 0x07ff003f, 0xffff003f, + 0x0020f83f, 0xf820f83f, 0x003ff83f, 0xf83ff83f, 0x07e0f83f, 0xffe0f83f, 0x07fff83f, 0xfffff83f, + 0x002007e0, 0xf82007e0, 0x003f07e0, 0xf83f07e0, 0x07e007e0, 0xffe007e0, 0x07ff07e0, 0xffff07e0, + 0x0020ffe0, 0xf820ffe0, 0x003fffe0, 0xf83fffe0, 0x07e0ffe0, 0xffe0ffe0, 0x07ffffe0, 0xffffffe0, + 0x002007ff, 0xf82007ff, 0x003f07ff, 0xf83f07ff, 0x07e007ff, 0xffe007ff, 0x07ff07ff, 0xffff07ff, + 0x0020ffff, 0xf820ffff, 0x003fffff, 0xf83fffff, 0x07e0ffff, 0xffe0ffff, 0x07ffffff, 0xffffffff, + 0x00200020, 0xc8200020, 0x00390020, 0xc8390020, 0x06600020, 0xce600020, 0x06790020, 0xce790020, + 0x0020c820, 0xc820c820, 0x0039c820, 0xc839c820, 0x0660c820, 0xce60c820, 0x0679c820, 0xce79c820, + 0x00200039, 0xc8200039, 0x00390039, 0xc8390039, 0x06600039, 0xce600039, 0x06790039, 0xce790039, + 0x0020c839, 0xc820c839, 0x0039c839, 0xc839c839, 0x0660c839, 0xce60c839, 0x0679c839, 0xce79c839, + 0x00200660, 0xc8200660, 0x00390660, 0xc8390660, 0x06600660, 0xce600660, 0x06790660, 0xce790660, + 0x0020ce60, 0xc820ce60, 0x0039ce60, 0xc839ce60, 0x0660ce60, 0xce60ce60, 0x0679ce60, 0xce79ce60, + 0x00200679, 0xc8200679, 0x00390679, 0xc8390679, 0x06600679, 0xce600679, 0x06790679, 0xce790679, + 0x0020ce79, 0xc820ce79, 0x0039ce79, 0xc839ce79, 0x0660ce79, 0xce60ce79, 0x0679ce79, 0xce79ce79, + 0x00200020, 0xf8200020, 0x003f0020, 0xf83f0020, 0x07e00020, 0xffe00020, 0x07ff0020, 0xffff0020, + 0x0020f820, 0xf820f820, 0x003ff820, 0xf83ff820, 0x07e0f820, 0xffe0f820, 0x07fff820, 0xfffff820, + 0x0020003f, 0xf820003f, 0x003f003f, 0xf83f003f, 0x07e0003f, 0xffe0003f, 0x07ff003f, 0xffff003f, + 0x0020f83f, 0xf820f83f, 0x003ff83f, 0xf83ff83f, 0x07e0f83f, 0xffe0f83f, 0x07fff83f, 0xfffff83f, + 0x002007e0, 0xf82007e0, 0x003f07e0, 0xf83f07e0, 0x07e007e0, 0xffe007e0, 0x07ff07e0, 0xffff07e0, + 0x0020ffe0, 0xf820ffe0, 0x003fffe0, 0xf83fffe0, 0x07e0ffe0, 0xffe0ffe0, 0x07ffffe0, 0xffffffe0, + 0x002007ff, 0xf82007ff, 0x003f07ff, 0xf83f07ff, 0x07e007ff, 0xffe007ff, 0x07ff07ff, 0xffff07ff, + 0x0020ffff, 0xf820ffff, 0x003fffff, 0xf83fffff, 0x07e0ffff, 0xffe0ffff, 0x07ffffff, 0xffffffff, + 0x00200020, 0x0020c820, 0x00200039, 0x0020c839, 0x00200660, 0x0020ce60, 0x00200679, 0x0020ce79, + 0xc8200020, 0xc820c820, 0xc8200039, 0xc820c839, 0xc8200660, 0xc820ce60, 0xc8200679, 0xc820ce79, + 0x00390020, 0x0039c820, 0x00390039, 0x0039c839, 0x00390660, 0x0039ce60, 0x00390679, 0x0039ce79, + 0xc8390020, 0xc839c820, 0xc8390039, 0xc839c839, 0xc8390660, 0xc839ce60, 0xc8390679, 0xc839ce79, + 0x06600020, 0x0660c820, 0x06600039, 0x0660c839, 0x06600660, 0x0660ce60, 0x06600679, 0x0660ce79, + 0xce600020, 0xce60c820, 0xce600039, 0xce60c839, 0xce600660, 0xce60ce60, 0xce600679, 0xce60ce79, + 0x06790020, 0x0679c820, 0x06790039, 0x0679c839, 0x06790660, 0x0679ce60, 0x06790679, 0x0679ce79, + 0xce790020, 0xce79c820, 0xce790039, 0xce79c839, 0xce790660, 0xce79ce60, 0xce790679, 0xce79ce79, + 0x00200020, 0x0020f820, 0x0020003f, 0x0020f83f, 0x002007e0, 0x0020ffe0, 0x002007ff, 0x0020ffff, + 0xf8200020, 0xf820f820, 0xf820003f, 0xf820f83f, 0xf82007e0, 0xf820ffe0, 0xf82007ff, 0xf820ffff, + 0x003f0020, 0x003ff820, 0x003f003f, 0x003ff83f, 0x003f07e0, 0x003fffe0, 0x003f07ff, 0x003fffff, + 0xf83f0020, 0xf83ff820, 0xf83f003f, 0xf83ff83f, 0xf83f07e0, 0xf83fffe0, 0xf83f07ff, 0xf83fffff, + 0x07e00020, 0x07e0f820, 0x07e0003f, 0x07e0f83f, 0x07e007e0, 0x07e0ffe0, 0x07e007ff, 0x07e0ffff, + 0xffe00020, 0xffe0f820, 0xffe0003f, 0xffe0f83f, 0xffe007e0, 0xffe0ffe0, 0xffe007ff, 0xffe0ffff, + 0x07ff0020, 0x07fff820, 0x07ff003f, 0x07fff83f, 0x07ff07e0, 0x07ffffe0, 0x07ff07ff, 0x07ffffff, + 0xffff0020, 0xfffff820, 0xffff003f, 0xfffff83f, 0xffff07e0, 0xffffffe0, 0xffff07ff, 0xffffffff, +}; +static uint32_t dark_colors[512] = { + 0x00200020, 0xc8200020, 0x00390020, 0xc8390020, 0x06600020, 0xce600020, 0x06790020, 0xce790020, + 0x0020c820, 0xc820c820, 0x0039c820, 0xc839c820, 0x0660c820, 0xce60c820, 0x0679c820, 0xce79c820, + 0x00200039, 0xc8200039, 0x00390039, 0xc8390039, 0x06600039, 0xce600039, 0x06790039, 0xce790039, + 0x0020c839, 0xc820c839, 0x0039c839, 0xc839c839, 0x0660c839, 0xce60c839, 0x0679c839, 0xce79c839, + 0x00200660, 0xc8200660, 0x00390660, 0xc8390660, 0x06600660, 0xce600660, 0x06790660, 0xce790660, + 0x0020ce60, 0xc820ce60, 0x0039ce60, 0xc839ce60, 0x0660ce60, 0xce60ce60, 0x0679ce60, 0xce79ce60, + 0x00200679, 0xc8200679, 0x00390679, 0xc8390679, 0x06600679, 0xce600679, 0x06790679, 0xce790679, + 0x0020ce79, 0xc820ce79, 0x0039ce79, 0xc839ce79, 0x0660ce79, 0xce60ce79, 0x0679ce79, 0xce79ce79, + 0x00200020, 0xf8200020, 0x003f0020, 0xf83f0020, 0x07e00020, 0xffe00020, 0x07ff0020, 0xffff0020, + 0x0020f820, 0xf820f820, 0x003ff820, 0xf83ff820, 0x07e0f820, 0xffe0f820, 0x07fff820, 0xfffff820, + 0x0020003f, 0xf820003f, 0x003f003f, 0xf83f003f, 0x07e0003f, 0xffe0003f, 0x07ff003f, 0xffff003f, + 0x0020f83f, 0xf820f83f, 0x003ff83f, 0xf83ff83f, 0x07e0f83f, 0xffe0f83f, 0x07fff83f, 0xfffff83f, + 0x002007e0, 0xf82007e0, 0x003f07e0, 0xf83f07e0, 0x07e007e0, 0xffe007e0, 0x07ff07e0, 0xffff07e0, + 0x0020ffe0, 0xf820ffe0, 0x003fffe0, 0xf83fffe0, 0x07e0ffe0, 0xffe0ffe0, 0x07ffffe0, 0xffffffe0, + 0x002007ff, 0xf82007ff, 0x003f07ff, 0xf83f07ff, 0x07e007ff, 0xffe007ff, 0x07ff07ff, 0xffff07ff, + 0x0020ffff, 0xf820ffff, 0x003fffff, 0xf83fffff, 0x07e0ffff, 0xffe0ffff, 0x07ffffff, 0xffffffff, + 0x00200020, 0xc8200020, 0x00390020, 0xc8390020, 0x06600020, 0xce600020, 0x06790020, 0xce790020, + 0x0020c820, 0xc820c820, 0x0039c820, 0xc839c820, 0x0660c820, 0xce60c820, 0x0679c820, 0xce79c820, + 0x00200039, 0xc8200039, 0x00390039, 0xc8390039, 0x06600039, 0xce600039, 0x06790039, 0xce790039, + 0x0020c839, 0xc820c839, 0x0039c839, 0xc839c839, 0x0660c839, 0xce60c839, 0x0679c839, 0xce79c839, + 0x00200660, 0xc8200660, 0x00390660, 0xc8390660, 0x06600660, 0xce600660, 0x06790660, 0xce790660, + 0x0020ce60, 0xc820ce60, 0x0039ce60, 0xc839ce60, 0x0660ce60, 0xce60ce60, 0x0679ce60, 0xce79ce60, + 0x00200679, 0xc8200679, 0x00390679, 0xc8390679, 0x06600679, 0xce600679, 0x06790679, 0xce790679, + 0x0020ce79, 0xc820ce79, 0x0039ce79, 0xc839ce79, 0x0660ce79, 0xce60ce79, 0x0679ce79, 0xce79ce79, + 0x00200020, 0xf8200020, 0x003f0020, 0xf83f0020, 0x07e00020, 0xffe00020, 0x07ff0020, 0xffff0020, + 0x0020f820, 0xf820f820, 0x003ff820, 0xf83ff820, 0x07e0f820, 0xffe0f820, 0x07fff820, 0xfffff820, + 0x0020003f, 0xf820003f, 0x003f003f, 0xf83f003f, 0x07e0003f, 0xffe0003f, 0x07ff003f, 0xffff003f, + 0x0020f83f, 0xf820f83f, 0x003ff83f, 0xf83ff83f, 0x07e0f83f, 0xffe0f83f, 0x07fff83f, 0xfffff83f, + 0x002007e0, 0xf82007e0, 0x003f07e0, 0xf83f07e0, 0x07e007e0, 0xffe007e0, 0x07ff07e0, 0xffff07e0, + 0x0020ffe0, 0xf820ffe0, 0x003fffe0, 0xf83fffe0, 0x07e0ffe0, 0xffe0ffe0, 0x07ffffe0, 0xffffffe0, + 0x002007ff, 0xf82007ff, 0x003f07ff, 0xf83f07ff, 0x07e007ff, 0xffe007ff, 0x07ff07ff, 0xffff07ff, + 0x0020ffff, 0xf820ffff, 0x003fffff, 0xf83fffff, 0x07e0ffff, 0xffe0ffff, 0x07ffffff, 0xffffffff, + 0x00200020, 0xc8200020, 0x00390020, 0xc8390020, 0x06600020, 0xce600020, 0x06790020, 0xce790020, + 0x0020c820, 0xc820c820, 0x0039c820, 0xc839c820, 0x0660c820, 0xce60c820, 0x0679c820, 0xce79c820, + 0x00200039, 0xc8200039, 0x00390039, 0xc8390039, 0x06600039, 0xce600039, 0x06790039, 0xce790039, + 0x0020c839, 0xc820c839, 0x0039c839, 0xc839c839, 0x0660c839, 0xce60c839, 0x0679c839, 0xce79c839, + 0x00200660, 0xc8200660, 0x00390660, 0xc8390660, 0x06600660, 0xce600660, 0x06790660, 0xce790660, + 0x0020ce60, 0xc820ce60, 0x0039ce60, 0xc839ce60, 0x0660ce60, 0xce60ce60, 0x0679ce60, 0xce79ce60, + 0x00200679, 0xc8200679, 0x00390679, 0xc8390679, 0x06600679, 0xce600679, 0x06790679, 0xce790679, + 0x0020ce79, 0xc820ce79, 0x0039ce79, 0xc839ce79, 0x0660ce79, 0xce60ce79, 0x0679ce79, 0xce79ce79, + 0x00200020, 0xf8200020, 0x003f0020, 0xf83f0020, 0x07e00020, 0xffe00020, 0x07ff0020, 0xffff0020, + 0x0020f820, 0xf820f820, 0x003ff820, 0xf83ff820, 0x07e0f820, 0xffe0f820, 0x07fff820, 0xfffff820, + 0x0020003f, 0xf820003f, 0x003f003f, 0xf83f003f, 0x07e0003f, 0xffe0003f, 0x07ff003f, 0xffff003f, + 0x0020f83f, 0xf820f83f, 0x003ff83f, 0xf83ff83f, 0x07e0f83f, 0xffe0f83f, 0x07fff83f, 0xfffff83f, + 0x002007e0, 0xf82007e0, 0x003f07e0, 0xf83f07e0, 0x07e007e0, 0xffe007e0, 0x07ff07e0, 0xffff07e0, + 0x0020ffe0, 0xf820ffe0, 0x003fffe0, 0xf83fffe0, 0x07e0ffe0, 0xffe0ffe0, 0x07ffffe0, 0xffffffe0, + 0x002007ff, 0xf82007ff, 0x003f07ff, 0xf83f07ff, 0x07e007ff, 0xffe007ff, 0x07ff07ff, 0xffff07ff, + 0x0020ffff, 0xf820ffff, 0x003fffff, 0xf83fffff, 0x07e0ffff, 0xffe0ffff, 0x07ffffff, 0xffffffff, + 0x00200020, 0x0020c820, 0x00200039, 0x0020c839, 0x00200660, 0x0020ce60, 0x00200679, 0x0020ce79, + 0xc8200020, 0xc820c820, 0xc8200039, 0xc820c839, 0xc8200660, 0xc820ce60, 0xc8200679, 0xc820ce79, + 0x00390020, 0x0039c820, 0x00390039, 0x0039c839, 0x00390660, 0x0039ce60, 0x00390679, 0x0039ce79, + 0xc8390020, 0xc839c820, 0xc8390039, 0xc839c839, 0xc8390660, 0xc839ce60, 0xc8390679, 0xc839ce79, + 0x06600020, 0x0660c820, 0x06600039, 0x0660c839, 0x06600660, 0x0660ce60, 0x06600679, 0x0660ce79, + 0xce600020, 0xce60c820, 0xce600039, 0xce60c839, 0xce600660, 0xce60ce60, 0xce600679, 0xce60ce79, + 0x06790020, 0x0679c820, 0x06790039, 0x0679c839, 0x06790660, 0x0679ce60, 0x06790679, 0x0679ce79, + 0xce790020, 0xce79c820, 0xce790039, 0xce79c839, 0xce790660, 0xce79ce60, 0xce790679, 0xce79ce79, + 0x00200020, 0x0020f820, 0x0020003f, 0x0020f83f, 0x002007e0, 0x0020ffe0, 0x002007ff, 0x0020ffff, + 0xf8200020, 0xf820f820, 0xf820003f, 0xf820f83f, 0xf82007e0, 0xf820ffe0, 0xf82007ff, 0xf820ffff, + 0x003f0020, 0x003ff820, 0x003f003f, 0x003ff83f, 0x003f07e0, 0x003fffe0, 0x003f07ff, 0x003fffff, + 0xf83f0020, 0xf83ff820, 0xf83f003f, 0xf83ff83f, 0xf83f07e0, 0xf83fffe0, 0xf83f07ff, 0xf83fffff, + 0x07e00020, 0x07e0f820, 0x07e0003f, 0x07e0f83f, 0x07e007e0, 0x07e0ffe0, 0x07e007ff, 0x07e0ffff, + 0xffe00020, 0xffe0f820, 0xffe0003f, 0xffe0f83f, 0xffe007e0, 0xffe0ffe0, 0xffe007ff, 0xffe0ffff, + 0x07ff0020, 0x07fff820, 0x07ff003f, 0x07fff83f, 0x07ff07e0, 0x07ffffe0, 0x07ff07ff, 0x07ffffff, + 0xffff0020, 0xfffff820, 0xffff003f, 0xfffff83f, 0xffff07e0, 0xffffffe0, 0xffff07ff, 0xffffffff, +}; +#endif +static int color_toggle = 0; + +#define SPECTRUM_BRIGHTNESS(c) (200 + (((c)&8)?55:0)) +#define SPECTRUM_TO_RGB(c) MAKE_RGB(((c)&2) ? SPECTRUM_BRIGHTNESS(c) : 0, ((c)&4) ? SPECTRUM_BRIGHTNESS(c) : 0, ((c)&1) ? SPECTRUM_BRIGHTNESS(c) : 0) + +static struct eOptionImage : public xOptions::eOptionIntWithPending +{ + eOptionImage() { value = -1; } + virtual const char* Name() const override { +#ifndef USE_MU + return "image"; +#else + return "Image"; +#endif + } + + virtual int Order() const override { return 54; } + + const char *Value() const override { + int v = *this; + if (v >= 0 && v<(int)embedded_game_count) { + return embedded_games[v].name; + } + return "none"; + } + + virtual void Complete(bool accept) override + { + assert(is_change_pending); + if (accept && pending_value != value) { + SetNow(pending_value); + } + is_change_pending = false; + } + + void SetNow(const int &v) override + { + int old_value = value; + eOptionIntWithPending::SetNow(v); + if (value != old_value && value >= -1 && value<=(int)embedded_game_count) { + if (value == -1) { + xPlatform::Handler()->OnAction(xPlatform::A_RESET); + } else { + const embedded_game_t *g = embedded_games + value; +#ifndef NO_USE_FAST_TAPE + xOptions::eOptionBool::Find("Fast tape")->Set((g->flags & GF_SLOW_TAPE) == 0); +#endif + no_wait_for_vblank = (g->flags & GF_NO_WAIT_VBLANK) != 0; + xPlatform::Handler()->OnOpenFileCompressed(g->ext, g->data_z, g->data_z_size, g->data_size); + } + } + } + + void Change(bool next) override + { + eOptionInt::Change(-1, embedded_game_count, next); + } +} op_image; + +int khan_init() +{ + mutex_init(&kms.mutex); +#if !PICO_ON_DEVICE + static char buf[1024]; + if (!getcwd(buf, sizeof(buf))) + return false; + strcat(buf, "/"); + xIo::SetResourcePath(buf); +#endif +#ifndef NO_USE_128K + xOptions::eOptionBool::Find("48K mode")->Set(false); +#endif + xPlatform::Handler()->OnInit(); +// xPlatform::Handler()->Speccy()->CPU()->set_breakpoint(0xe7); //0x6EB1); //0x6D31); + // + op_image.SetNow(embedded_game_default); // actually select image + +#if !PICO_ON_DEVICE + +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/tests/z80tests.tap")); + +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/z80full.tap")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/Manic_Miner_1983_Software_Projects.z80")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/Jet Set Willy (R D Foord Software).tzx")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/knightlore.sna")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/g+g.sna")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/JETSET.TAP")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/3D_Pacman_1983_Freddy_Kristiansen.z80")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/Donkey_Kong_1986_Ocean.z80")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/Hobbit_The_v1.2_1982_Melbourne_House_a.z80")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/Star_Wars_1987_Domark.z80")); +// xPlatform::Handler()->OnOpenFile(xIo::ResourcePath("res/The Sentinel.tzx")); + platform_key_down = khan_key_down; + platform_key_up = khan_key_up; +#endif +#ifdef REGENERATE_COLORS + // make colortab: zx-attr -> pc-attr + for(int a = 0; a < 0x100; a++) { + byte ink = a & 7; + byte paper = (a >> 3) & 7; + byte bright = (a >> 6) & 1; + byte flash = (a >> 7) & 1; + if (ink) + ink |= bright << 3; // no bright for 0th color + if (paper) + paper |= bright << 3; // no bright for 0th color +// if (ink == paper) { +// ink = paper = 0; +// } + colors[a] = (SPECTRUM_TO_RGB(ink) << 16) | SPECTRUM_TO_RGB(paper); + if (flash) { + colors[a + 256] = SPECTRUM_TO_RGB(ink) | (SPECTRUM_TO_RGB(paper) << 16); + } else { + colors[a + 256] = colors[a]; + } + dark_colors[a] = colors[a]; + dark_colors[a + 256] = colors[a+256]; + } + printf("static uint32_t colors[512] = {\n"); + for(int i=0;i<0x200;i+=8) { + printf("\t"); + for(int j=0;j<8;j++) { + printf("0x%08x, ", colors[i+j]); + } + printf("\n"); + } + printf("};\n"); + printf("static uint32_t dark_colors[512] = {\n"); + for(int i=0;i<0x200;i+=8) { + printf("\t"); + for(int j=0;j<8;j++) { + printf("0x%08x, ", colors[i+j]); + } + printf("\n"); + } + printf("};\n"); +#endif +#if !PICO_ON_DEVICE + spoono(); +#endif + return 0; +} + + +void khan_done() { + xPlatform::Handler()->OnDone(); +} + +static int frame; +static int frame2; +static bool do_pulldown56; + +static bool option_next, option_complete; +static xOptions::eOptionB* option_needed; + +void __maybe_in_ram khan_loop() +{ +#if defined(USE_BANKED_MEMORY_ACCESS) && PICO_ON_DEVICE + xPlatform::Handler()->Speccy()->CPU()->InitMemoryBanks(); +#endif + while (true) { + if (!xPlatform::Handler()->IsVideoPaused()) { + if (do_pulldown56 && ++frame2 >= 6) { + frame2 = 0; + } else { + khan_cb_begin_frame(); + xPlatform::Handler()->OnLoop(); + if (++frame >= 15) { + frame = 0; + color_toggle ^= 256; + } + khan_cb_end_frame(); + } + } + if (!no_wait_for_vblank) scanvideo_wait_for_vblank(); + khan_button_state(khan_cb_is_button_down()); + xOptions::eOptionB *option; + bool next; + + mutex_enter_blocking(&kms.mutex); + option = option_needed; + next = option_next; + option_needed = NULL; + if (xPlatform::OpTapeRefresh()) { + kms.do_fill_menu = true; + } + if (option) { + if (option_complete) + { + // switch images is slow... we block this thread anyway, but releasing the mutex allows the video thread to still update the screen!! + mutex_exit(&kms.mutex); + option->Complete(); + mutex_enter_blocking(&kms.mutex); + kms.do_hide_menu = true; + kms.flashing = false; + } else { + const char *v = option->Value(); + option->Change(next); + // bit of a hack to detect unchangable values + if (v == option->Value()) { + kms.error_level = MENU_ERROR_LEVEL_MAX; + } + if (option->IsAction()) + { + kms.do_hide_menu = true; + kms.flashing = false; + } + else + { + kms.do_fill_menu = true; + } + } + } + mutex_exit(&kms.mutex); + } +} + +int __maybe_in_ram khan_go(int video_mode_hz) { + do_pulldown56 = video_mode_hz == 60; + if (do_pulldown56) { + OpVsyncRate(xPlatform::VR_5060); + } else if (video_mode_hz == 50) { + OpVsyncRate(xPlatform::VR_50); + } else { + OpVsyncRate(xPlatform::VR_WRONG); + } + khan_loop(); + khan_done(); + return 0; +} + +bool __maybe_in_ram khan_get_scanline_info(int l, uint16_t* border, const uint8_t ** attr, const uint8_t ** pixels, const uint32_t **attr_colors, const uint32_t **dark_attr_colors) { + eUla *ula = xPlatform::Handler()->Speccy()->Devices().Get(); + uint8_t bborder; + ula->GetLineInfo(l, bborder, *attr, *pixels); + *border = colors[bborder&0xf]>>16; + *attr_colors = colors + color_toggle; + if (kms.opacity) { + *dark_attr_colors = dark_colors + color_toggle; + } + return true; +} + +using xOptions::eOptionB; + +void __maybe_in_ram khan_fill_main_menu() { + kms.num_lines = 0; + for(eOptionB* o = eOptionB::First(); o; o = o->Next()) { + kms.lines[kms.num_lines].left_text.text = o->Name(); + kms.lines[kms.num_lines].right_text.text = o->Value(); + kms.lines[kms.num_lines].left_text.width = 0; + kms.lines[kms.num_lines].right_text.width = 0; + kms.num_lines++; + if (kms.num_lines == MENU_MAX_LINES) break; + } + for(int i=kms.num_lines;i 0) { + kms.error_level--; + } + if (kms.flashing) { + kms.flash_pos = (kms.flash_pos + 1); + if (kms.flash_pos == MENU_FLASH_LENGTH) kms.flash_pos = 0; + } + int next_opacity = kms.opacity; + if (kms.appearing) { + if (next_opacity < MENU_OPACITY_MAX) { + next_opacity = kms.opacity+2; + } else { + kms.appearing = false; + } + } else if (kms.disappearing) { + if (next_opacity > 0) { + next_opacity = kms.opacity-2; + } else { + kms.disappearing = false; + } + } + if (next_opacity != kms.opacity) { + int l = 32 - kms.opacity; + for(int i=0;i<512;i++) { + uint32_t cmask = 0x001f001f; + uint32_t r = (colors[i] >> PICO_SCANVIDEO_PIXEL_RSHIFT) & cmask; + uint32_t g = (colors[i] >> PICO_SCANVIDEO_PIXEL_GSHIFT) & cmask; + uint32_t b = (colors[i] >> PICO_SCANVIDEO_PIXEL_BSHIFT) & cmask; + uint32_t br = 4; + uint32_t bg = 2; + uint32_t bb = 7; + r = (((r * l)>>5) + ((br*kms.opacity)>>5)) & cmask; + g = (((g * l)>>5) + ((bg*kms.opacity)>>5)) & cmask; + b = (((b * l)>>5) + ((bb*kms.opacity)>>5)) & cmask; + dark_colors[i] = PICO_SCANVIDEO_PIXEL_FROM_RGB5(r, g, b); + } + kms.opacity = next_opacity; + } +} + +void __maybe_in_ram khan_defer_option(eOptionB *option, bool next, bool complete = false) { + mutex_enter_blocking(&kms.mutex); + option_needed = option; + option_next = next; + option_complete = complete; + mutex_exit(&kms.mutex); +} + +static inline void nav_error() { + kms.error_level = MENU_ERROR_LEVEL_MAX; +} + +// called to update the menu +bool __maybe_in_ram khan_menu_selection_change(enum menu_key key) +{ + int i = kms.selected_line; + eOptionB *o; + for (o = eOptionB::First(); o && i; o = o->Next(), i--) {} + if (o) { + switch (key) { + case MK_NONE: { + // bit of a hack.. just a safe place to do this once per frame + kms.flashing = o->IsChangePending(); + return false; + } + case MK_UP: + if (!o->IsChangePending()) + { + kms.selected_line = (kms.selected_line + kms.num_lines - 1) % kms.num_lines; + } else nav_error(); + return true; + case MK_DOWN: + if (!o->IsChangePending()) + { + kms.selected_line = (kms.selected_line + 1) % kms.num_lines; + } else nav_error(); + return true; + case MK_LEFT: + if (!o->IsAction()) { + // tell cpu thread to run the action + khan_defer_option(o, false); + } else nav_error(); + return true; + case MK_RIGHT: + if (!o->IsAction()) { + khan_defer_option(o, true); + } else nav_error(); + return true; + case MK_ENTER: + if (o->IsAction() || o->IsChangePending()) { + khan_defer_option(o, true, o->IsChangePending()); + } + return true; + case MK_ESCAPE: + if (o->IsChangePending()) { + o->Complete(false); + kms.do_fill_menu = true; + return true; + } + break; + default: + break; + } + } + return false; +} + +#ifndef NO_USE_KEMPSTON +#include "../devices/input/kempston_joy.h" + +void khan_set_joystick_state(uint8_t state) { + xPlatform::Handler()->Speccy()->Devices().Get()->setState(state); +} +#endif +namespace xIo { + +bool MkDir(const char* path) { + assert(false); + return false; +} + +} + +#if !PICO_ON_DEVICE +#include +#endif + +namespace xPlatform { +#if false && !PICO_ON_DEVICE + static bool PreProcessKey(SDL_Event& e) + { + if(e.key.keysym.mod) + return false; + if(e.type != SDL_KEYUP) + return false; + switch(e.key.keysym.sym) + { + case SDL_SCANCODE_F2: + { + using namespace xOptions; + eOptionB* o = eOptionB::Find("save state"); + SAFE_CALL(o)->Change(); + } + return true; + case SDL_SCANCODE_F3: + { + using namespace xOptions; + eOptionB* o = eOptionB::Find("load state"); + SAFE_CALL(o)->Change(); + } + return true; + case SDL_SCANCODE_F5: +#ifndef NO_USE_TAPE + Handler()->OnAction(A_TAPE_TOGGLE); +#endif + return true; + case SDL_SCANCODE_F7: + { + using namespace xOptions; + eOptionB* o = eOptionB::Find("pause"); + SAFE_CALL(o)->Change(); + } + return true; + case SDL_SCANCODE_F12: + Handler()->OnAction(A_RESET); + return true; + default: + return false; + } + } +#endif + + static byte TranslateScancode(SDL_Scancode _scancode, dword& _flags) + { + switch(_scancode) + { + case SDL_SCANCODE_GRAVE:return 'm'; + case SDL_SCANCODE_LSHIFT: return 'c'; + case SDL_SCANCODE_RSHIFT: return 'c'; + case SDL_SCANCODE_LALT: return 's'; + case SDL_SCANCODE_RALT: return 's'; + case SDL_SCANCODE_RETURN: return 'e'; + case SDL_SCANCODE_BACKSPACE: + _flags |= KF_SHIFT; + return '0'; + case SDL_SCANCODE_APOSTROPHE: + _flags |= KF_ALT; + if(_flags&KF_SHIFT) + { + _flags &= ~KF_SHIFT; + return 'P'; + } + else + return '7'; + case SDL_SCANCODE_COMMA: + _flags |= KF_ALT; + if(_flags&KF_SHIFT) + { + _flags &= ~KF_SHIFT; + return 'R'; + } + else + return 'N'; + case SDL_SCANCODE_PERIOD: + _flags |= KF_ALT; + if(_flags&KF_SHIFT) + { + _flags &= ~KF_SHIFT; + return 'T'; + } + else + return 'M'; + case SDL_SCANCODE_SEMICOLON: + _flags |= KF_ALT; + if(_flags&KF_SHIFT) + { + _flags &= ~KF_SHIFT; + return 'Z'; + } + else + return 'O'; + case SDL_SCANCODE_SLASH: + _flags |= KF_ALT; + if(_flags&KF_SHIFT) + { + _flags &= ~KF_SHIFT; + return 'C'; + } + else + return 'V'; + case SDL_SCANCODE_MINUS: + _flags |= KF_ALT; + if(_flags&KF_SHIFT) + { + _flags &= ~KF_SHIFT; + return '0'; + } + else + return 'J'; + case SDL_SCANCODE_EQUALS: + _flags |= KF_ALT; + if(_flags&KF_SHIFT) + { + _flags &= ~KF_SHIFT; + return 'K'; + } + else + return 'L'; + case SDL_SCANCODE_TAB: + _flags |= KF_ALT; + _flags |= KF_SHIFT; + return 0; + case SDL_SCANCODE_LEFT: return 'l'; + case SDL_SCANCODE_RIGHT: return 'r'; + case SDL_SCANCODE_UP: return 'u'; + case SDL_SCANCODE_DOWN: return 'd'; + case SDL_SCANCODE_INSERT: + case SDL_SCANCODE_RCTRL: + case SDL_SCANCODE_LCTRL: return 'f'; + case SDL_SCANCODE_0: return '0'; + default: + break; + } + if(_scancode >= SDL_SCANCODE_1 && _scancode <= SDL_SCANCODE_9) + return _scancode + '1' - SDL_SCANCODE_1; + if(_scancode >= SDL_SCANCODE_A && _scancode <= SDL_SCANCODE_Z) + return 'A' + _scancode - SDL_SCANCODE_A; + if(_scancode == SDL_SCANCODE_SPACE) + return ' '; + return 0; + } +} + +bool cursor_mode = true; + +void update_cursor_mode(int scan) { + cursor_mode = scan >= SDL_SCANCODE_RIGHT && scan <= SDL_SCANCODE_UP; +} + +void khan_key_down(int scan, int sym, int mod) { + if (scan == SDL_SCANCODE_ESCAPE) { + khan_menu_key(MK_ESCAPE); + return; + } + update_cursor_mode(scan); + if (!kms.opacity || kms.disappearing) + { + dword flags = xPlatform::KF_DOWN; + if (mod & KMOD_ALT) + flags |= xPlatform::KF_ALT; + if (mod & KMOD_SHIFT) + flags |= xPlatform::KF_SHIFT; + if (cursor_mode) + flags |= xPlatform::KF_CURSOR; + byte key = xPlatform::TranslateScancode((SDL_Scancode)scan, flags); + khan_zx_key_event(key, flags); + } else { + switch (scan) { + case SDL_SCANCODE_LEFT: + khan_menu_key(MK_LEFT); + return; + case SDL_SCANCODE_RIGHT: + khan_menu_key(MK_RIGHT); + return; + case SDL_SCANCODE_DOWN: + khan_menu_key(MK_DOWN); + return; + case SDL_SCANCODE_UP: + khan_menu_key(MK_UP); + return; + case SDL_SCANCODE_RETURN: + khan_menu_key(MK_ENTER); + return; + } + } +} + +void khan_key_up(int scan, int sym, int mod) { + if (sym == SDL_SCANCODE_ESCAPE) { + return; + } + dword flags = 0;//xPlatform::KF_DOWN; + if(mod&KMOD_ALT) + flags |= xPlatform::KF_ALT; + if(mod&KMOD_SHIFT) + flags |= xPlatform::KF_SHIFT; + update_cursor_mode(scan); + if (cursor_mode) + flags |= xPlatform::KF_CURSOR; + byte key = xPlatform::TranslateScancode((SDL_Scancode)scan, flags); + khan_zx_key_event(key, flags); +} + +#ifndef NO_USE_AY +int ay_sample_rate() { + return SNDR_DEFAULT_SAMPLE_RATE; +} +#endif \ No newline at end of file diff --git a/khan/khan_lib.h b/khan/khan_lib.h new file mode 100644 index 0000000..5d264c1 --- /dev/null +++ b/khan/khan_lib.h @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef KHAN_LIB_H +#define KHAN_LIB_H +#include "pico.h" +#include "pico/sync.h" + +//#if PICO_ON_DEVICE +//#define __maybe_in_ram __attribute__((section(".time_critical"))) +//#else +#define __maybe_in_ram +//#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern int khan_init(); +extern int khan_go(int video_mode_hz); + +void khan_key_down(int scan, int sym, int mod); +void khan_key_up(int scan, int sym, int mod); + +bool khan_get_scanline_info(int l, uint16_t *border, const uint8_t **attr, const uint8_t **pixels, const uint32_t **attr_colors, const uint32_t **dark_attr_colors); + +// these calls are into khan from the rendering thread + +// called from the rendering thread after the last visible line is rendered... a good time to do some per frame stuff +extern void khan_idle_blanking(); +extern void khan_fill_main_menu(); +extern void khan_zx_key_event(uint8_t key, uint8_t flags); +enum menu_key +{ + MK_NONE, + MK_ESCAPE, + MK_UP, + MK_DOWN, + MK_LEFT, + MK_RIGHT, + MK_ENTER +}; +extern void khan_menu_key(enum menu_key key); +extern void khan_hide_menu(); + +// callbacks from khan on the cpu thread +extern bool khan_cb_is_button_down(); +extern void khan_cb_begin_frame(); +extern void khan_cb_end_frame(); +// returns true if the key was consumed +extern bool khan_menu_selection_change(enum menu_key key); +#ifdef USE_KHAN_GPIO +extern uint8_t khan_gpio_read(uint reg); +extern void khan_gpio_write(uint reg, uint8_t value); +#endif + +#ifndef NO_USE_KEMPSTON +#define KEMPSTON_R 0x01 +#define KEMPSTON_L 0x02 +#define KEMPSTON_D 0x04 +#define KEMPSTON_U 0x08 +#define KEMPSTON_F 0x10 +extern void khan_set_joystick_state(uint8_t state); +#endif + +extern void khan_beeper_reset(); +extern void khan_beeper_begin_frame(uint32_t t, int32_t frame_num); +// t is within frame +extern void khan_beeper_level_change(uint32_t t, uint8_t level); +extern void khan_beeper_end_frame(uint32_t t); + +struct text_element +{ + const char *text; + int width; +}; + + +extern const uint8_t atlantis_glyph_bitmap[]; + +extern const uint8_t atlantis_glyph_widths[]; + +#define MENU_GLYPH_MIN 32 +#define MENU_GLYPH_COUNT 95 +#define MENU_GLYPH_MAX (MENU_GLYPH_MIN + MENU_GLYPH_COUNT - 1) +#define MENU_GLYPH_HEIGHT 9 +#define MENU_GLYPH_Y_OFFSET 2 +#define MENU_GLYPH_ADVANCE 1 + +// so we're offset a bit +#define MENU_WIDTH_IN_BLOCKS 17 +#define MENU_LEFT_WIDTH_IN_BLOCKS 10 +#define MENU_RIGHT_WIDTH_IN_BLOCKS (MENU_WIDTH_IN_BLOCKS - MENU_LEFT_WIDTH_IN_BLOCKS) + +#define MENU_LINE_HEIGHT 12 +#define MENU_LINE_BORDER_WIDTH 4 +#define MENU_LINE_TEXT_INDENT 6 +#ifndef NO_USE_128K +#define MENU_MAX_LINES 11 +#else +#define MENU_MAX_LINES 9 +#endif + +// must be <32 +#define MENU_AREA_WIDTH_IN_BLOCKS 24 +#define MENU_AREA_OFFSET_Y 4 +#define MENU_AREA_BORDER_HEIGHT 2 +// how opaque the bg is +//#define MENU_OPACITY_MAX 24 +//#define MENU_OPACITY_OFFSET 0 +#define MENU_OPACITY_MAX 20 +#define MENU_OPACITY_OFFSET 3 +// went the fg kicks in +#define MENU_OPACITY_THRESHOLD 10 + +#define MENU_AREA_WIDTH (MENU_AREA_WIDTH_IN_BLOCKS * 8) +#define MENU_WIDTH (MENU_WIDTH_IN_BLOCKS * 8) +#define MENU_BORDER_WIDTH_IN_BLOCKS ((MENU_AREA_WIDTH-MENU_WIDTH)/2) + +// note this is assumed to be 16 +#define MENU_ERROR_LEVEL_MAX 16 +#define MENU_FLASH_LENGTH 32 + +#define menu_glypth_bitmap atlantis_glyph_bitmap +#define menu_glyph_widths atlantis_glyph_widths + +struct menu_state +{ + struct mutex mutex; + struct + { + struct text_element left_text; + struct text_element right_text; + } lines[MENU_MAX_LINES]; + int16_t opacity; + bool appearing; + bool disappearing; + bool flashing; + + int8_t num_lines; + int8_t selected_line; + int8_t selection_top_pixel; + int8_t flash_pos; + int8_t error_level; + + // protected by mutex + bool do_hide_menu; + bool do_fill_menu; +}; + +extern struct menu_state kms; + +#ifndef NO_USE_AY +int ay_sample_rate(); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/khan/memmap_khan.ld b/khan/memmap_khan.ld new file mode 100644 index 0000000..47238e9 --- /dev/null +++ b/khan/memmap_khan.ld @@ -0,0 +1,291 @@ +/* Based on GCC ARM embedded samples. + Defines the following symbols for use by code: + __exidx_start + __exidx_end + __etext + __data_start__ + __preinit_array_start + __preinit_array_end + __init_array_start + __init_array_end + __fini_array_start + __fini_array_end + __data_end__ + __bss_start__ + __bss_end__ + __end__ + end + __HeapLimit + __StackLimit + __StackTop + __stack (== StackTop) +*/ + +MEMORY +{ + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k +/* RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k + SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k*/ + + /* not currently using scratch except for stack, so shrink and have more ram */ + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 262k + SCRATCH_X(rwx) : ORIGIN = 0x20041800, LENGTH = 1k + SCRATCH_Y(rwx) : ORIGIN = 0x20041c00, LENGTH = 1k +} + +ENTRY(_entry_point) + +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes big + and checksummed. It is usually built by the boot_stage2 target + in the Pico SDK + */ + + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > FLASH + + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size") + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ + + .text : { + __logical_binary_start = .; + KEEP (*(.vectors)) + KEEP (*(.binary_info_header)) + __binary_info_header_end = .; + KEEP (*(.reset)) + *runtime.c.o*(.text*) + *pll.c.o*(.text*) + *pio.c.o*(.text*) + *dma.c.o*(.text*) + *irq.c.o*(.text*) + *stdio.c.o*(.text*) + *stdout.c.o*(.text*) + *stdlib.c.o*(.text*) + *lock_core.c.o*(.text*) + *sample_encoding.cpp.o*(.text*) + *printf.c.o*(.text*) + *multicore.c.o*(.text*) + *claim.c.o*(.text*) + *pico_malloc.c.o*(.text*) + *bootrom_helper.c.o*(.text*) + *lib_a*.o(.text*) + *options.cpp.obj(.text*) + *options_common.cpp.obj(.text*) + *speccy.cpp.obj(.text*) + *speccy_handler.cpp.obj(.text*) + *clocks.c.obj(.text*) + *runtime.c.obj(.text*) + *khan_init.cpp.obj(.text*) + *(.text.__wrap___aeabi_ldiv) + *(.text.process_enumeration) + *(.text._get_descriptor) + *(.text.scanvideo_setup_with_timing) + *(.text.process_set_config) + *(.text.hidh_open) + *(.text.hidh_set_config) + *(.text.usbh_driver_set_config_complete) + *(.text.audio_pwm_setup) + *(.text.audio_pwm_setup) + *(.text._Z21audio_new_buffer_poolP19audio_buffer_formatii) + *(.text._malloc_r) + *(.text._free_r) + *printf.c.obj(*) + /**platform_common.c.o*(.text*)*/ + *(.init) + /**(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)*/ + *(.fini) + /* Pull all c'tors into .text */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* Followed by destructors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .rodata : { + *(*.game_data) + /**(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a: *khan*) .rodata*)*/ + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) + . = ALIGN(4); + } > FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + __exidx_start = .; + .ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + __exidx_end = .; + + /* Machine inspectable binary information */ + . = ALIGN(4); + __binary_info_start = .; + .binary_info : + { + KEEP(*(.binary_info.keep.*)) + *(.binary_info.*) + } > FLASH + __binary_info_end = .; + . = ALIGN(4); + + /* End of .text-like segments */ + __etext = .; + + .ram_vector_table (COPY): { + *(.ram_vector_table) + } > RAM + + .data : { + __data_start__ = .; + *(vtable) + + *(.time_critical*) + + /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ + *(.text*) + . = ALIGN(4); + *(.rodata*) + . = ALIGN(4); + + *(.data*) + + . = ALIGN(4); + *(.after_data.*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__mutex_array_start = .); + KEEP(*(SORT(.mutex_array.*))) + KEEP(*(.mutex_array)) + PROVIDE_HIDDEN (__mutex_array_end = .); + + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(SORT(.preinit_array.*))) + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN (__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN (__fini_array_end = .); + + *(.jcr) + . = ALIGN(4); + __ram64k = .; + *(.ram64k) + + /* All data end */ + __data_end__ = .; + } > RAM AT> FLASH + + .uninitialized_data (COPY): { + . = ALIGN(4); + *(.uninitialized_data*) + } > RAM + + /* Start and end symbols must be word-aligned */ + .scratch_x : { + __scratch_x_start__ = .; + *(.scratch_x.*) + . = ALIGN(4); + __scratch_x_end__ = .; + } > SCRATCH_X AT > FLASH + __scratch_x_source__ = LOADADDR(.scratch_x); + + .scratch_y : { + __scratch_y_start__ = .; + *(.scratch_y.*) + . = ALIGN(4); + __scratch_y_end__ = .; + } > SCRATCH_Y AT > FLASH + __scratch_y_source__ = LOADADDR(.scratch_y); + + .bss : { + . = ALIGN(4); + __bss_start__ = .; + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (COPY): + { + __end__ = .; + end = __end__; + *(.heap*) + __HeapLimit = .; + } > RAM + + /* .stack*_dummy section doesn't contains any symbols. It is only + * used for linker to calculate size of stack sections, and assign + * values to stack symbols later + * + * stack1 section may be empty/missing if platform_launch_core1 is not used */ + + /* by default we put core 0 stack at the end of scratch Y, so that if core 1 + * stack is not used then all of SCRATCH_X is free. + */ + .stack1_dummy (COPY): + { + *(.stack1*) + } > SCRATCH_X + .stack_dummy (COPY): + { + *(.stack*) + } > SCRATCH_Y + + .flash_end : { + __flash_binary_end = .; + } > FLASH + + /* stack limit is poorly named, but historically is maximum heap ptr */ + __StackLimit = ORIGIN(RAM) + LENGTH(RAM); + __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); + __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); + __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); + __StackBottom = __StackTop - SIZEOF(.stack_dummy); + PROVIDE(__stack = __StackTop); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") + + ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") + /* todo assert on extra code */ +} + diff --git a/khan/ram64k.h b/khan/ram64k.h new file mode 100644 index 0000000..c21b4b1 --- /dev/null +++ b/khan/ram64k.h @@ -0,0 +1,1368 @@ +__attribute__((section(".ram64k"))) unsigned char ram64k[65536] = { + 0xf3, 0xaf, 0x11, 0xff, 0xff, 0xc3, 0xcb, 0x11, 0x2a, 0x5d, 0x5c, 0x22, + 0x5f, 0x5c, 0x18, 0x43, 0xc3, 0xf2, 0x15, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x2a, 0x5d, 0x5c, 0x7e, 0xcd, 0x7d, 0x00, 0xd0, 0xcd, 0x74, 0x00, 0x18, + 0xf7, 0xff, 0xff, 0xff, 0xc3, 0x5b, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc5, 0x2a, 0x61, 0x5c, 0xe5, 0xc3, 0x9e, 0x16, 0xf5, 0xe5, 0x2a, 0x78, + 0x5c, 0x23, 0x22, 0x78, 0x5c, 0x7c, 0xb5, 0x20, 0x03, 0xfd, 0x34, 0x40, + 0xc5, 0xd5, 0xcd, 0xbf, 0x02, 0xd1, 0xc1, 0xe1, 0xf1, 0xfb, 0xc9, 0xe1, + 0x6e, 0xfd, 0x75, 0x00, 0xed, 0x7b, 0x3d, 0x5c, 0xc3, 0xc5, 0x16, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0xe5, 0x2a, 0xb0, 0x5c, 0x7c, + 0xb5, 0x20, 0x01, 0xe9, 0xe1, 0xf1, 0xed, 0x45, 0x2a, 0x5d, 0x5c, 0x23, + 0x22, 0x5d, 0x5c, 0x7e, 0xc9, 0xfe, 0x21, 0xd0, 0xfe, 0x0d, 0xc8, 0xfe, + 0x10, 0xd8, 0xfe, 0x18, 0x3f, 0xd8, 0x23, 0xfe, 0x16, 0x38, 0x01, 0x23, + 0x37, 0x22, 0x5d, 0x5c, 0xc9, 0xbf, 0x52, 0x4e, 0xc4, 0x49, 0x4e, 0x4b, + 0x45, 0x59, 0xa4, 0x50, 0xc9, 0x46, 0xce, 0x50, 0x4f, 0x49, 0x4e, 0xd4, + 0x53, 0x43, 0x52, 0x45, 0x45, 0x4e, 0xa4, 0x41, 0x54, 0x54, 0xd2, 0x41, + 0xd4, 0x54, 0x41, 0xc2, 0x56, 0x41, 0x4c, 0xa4, 0x43, 0x4f, 0x44, 0xc5, + 0x56, 0x41, 0xcc, 0x4c, 0x45, 0xce, 0x53, 0x49, 0xce, 0x43, 0x4f, 0xd3, + 0x54, 0x41, 0xce, 0x41, 0x53, 0xce, 0x41, 0x43, 0xd3, 0x41, 0x54, 0xce, + 0x4c, 0xce, 0x45, 0x58, 0xd0, 0x49, 0x4e, 0xd4, 0x53, 0x51, 0xd2, 0x53, + 0x47, 0xce, 0x41, 0x42, 0xd3, 0x50, 0x45, 0x45, 0xcb, 0x49, 0xce, 0x55, + 0x53, 0xd2, 0x53, 0x54, 0x52, 0xa4, 0x43, 0x48, 0x52, 0xa4, 0x4e, 0x4f, + 0xd4, 0x42, 0x49, 0xce, 0x4f, 0xd2, 0x41, 0x4e, 0xc4, 0x3c, 0xbd, 0x3e, + 0xbd, 0x3c, 0xbe, 0x4c, 0x49, 0x4e, 0xc5, 0x54, 0x48, 0x45, 0xce, 0x54, + 0xcf, 0x53, 0x54, 0x45, 0xd0, 0x44, 0x45, 0x46, 0x20, 0x46, 0xce, 0x43, + 0x41, 0xd4, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0xd4, 0x4d, 0x4f, 0x56, 0xc5, + 0x45, 0x52, 0x41, 0x53, 0xc5, 0x4f, 0x50, 0x45, 0x4e, 0x20, 0xa3, 0x43, + 0x4c, 0x4f, 0x53, 0x45, 0x20, 0xa3, 0x4d, 0x45, 0x52, 0x47, 0xc5, 0x56, + 0x45, 0x52, 0x49, 0x46, 0xd9, 0x42, 0x45, 0x45, 0xd0, 0x43, 0x49, 0x52, + 0x43, 0x4c, 0xc5, 0x49, 0x4e, 0xcb, 0x50, 0x41, 0x50, 0x45, 0xd2, 0x46, + 0x4c, 0x41, 0x53, 0xc8, 0x42, 0x52, 0x49, 0x47, 0x48, 0xd4, 0x49, 0x4e, + 0x56, 0x45, 0x52, 0x53, 0xc5, 0x4f, 0x56, 0x45, 0xd2, 0x4f, 0x55, 0xd4, + 0x4c, 0x50, 0x52, 0x49, 0x4e, 0xd4, 0x4c, 0x4c, 0x49, 0x53, 0xd4, 0x53, + 0x54, 0x4f, 0xd0, 0x52, 0x45, 0x41, 0xc4, 0x44, 0x41, 0x54, 0xc1, 0x52, + 0x45, 0x53, 0x54, 0x4f, 0x52, 0xc5, 0x4e, 0x45, 0xd7, 0x42, 0x4f, 0x52, + 0x44, 0x45, 0xd2, 0x43, 0x4f, 0x4e, 0x54, 0x49, 0x4e, 0x55, 0xc5, 0x44, + 0x49, 0xcd, 0x52, 0x45, 0xcd, 0x46, 0x4f, 0xd2, 0x47, 0x4f, 0x20, 0x54, + 0xcf, 0x47, 0x4f, 0x20, 0x53, 0x55, 0xc2, 0x49, 0x4e, 0x50, 0x55, 0xd4, + 0x4c, 0x4f, 0x41, 0xc4, 0x4c, 0x49, 0x53, 0xd4, 0x4c, 0x45, 0xd4, 0x50, + 0x41, 0x55, 0x53, 0xc5, 0x4e, 0x45, 0x58, 0xd4, 0x50, 0x4f, 0x4b, 0xc5, + 0x50, 0x52, 0x49, 0x4e, 0xd4, 0x50, 0x4c, 0x4f, 0xd4, 0x52, 0x55, 0xce, + 0x53, 0x41, 0x56, 0xc5, 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x49, 0x5a, + 0xc5, 0x49, 0xc6, 0x43, 0x4c, 0xd3, 0x44, 0x52, 0x41, 0xd7, 0x43, 0x4c, + 0x45, 0x41, 0xd2, 0x52, 0x45, 0x54, 0x55, 0x52, 0xce, 0x43, 0x4f, 0x50, + 0xd9, 0x42, 0x48, 0x59, 0x36, 0x35, 0x54, 0x47, 0x56, 0x4e, 0x4a, 0x55, + 0x37, 0x34, 0x52, 0x46, 0x43, 0x4d, 0x4b, 0x49, 0x38, 0x33, 0x45, 0x44, + 0x58, 0x0e, 0x4c, 0x4f, 0x39, 0x32, 0x57, 0x53, 0x5a, 0x20, 0x0d, 0x50, + 0x30, 0x31, 0x51, 0x41, 0xe3, 0xc4, 0xe0, 0xe4, 0xb4, 0xbc, 0xbd, 0xbb, + 0xaf, 0xb0, 0xb1, 0xc0, 0xa7, 0xa6, 0xbe, 0xad, 0xb2, 0xba, 0xe5, 0xa5, + 0xc2, 0xe1, 0xb3, 0xb9, 0xc1, 0xb8, 0x7e, 0xdc, 0xda, 0x5c, 0xb7, 0x7b, + 0x7d, 0xd8, 0xbf, 0xae, 0xaa, 0xab, 0xdd, 0xde, 0xdf, 0x7f, 0xb5, 0xd6, + 0x7c, 0xd5, 0x5d, 0xdb, 0xb6, 0xd9, 0x5b, 0xd7, 0x0c, 0x07, 0x06, 0x04, + 0x05, 0x08, 0x0a, 0x0b, 0x09, 0x0f, 0xe2, 0x2a, 0x3f, 0xcd, 0xc8, 0xcc, + 0xcb, 0x5e, 0xac, 0x2d, 0x2b, 0x3d, 0x2e, 0x2c, 0x3b, 0x22, 0xc7, 0x3c, + 0xc3, 0x3e, 0xc5, 0x2f, 0xc9, 0x60, 0xc6, 0x3a, 0xd0, 0xce, 0xa8, 0xca, + 0xd3, 0xd4, 0xd1, 0xd2, 0xa9, 0xcf, 0x2e, 0x2f, 0x11, 0xff, 0xff, 0x01, + 0xfe, 0xfe, 0xed, 0x78, 0x2f, 0xe6, 0x1f, 0x28, 0x0e, 0x67, 0x7d, 0x14, + 0xc0, 0xd6, 0x08, 0xcb, 0x3c, 0x30, 0xfa, 0x53, 0x5f, 0x20, 0xf4, 0x2d, + 0xcb, 0x00, 0x38, 0xe6, 0x7a, 0x3c, 0xc8, 0xfe, 0x28, 0xc8, 0xfe, 0x19, + 0xc8, 0x7b, 0x5a, 0x57, 0xfe, 0x18, 0xc9, 0xcd, 0x8e, 0x02, 0xc0, 0x21, + 0x00, 0x5c, 0xcb, 0x7e, 0x20, 0x07, 0x23, 0x35, 0x2b, 0x20, 0x02, 0x36, + 0xff, 0x7d, 0x21, 0x04, 0x5c, 0xbd, 0x20, 0xee, 0xcd, 0x1e, 0x03, 0xd0, + 0x21, 0x00, 0x5c, 0xbe, 0x28, 0x2e, 0xeb, 0x21, 0x04, 0x5c, 0xbe, 0x28, + 0x27, 0xcb, 0x7e, 0x20, 0x04, 0xeb, 0xcb, 0x7e, 0xc8, 0x5f, 0x77, 0x23, + 0x36, 0x05, 0x23, 0x3a, 0x09, 0x5c, 0x77, 0x23, 0xfd, 0x4e, 0x07, 0xfd, + 0x56, 0x01, 0xe5, 0xcd, 0x33, 0x03, 0xe1, 0x77, 0x32, 0x08, 0x5c, 0xfd, + 0xcb, 0x01, 0xee, 0xc9, 0x23, 0x36, 0x05, 0x23, 0x35, 0xc0, 0x3a, 0x0a, + 0x5c, 0x77, 0x23, 0x7e, 0x18, 0xea, 0x42, 0x16, 0x00, 0x7b, 0xfe, 0x27, + 0xd0, 0xfe, 0x18, 0x20, 0x03, 0xcb, 0x78, 0xc0, 0x21, 0x05, 0x02, 0x19, + 0x7e, 0x37, 0xc9, 0x7b, 0xfe, 0x3a, 0x38, 0x2f, 0x0d, 0xfa, 0x4f, 0x03, + 0x28, 0x03, 0xc6, 0x4f, 0xc9, 0x21, 0xeb, 0x01, 0x04, 0x28, 0x03, 0x21, + 0x05, 0x02, 0x16, 0x00, 0x19, 0x7e, 0xc9, 0x21, 0x29, 0x02, 0xcb, 0x40, + 0x28, 0xf4, 0xcb, 0x5a, 0x28, 0x0a, 0xfd, 0xcb, 0x30, 0x5e, 0xc0, 0x04, + 0xc0, 0xc6, 0x20, 0xc9, 0xc6, 0xa5, 0xc9, 0xfe, 0x30, 0xd8, 0x0d, 0xfa, + 0x9d, 0x03, 0x20, 0x19, 0x21, 0x54, 0x02, 0xcb, 0x68, 0x28, 0xd3, 0xfe, + 0x38, 0x30, 0x07, 0xd6, 0x20, 0x04, 0xc8, 0xc6, 0x08, 0xc9, 0xd6, 0x36, + 0x04, 0xc8, 0xc6, 0xfe, 0xc9, 0x21, 0x30, 0x02, 0xfe, 0x39, 0x28, 0xba, + 0xfe, 0x30, 0x28, 0xb6, 0xe6, 0x07, 0xc6, 0x80, 0x04, 0xc8, 0xee, 0x0f, + 0xc9, 0x04, 0xc8, 0xcb, 0x68, 0x21, 0x30, 0x02, 0x20, 0xa4, 0xd6, 0x10, + 0xfe, 0x22, 0x28, 0x06, 0xfe, 0x20, 0xc0, 0x3e, 0x5f, 0xc9, 0x3e, 0x40, + 0xc9, 0xf3, 0x7d, 0xcb, 0x3d, 0xcb, 0x3d, 0x2f, 0xe6, 0x03, 0x4f, 0x06, + 0x00, 0xdd, 0x21, 0xd1, 0x03, 0xdd, 0x09, 0x3a, 0x48, 0x5c, 0xe6, 0x38, + 0x0f, 0x0f, 0x0f, 0xf6, 0x08, 0x00, 0x00, 0x00, 0x04, 0x0c, 0x0d, 0x20, + 0xfd, 0x0e, 0x3f, 0x05, 0xc2, 0xd6, 0x03, 0xee, 0x10, 0xd3, 0xfe, 0x44, + 0x4f, 0xcb, 0x67, 0x20, 0x09, 0x7a, 0xb3, 0x28, 0x09, 0x79, 0x4d, 0x1b, + 0xdd, 0xe9, 0x4d, 0x0c, 0xdd, 0xe9, 0xfb, 0xc9, 0xef, 0x31, 0x27, 0xc0, + 0x03, 0x34, 0xec, 0x6c, 0x98, 0x1f, 0xf5, 0x04, 0xa1, 0x0f, 0x38, 0x21, + 0x92, 0x5c, 0x7e, 0xa7, 0x20, 0x5e, 0x23, 0x4e, 0x23, 0x46, 0x78, 0x17, + 0x9f, 0xb9, 0x20, 0x54, 0x23, 0xbe, 0x20, 0x50, 0x78, 0xc6, 0x3c, 0xf2, + 0x25, 0x04, 0xe2, 0x6c, 0x04, 0x06, 0xfa, 0x04, 0xd6, 0x0c, 0x30, 0xfb, + 0xc6, 0x0c, 0xc5, 0x21, 0x6e, 0x04, 0xcd, 0x06, 0x34, 0xcd, 0xb4, 0x33, + 0xef, 0x04, 0x38, 0xf1, 0x86, 0x77, 0xef, 0xc0, 0x02, 0x31, 0x38, 0xcd, + 0x94, 0x1e, 0xfe, 0x0b, 0x30, 0x22, 0xef, 0xe0, 0x04, 0xe0, 0x34, 0x80, + 0x43, 0x55, 0x9f, 0x80, 0x01, 0x05, 0x34, 0x35, 0x71, 0x03, 0x38, 0xcd, + 0x99, 0x1e, 0xc5, 0xcd, 0x99, 0x1e, 0xe1, 0x50, 0x59, 0x7a, 0xb3, 0xc8, + 0x1b, 0xc3, 0xb5, 0x03, 0xcf, 0x0a, 0x89, 0x02, 0xd0, 0x12, 0x86, 0x89, + 0x0a, 0x97, 0x60, 0x75, 0x89, 0x12, 0xd5, 0x17, 0x1f, 0x89, 0x1b, 0x90, + 0x41, 0x02, 0x89, 0x24, 0xd0, 0x53, 0xca, 0x89, 0x2e, 0x9d, 0x36, 0xb1, + 0x89, 0x38, 0xff, 0x49, 0x3e, 0x89, 0x43, 0xff, 0x6a, 0x73, 0x89, 0x4f, + 0xa7, 0x00, 0x54, 0x89, 0x5c, 0x00, 0x00, 0x00, 0x89, 0x69, 0x14, 0xf6, + 0x24, 0x89, 0x76, 0xf1, 0x10, 0x05, 0xcd, 0xfb, 0x24, 0x3a, 0x3b, 0x5c, + 0x87, 0xfa, 0x8a, 0x1c, 0xe1, 0xd0, 0xe5, 0xcd, 0xf1, 0x2b, 0x62, 0x6b, + 0x0d, 0xf8, 0x09, 0xcb, 0xfe, 0xc9, 0x21, 0x3f, 0x05, 0xe5, 0x21, 0x80, + 0x1f, 0xcb, 0x7f, 0x28, 0x03, 0x21, 0x98, 0x0c, 0x08, 0x13, 0xdd, 0x2b, + 0xf3, 0x3e, 0x02, 0x47, 0x10, 0xfe, 0xd3, 0xfe, 0xee, 0x0f, 0x06, 0xa4, + 0x2d, 0x20, 0xf5, 0x05, 0x25, 0xf2, 0xd8, 0x04, 0x06, 0x2f, 0x10, 0xfe, + 0xd3, 0xfe, 0x3e, 0x0d, 0x06, 0x37, 0x10, 0xfe, 0xd3, 0xfe, 0x01, 0x0e, + 0x3b, 0x08, 0x6f, 0xc3, 0x07, 0x05, 0x7a, 0xb3, 0x28, 0x0c, 0xdd, 0x6e, + 0x00, 0x7c, 0xad, 0x67, 0x3e, 0x01, 0x37, 0xc3, 0x25, 0x05, 0x6c, 0x18, + 0xf4, 0x79, 0xcb, 0x78, 0x10, 0xfe, 0x30, 0x04, 0x06, 0x42, 0x10, 0xfe, + 0xd3, 0xfe, 0x06, 0x3e, 0x20, 0xef, 0x05, 0xaf, 0x3c, 0xcb, 0x15, 0xc2, + 0x14, 0x05, 0x1b, 0xdd, 0x23, 0x06, 0x31, 0x3e, 0x7f, 0xdb, 0xfe, 0x1f, + 0xd0, 0x7a, 0x3c, 0xc2, 0xfe, 0x04, 0x06, 0x3b, 0x10, 0xfe, 0xc9, 0xf5, + 0x3a, 0x48, 0x5c, 0xe6, 0x38, 0x0f, 0x0f, 0x0f, 0xd3, 0xfe, 0x3e, 0x7f, + 0xdb, 0xfe, 0x1f, 0xfb, 0x38, 0x02, 0xcf, 0x0c, 0xf1, 0xc9, 0x14, 0x08, + 0x15, 0xf3, 0x3e, 0x0f, 0xd3, 0xfe, 0x21, 0x3f, 0x05, 0xe5, 0xdb, 0xfe, + 0x1f, 0xe6, 0x20, 0xf6, 0x02, 0x4f, 0xbf, 0xc0, 0xcd, 0xe7, 0x05, 0x30, + 0xfa, 0x21, 0x15, 0x04, 0x10, 0xfe, 0x2b, 0x7c, 0xb5, 0x20, 0xf9, 0xcd, + 0xe3, 0x05, 0x30, 0xeb, 0x06, 0x9c, 0xcd, 0xe3, 0x05, 0x30, 0xe4, 0x3e, + 0xc6, 0xb8, 0x30, 0xe0, 0x24, 0x20, 0xf1, 0x06, 0xc9, 0xcd, 0xe7, 0x05, + 0x30, 0xd5, 0x78, 0xfe, 0xd4, 0x30, 0xf4, 0xcd, 0xe7, 0x05, 0xd0, 0x79, + 0xee, 0x03, 0x4f, 0x26, 0x00, 0x06, 0xb0, 0x18, 0x1f, 0x08, 0x20, 0x07, + 0x30, 0x0f, 0xdd, 0x75, 0x00, 0x18, 0x0f, 0xcb, 0x11, 0xad, 0xc0, 0x79, + 0x1f, 0x4f, 0x13, 0x18, 0x07, 0xdd, 0x7e, 0x00, 0xad, 0xc0, 0xdd, 0x23, + 0x1b, 0x08, 0x06, 0xb2, 0x2e, 0x01, 0xcd, 0xe3, 0x05, 0xd0, 0x3e, 0xcb, + 0xb8, 0xcb, 0x15, 0x06, 0xb0, 0xd2, 0xca, 0x05, 0x7c, 0xad, 0x67, 0x7a, + 0xb3, 0x20, 0xca, 0x7c, 0xfe, 0x01, 0xc9, 0xcd, 0xe7, 0x05, 0xd0, 0x3e, + 0x16, 0x3d, 0x20, 0xfd, 0xa7, 0x04, 0xc8, 0x3e, 0x7f, 0xdb, 0xfe, 0x1f, + 0xd0, 0xa9, 0xe6, 0x20, 0x28, 0xf3, 0x79, 0x2f, 0x4f, 0xe6, 0x07, 0xf6, + 0x08, 0xd3, 0xfe, 0x37, 0xc9, 0xf1, 0x3a, 0x74, 0x5c, 0xd6, 0xe0, 0x32, + 0x74, 0x5c, 0xcd, 0x8c, 0x1c, 0xcd, 0x30, 0x25, 0x28, 0x3c, 0x01, 0x11, + 0x00, 0x3a, 0x74, 0x5c, 0xa7, 0x28, 0x02, 0x0e, 0x22, 0xf7, 0xd5, 0xdd, + 0xe1, 0x06, 0x0b, 0x3e, 0x20, 0x12, 0x13, 0x10, 0xfc, 0xdd, 0x36, 0x01, + 0xff, 0xcd, 0xf1, 0x2b, 0x21, 0xf6, 0xff, 0x0b, 0x09, 0x03, 0x30, 0x0f, + 0x3a, 0x74, 0x5c, 0xa7, 0x20, 0x02, 0xcf, 0x0e, 0x78, 0xb1, 0x28, 0x0a, + 0x01, 0x0a, 0x00, 0xdd, 0xe5, 0xe1, 0x23, 0xeb, 0xed, 0xb0, 0xdf, 0xfe, + 0xe4, 0x20, 0x49, 0x3a, 0x74, 0x5c, 0xfe, 0x03, 0xca, 0x8a, 0x1c, 0xe7, + 0xcd, 0xb2, 0x28, 0xcb, 0xf9, 0x30, 0x0b, 0x21, 0x00, 0x00, 0x3a, 0x74, + 0x5c, 0x3d, 0x28, 0x15, 0xcf, 0x01, 0xc2, 0x8a, 0x1c, 0xcd, 0x30, 0x25, + 0x28, 0x18, 0x23, 0x7e, 0xdd, 0x77, 0x0b, 0x23, 0x7e, 0xdd, 0x77, 0x0c, + 0x23, 0xdd, 0x71, 0x0e, 0x3e, 0x01, 0xcb, 0x71, 0x28, 0x01, 0x3c, 0xdd, + 0x77, 0x00, 0xeb, 0xe7, 0xfe, 0x29, 0x20, 0xda, 0xe7, 0xcd, 0xee, 0x1b, + 0xeb, 0xc3, 0x5a, 0x07, 0xfe, 0xaa, 0x20, 0x1f, 0x3a, 0x74, 0x5c, 0xfe, + 0x03, 0xca, 0x8a, 0x1c, 0xe7, 0xcd, 0xee, 0x1b, 0xdd, 0x36, 0x0b, 0x00, + 0xdd, 0x36, 0x0c, 0x1b, 0x21, 0x00, 0x40, 0xdd, 0x75, 0x0d, 0xdd, 0x74, + 0x0e, 0x18, 0x4d, 0xfe, 0xaf, 0x20, 0x4f, 0x3a, 0x74, 0x5c, 0xfe, 0x03, + 0xca, 0x8a, 0x1c, 0xe7, 0xcd, 0x48, 0x20, 0x20, 0x0c, 0x3a, 0x74, 0x5c, + 0xa7, 0xca, 0x8a, 0x1c, 0xcd, 0xe6, 0x1c, 0x18, 0x0f, 0xcd, 0x82, 0x1c, + 0xdf, 0xfe, 0x2c, 0x28, 0x0c, 0x3a, 0x74, 0x5c, 0xa7, 0xca, 0x8a, 0x1c, + 0xcd, 0xe6, 0x1c, 0x18, 0x04, 0xe7, 0xcd, 0x82, 0x1c, 0xcd, 0xee, 0x1b, + 0xcd, 0x99, 0x1e, 0xdd, 0x71, 0x0b, 0xdd, 0x70, 0x0c, 0xcd, 0x99, 0x1e, + 0xdd, 0x71, 0x0d, 0xdd, 0x70, 0x0e, 0x60, 0x69, 0xdd, 0x36, 0x00, 0x03, + 0x18, 0x44, 0xfe, 0xca, 0x28, 0x09, 0xcd, 0xee, 0x1b, 0xdd, 0x36, 0x0e, + 0x80, 0x18, 0x17, 0x3a, 0x74, 0x5c, 0xa7, 0xc2, 0x8a, 0x1c, 0xe7, 0xcd, + 0x82, 0x1c, 0xcd, 0xee, 0x1b, 0xcd, 0x99, 0x1e, 0xdd, 0x71, 0x0d, 0xdd, + 0x70, 0x0e, 0xdd, 0x36, 0x00, 0x00, 0x2a, 0x59, 0x5c, 0xed, 0x5b, 0x53, + 0x5c, 0x37, 0xed, 0x52, 0xdd, 0x75, 0x0b, 0xdd, 0x74, 0x0c, 0x2a, 0x4b, + 0x5c, 0xed, 0x52, 0xdd, 0x75, 0x0f, 0xdd, 0x74, 0x10, 0xeb, 0x3a, 0x74, + 0x5c, 0xa7, 0xca, 0x70, 0x09, 0xe5, 0x01, 0x11, 0x00, 0xdd, 0x09, 0xdd, + 0xe5, 0x11, 0x11, 0x00, 0xaf, 0x37, 0xcd, 0x56, 0x05, 0xdd, 0xe1, 0x30, + 0xf2, 0x3e, 0xfe, 0xcd, 0x01, 0x16, 0xfd, 0x36, 0x52, 0x03, 0x0e, 0x80, + 0xdd, 0x7e, 0x00, 0xdd, 0xbe, 0xef, 0x20, 0x02, 0x0e, 0xf6, 0xfe, 0x04, + 0x30, 0xd9, 0x11, 0xc0, 0x09, 0xc5, 0xcd, 0x0a, 0x0c, 0xc1, 0xdd, 0xe5, + 0xd1, 0x21, 0xf0, 0xff, 0x19, 0x06, 0x0a, 0x7e, 0x3c, 0x20, 0x03, 0x79, + 0x80, 0x4f, 0x13, 0x1a, 0xbe, 0x23, 0x20, 0x01, 0x0c, 0xd7, 0x10, 0xf6, + 0xcb, 0x79, 0x20, 0xb3, 0x3e, 0x0d, 0xd7, 0xe1, 0xdd, 0x7e, 0x00, 0xfe, + 0x03, 0x28, 0x0c, 0x3a, 0x74, 0x5c, 0x3d, 0xca, 0x08, 0x08, 0xfe, 0x02, + 0xca, 0xb6, 0x08, 0xe5, 0xdd, 0x6e, 0xfa, 0xdd, 0x66, 0xfb, 0xdd, 0x5e, + 0x0b, 0xdd, 0x56, 0x0c, 0x7c, 0xb5, 0x28, 0x0d, 0xed, 0x52, 0x38, 0x26, + 0x28, 0x07, 0xdd, 0x7e, 0x00, 0xfe, 0x03, 0x20, 0x1d, 0xe1, 0x7c, 0xb5, + 0x20, 0x06, 0xdd, 0x6e, 0x0d, 0xdd, 0x66, 0x0e, 0xe5, 0xdd, 0xe1, 0x3a, + 0x74, 0x5c, 0xfe, 0x02, 0x37, 0x20, 0x01, 0xa7, 0x3e, 0xff, 0xcd, 0x56, + 0x05, 0xd8, 0xcf, 0x1a, 0xdd, 0x5e, 0x0b, 0xdd, 0x56, 0x0c, 0xe5, 0x7c, + 0xb5, 0x20, 0x06, 0x13, 0x13, 0x13, 0xeb, 0x18, 0x0c, 0xdd, 0x6e, 0xfa, + 0xdd, 0x66, 0xfb, 0xeb, 0x37, 0xed, 0x52, 0x38, 0x09, 0x11, 0x05, 0x00, + 0x19, 0x44, 0x4d, 0xcd, 0x05, 0x1f, 0xe1, 0xdd, 0x7e, 0x00, 0xa7, 0x28, + 0x3e, 0x7c, 0xb5, 0x28, 0x13, 0x2b, 0x46, 0x2b, 0x4e, 0x2b, 0x03, 0x03, + 0x03, 0xdd, 0x22, 0x5f, 0x5c, 0xcd, 0xe8, 0x19, 0xdd, 0x2a, 0x5f, 0x5c, + 0x2a, 0x59, 0x5c, 0x2b, 0xdd, 0x4e, 0x0b, 0xdd, 0x46, 0x0c, 0xc5, 0x03, + 0x03, 0x03, 0xdd, 0x7e, 0xfd, 0xf5, 0xcd, 0x55, 0x16, 0x23, 0xf1, 0x77, + 0xd1, 0x23, 0x73, 0x23, 0x72, 0x23, 0xe5, 0xdd, 0xe1, 0x37, 0x3e, 0xff, + 0xc3, 0x02, 0x08, 0xeb, 0x2a, 0x59, 0x5c, 0x2b, 0xdd, 0x22, 0x5f, 0x5c, + 0xdd, 0x4e, 0x0b, 0xdd, 0x46, 0x0c, 0xc5, 0xcd, 0xe5, 0x19, 0xc1, 0xe5, + 0xc5, 0xcd, 0x55, 0x16, 0xdd, 0x2a, 0x5f, 0x5c, 0x23, 0xdd, 0x4e, 0x0f, + 0xdd, 0x46, 0x10, 0x09, 0x22, 0x4b, 0x5c, 0xdd, 0x66, 0x0e, 0x7c, 0xe6, + 0xc0, 0x20, 0x0a, 0xdd, 0x6e, 0x0d, 0x22, 0x42, 0x5c, 0xfd, 0x36, 0x0a, + 0x00, 0xd1, 0xdd, 0xe1, 0x37, 0x3e, 0xff, 0xc3, 0x02, 0x08, 0xdd, 0x4e, + 0x0b, 0xdd, 0x46, 0x0c, 0xc5, 0x03, 0xf7, 0x36, 0x80, 0xeb, 0xd1, 0xe5, + 0xe5, 0xdd, 0xe1, 0x37, 0x3e, 0xff, 0xcd, 0x02, 0x08, 0xe1, 0xed, 0x5b, + 0x53, 0x5c, 0x7e, 0xe6, 0xc0, 0x20, 0x19, 0x1a, 0x13, 0xbe, 0x23, 0x20, + 0x02, 0x1a, 0xbe, 0x1b, 0x2b, 0x30, 0x08, 0xe5, 0xeb, 0xcd, 0xb8, 0x19, + 0xe1, 0x18, 0xec, 0xcd, 0x2c, 0x09, 0x18, 0xe2, 0x7e, 0x4f, 0xfe, 0x80, + 0xc8, 0xe5, 0x2a, 0x4b, 0x5c, 0x7e, 0xfe, 0x80, 0x28, 0x25, 0xb9, 0x28, + 0x08, 0xc5, 0xcd, 0xb8, 0x19, 0xc1, 0xeb, 0x18, 0xf0, 0xe6, 0xe0, 0xfe, + 0xa0, 0x20, 0x12, 0xd1, 0xd5, 0xe5, 0x23, 0x13, 0x1a, 0xbe, 0x20, 0x06, + 0x17, 0x30, 0xf7, 0xe1, 0x18, 0x03, 0xe1, 0x18, 0xe0, 0x3e, 0xff, 0xd1, + 0xeb, 0x3c, 0x37, 0xcd, 0x2c, 0x09, 0x18, 0xc4, 0x20, 0x10, 0x08, 0x22, + 0x5f, 0x5c, 0xeb, 0xcd, 0xb8, 0x19, 0xcd, 0xe8, 0x19, 0xeb, 0x2a, 0x5f, + 0x5c, 0x08, 0x08, 0xd5, 0xcd, 0xb8, 0x19, 0x22, 0x5f, 0x5c, 0x2a, 0x53, + 0x5c, 0xe3, 0xc5, 0x08, 0x38, 0x07, 0x2b, 0xcd, 0x55, 0x16, 0x23, 0x18, + 0x03, 0xcd, 0x55, 0x16, 0x23, 0xc1, 0xd1, 0xed, 0x53, 0x53, 0x5c, 0xed, + 0x5b, 0x5f, 0x5c, 0xc5, 0xd5, 0xeb, 0xed, 0xb0, 0xe1, 0xc1, 0xd5, 0xcd, + 0xe8, 0x19, 0xd1, 0xc9, 0xe5, 0x3e, 0xfd, 0xcd, 0x01, 0x16, 0xaf, 0x11, + 0xa1, 0x09, 0xcd, 0x0a, 0x0c, 0xfd, 0xcb, 0x02, 0xee, 0xcd, 0xd4, 0x15, + 0xdd, 0xe5, 0x11, 0x11, 0x00, 0xaf, 0xcd, 0xc2, 0x04, 0xdd, 0xe1, 0x06, + 0x32, 0x76, 0x10, 0xfd, 0xdd, 0x5e, 0x0b, 0xdd, 0x56, 0x0c, 0x3e, 0xff, + 0xdd, 0xe1, 0xc3, 0xc2, 0x04, 0x80, 0x53, 0x74, 0x61, 0x72, 0x74, 0x20, + 0x74, 0x61, 0x70, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x70, + 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x6b, 0x65, 0x79, + 0xae, 0x0d, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x3a, 0xa0, 0x0d, + 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, + 0x3a, 0xa0, 0x0d, 0x43, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, + 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x3a, 0xa0, 0x0d, 0x42, 0x79, 0x74, + 0x65, 0x73, 0x3a, 0xa0, 0xcd, 0x03, 0x0b, 0xfe, 0x20, 0xd2, 0xd9, 0x0a, + 0xfe, 0x06, 0x38, 0x69, 0xfe, 0x18, 0x30, 0x65, 0x21, 0x0b, 0x0a, 0x5f, + 0x16, 0x00, 0x19, 0x5e, 0x19, 0xe5, 0xc3, 0x03, 0x0b, 0x4e, 0x57, 0x10, + 0x29, 0x54, 0x53, 0x52, 0x37, 0x50, 0x4f, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, + 0x5a, 0x54, 0x53, 0x0c, 0x3e, 0x22, 0xb9, 0x20, 0x11, 0xfd, 0xcb, 0x01, + 0x4e, 0x20, 0x09, 0x04, 0x0e, 0x02, 0x3e, 0x18, 0xb8, 0x20, 0x03, 0x05, + 0x0e, 0x21, 0xc3, 0xd9, 0x0d, 0x3a, 0x91, 0x5c, 0xf5, 0xfd, 0x36, 0x57, + 0x01, 0x3e, 0x20, 0xcd, 0x65, 0x0b, 0xf1, 0x32, 0x91, 0x5c, 0xc9, 0xfd, + 0xcb, 0x01, 0x4e, 0xc2, 0xcd, 0x0e, 0x0e, 0x21, 0xcd, 0x55, 0x0c, 0x05, + 0xc3, 0xd9, 0x0d, 0xcd, 0x03, 0x0b, 0x79, 0x3d, 0x3d, 0xe6, 0x10, 0x18, + 0x5a, 0x3e, 0x3f, 0x18, 0x6c, 0x11, 0x87, 0x0a, 0x32, 0x0f, 0x5c, 0x18, + 0x0b, 0x11, 0x6d, 0x0a, 0x18, 0x03, 0x11, 0x87, 0x0a, 0x32, 0x0e, 0x5c, + 0x2a, 0x51, 0x5c, 0x73, 0x23, 0x72, 0xc9, 0x11, 0xf4, 0x09, 0xcd, 0x80, + 0x0a, 0x2a, 0x0e, 0x5c, 0x57, 0x7d, 0xfe, 0x16, 0xda, 0x11, 0x22, 0x20, + 0x29, 0x44, 0x4a, 0x3e, 0x1f, 0x91, 0x38, 0x0c, 0xc6, 0x02, 0x4f, 0xfd, + 0xcb, 0x01, 0x4e, 0x20, 0x16, 0x3e, 0x16, 0x90, 0xda, 0x9f, 0x1e, 0x3c, + 0x47, 0x04, 0xfd, 0xcb, 0x02, 0x46, 0xc2, 0x55, 0x0c, 0xfd, 0xbe, 0x31, + 0xda, 0x86, 0x0c, 0xc3, 0xd9, 0x0d, 0x7c, 0xcd, 0x03, 0x0b, 0x81, 0x3d, + 0xe6, 0x1f, 0xc8, 0x57, 0xfd, 0xcb, 0x01, 0xc6, 0x3e, 0x20, 0xcd, 0x3b, + 0x0c, 0x15, 0x20, 0xf8, 0xc9, 0xcd, 0x24, 0x0b, 0xfd, 0xcb, 0x01, 0x4e, + 0x20, 0x1a, 0xfd, 0xcb, 0x02, 0x46, 0x20, 0x08, 0xed, 0x43, 0x88, 0x5c, + 0x22, 0x84, 0x5c, 0xc9, 0xed, 0x43, 0x8a, 0x5c, 0xed, 0x43, 0x82, 0x5c, + 0x22, 0x86, 0x5c, 0xc9, 0xfd, 0x71, 0x45, 0x22, 0x80, 0x5c, 0xc9, 0xfd, + 0xcb, 0x01, 0x4e, 0x20, 0x14, 0xed, 0x4b, 0x88, 0x5c, 0x2a, 0x84, 0x5c, + 0xfd, 0xcb, 0x02, 0x46, 0xc8, 0xed, 0x4b, 0x8a, 0x5c, 0x2a, 0x86, 0x5c, + 0xc9, 0xfd, 0x4e, 0x45, 0x2a, 0x80, 0x5c, 0xc9, 0xfe, 0x80, 0x38, 0x3d, + 0xfe, 0x90, 0x30, 0x26, 0x47, 0xcd, 0x38, 0x0b, 0xcd, 0x03, 0x0b, 0x11, + 0x92, 0x5c, 0x18, 0x47, 0x21, 0x92, 0x5c, 0xcd, 0x3e, 0x0b, 0xcb, 0x18, + 0x9f, 0xe6, 0x0f, 0x4f, 0xcb, 0x18, 0x9f, 0xe6, 0xf0, 0xb1, 0x0e, 0x04, + 0x77, 0x23, 0x0d, 0x20, 0xfb, 0xc9, 0xd6, 0xa5, 0x30, 0x09, 0xc6, 0x15, + 0xc5, 0xed, 0x4b, 0x7b, 0x5c, 0x18, 0x0b, 0xcd, 0x10, 0x0c, 0xc3, 0x03, + 0x0b, 0xc5, 0xed, 0x4b, 0x36, 0x5c, 0xeb, 0x21, 0x3b, 0x5c, 0xcb, 0x86, + 0xfe, 0x20, 0x20, 0x02, 0xcb, 0xc6, 0x26, 0x00, 0x6f, 0x29, 0x29, 0x29, + 0x09, 0xc1, 0xeb, 0x79, 0x3d, 0x3e, 0x21, 0x20, 0x0e, 0x05, 0x4f, 0xfd, + 0xcb, 0x01, 0x4e, 0x28, 0x06, 0xd5, 0xcd, 0xcd, 0x0e, 0xd1, 0x79, 0xb9, + 0xd5, 0xcc, 0x55, 0x0c, 0xd1, 0xc5, 0xe5, 0x3a, 0x91, 0x5c, 0x06, 0xff, + 0x1f, 0x38, 0x01, 0x04, 0x1f, 0x1f, 0x9f, 0x4f, 0x3e, 0x08, 0xa7, 0xfd, + 0xcb, 0x01, 0x4e, 0x28, 0x05, 0xfd, 0xcb, 0x30, 0xce, 0x37, 0xeb, 0x08, + 0x1a, 0xa0, 0xae, 0xa9, 0x12, 0x08, 0x38, 0x13, 0x14, 0x23, 0x3d, 0x20, + 0xf2, 0xeb, 0x25, 0xfd, 0xcb, 0x01, 0x4e, 0xcc, 0xdb, 0x0b, 0xe1, 0xc1, + 0x0d, 0x23, 0xc9, 0x08, 0x3e, 0x20, 0x83, 0x5f, 0x08, 0x18, 0xe6, 0x7c, + 0x0f, 0x0f, 0x0f, 0xe6, 0x03, 0xf6, 0x58, 0x67, 0xed, 0x5b, 0x8f, 0x5c, + 0x7e, 0xab, 0xa2, 0xab, 0xfd, 0xcb, 0x57, 0x76, 0x28, 0x08, 0xe6, 0xc7, + 0xcb, 0x57, 0x20, 0x02, 0xee, 0x38, 0xfd, 0xcb, 0x57, 0x66, 0x28, 0x08, + 0xe6, 0xf8, 0xcb, 0x6f, 0x20, 0x02, 0xee, 0x07, 0x77, 0xc9, 0xe5, 0x26, + 0x00, 0xe3, 0x18, 0x04, 0x11, 0x95, 0x00, 0xf5, 0xcd, 0x41, 0x0c, 0x38, + 0x09, 0x3e, 0x20, 0xfd, 0xcb, 0x01, 0x46, 0xcc, 0x3b, 0x0c, 0x1a, 0xe6, + 0x7f, 0xcd, 0x3b, 0x0c, 0x1a, 0x13, 0x87, 0x30, 0xf5, 0xd1, 0xfe, 0x48, + 0x28, 0x03, 0xfe, 0x82, 0xd8, 0x7a, 0xfe, 0x03, 0xd8, 0x3e, 0x20, 0xd5, + 0xd9, 0xd7, 0xd9, 0xd1, 0xc9, 0xf5, 0xeb, 0x3c, 0xcb, 0x7e, 0x23, 0x28, + 0xfb, 0x3d, 0x20, 0xf8, 0xeb, 0xf1, 0xfe, 0x20, 0xd8, 0x1a, 0xd6, 0x41, + 0xc9, 0xfd, 0xcb, 0x01, 0x4e, 0xc0, 0x11, 0xd9, 0x0d, 0xd5, 0x78, 0xfd, + 0xcb, 0x02, 0x46, 0xc2, 0x02, 0x0d, 0xfd, 0xbe, 0x31, 0x38, 0x1b, 0xc0, + 0xfd, 0xcb, 0x02, 0x66, 0x28, 0x16, 0xfd, 0x5e, 0x2d, 0x1d, 0x28, 0x5a, + 0x3e, 0x00, 0xcd, 0x01, 0x16, 0xed, 0x7b, 0x3f, 0x5c, 0xfd, 0xcb, 0x02, + 0xa6, 0xc9, 0xcf, 0x04, 0xfd, 0x35, 0x52, 0x20, 0x45, 0x3e, 0x18, 0x90, + 0x32, 0x8c, 0x5c, 0x2a, 0x8f, 0x5c, 0xe5, 0x3a, 0x91, 0x5c, 0xf5, 0x3e, + 0xfd, 0xcd, 0x01, 0x16, 0xaf, 0x11, 0xf8, 0x0c, 0xcd, 0x0a, 0x0c, 0xfd, + 0xcb, 0x02, 0xee, 0x21, 0x3b, 0x5c, 0xcb, 0xde, 0xcb, 0xae, 0xd9, 0xcd, + 0xd4, 0x15, 0xd9, 0xfe, 0x20, 0x28, 0x45, 0xfe, 0xe2, 0x28, 0x41, 0xf6, + 0x20, 0xfe, 0x6e, 0x28, 0x3b, 0x3e, 0xfe, 0xcd, 0x01, 0x16, 0xf1, 0x32, + 0x91, 0x5c, 0xe1, 0x22, 0x8f, 0x5c, 0xcd, 0xfe, 0x0d, 0xfd, 0x46, 0x31, + 0x04, 0x0e, 0x21, 0xc5, 0xcd, 0x9b, 0x0e, 0x7c, 0x0f, 0x0f, 0x0f, 0xe6, + 0x03, 0xf6, 0x58, 0x67, 0x11, 0xe0, 0x5a, 0x1a, 0x4e, 0x06, 0x20, 0xeb, + 0x12, 0x71, 0x13, 0x23, 0x10, 0xfa, 0xc1, 0xc9, 0x80, 0x73, 0x63, 0x72, + 0x6f, 0x6c, 0x6c, 0xbf, 0xcf, 0x0c, 0xfe, 0x02, 0x38, 0x80, 0xfd, 0x86, + 0x31, 0xd6, 0x19, 0xd0, 0xed, 0x44, 0xc5, 0x47, 0x2a, 0x8f, 0x5c, 0xe5, + 0x2a, 0x91, 0x5c, 0xe5, 0xcd, 0x4d, 0x0d, 0x78, 0xf5, 0x21, 0x6b, 0x5c, + 0x46, 0x78, 0x3c, 0x77, 0x21, 0x89, 0x5c, 0xbe, 0x38, 0x03, 0x34, 0x06, + 0x18, 0xcd, 0x00, 0x0e, 0xf1, 0x3d, 0x20, 0xe8, 0xe1, 0xfd, 0x75, 0x57, + 0xe1, 0x22, 0x8f, 0x5c, 0xed, 0x4b, 0x88, 0x5c, 0xfd, 0xcb, 0x02, 0x86, + 0xcd, 0xd9, 0x0d, 0xfd, 0xcb, 0x02, 0xc6, 0xc1, 0xc9, 0xaf, 0x2a, 0x8d, + 0x5c, 0xfd, 0xcb, 0x02, 0x46, 0x28, 0x04, 0x67, 0xfd, 0x6e, 0x0e, 0x22, + 0x8f, 0x5c, 0x21, 0x91, 0x5c, 0x20, 0x02, 0x7e, 0x0f, 0xae, 0xe6, 0x55, + 0xae, 0x77, 0xc9, 0xcd, 0xaf, 0x0d, 0x21, 0x3c, 0x5c, 0xcb, 0xae, 0xcb, + 0xc6, 0xcd, 0x4d, 0x0d, 0xfd, 0x46, 0x31, 0xcd, 0x44, 0x0e, 0x21, 0xc0, + 0x5a, 0x3a, 0x8d, 0x5c, 0x05, 0x18, 0x07, 0x0e, 0x20, 0x2b, 0x77, 0x0d, + 0x20, 0xfb, 0x10, 0xf7, 0xfd, 0x36, 0x31, 0x02, 0x3e, 0xfd, 0xcd, 0x01, + 0x16, 0x2a, 0x51, 0x5c, 0x11, 0xf4, 0x09, 0xa7, 0x73, 0x23, 0x72, 0x23, + 0x11, 0xa8, 0x10, 0x3f, 0x38, 0xf6, 0x01, 0x21, 0x17, 0x18, 0x2a, 0x21, + 0x00, 0x00, 0x22, 0x7d, 0x5c, 0xfd, 0xcb, 0x30, 0x86, 0xcd, 0x94, 0x0d, + 0x3e, 0xfe, 0xcd, 0x01, 0x16, 0xcd, 0x4d, 0x0d, 0x06, 0x18, 0xcd, 0x44, + 0x0e, 0x2a, 0x51, 0x5c, 0x11, 0xf4, 0x09, 0x73, 0x23, 0x72, 0xfd, 0x36, + 0x52, 0x01, 0x01, 0x21, 0x18, 0x21, 0x00, 0x5b, 0xfd, 0xcb, 0x01, 0x4e, + 0x20, 0x12, 0x78, 0xfd, 0xcb, 0x02, 0x46, 0x28, 0x05, 0xfd, 0x86, 0x31, + 0xd6, 0x18, 0xc5, 0x47, 0xcd, 0x9b, 0x0e, 0xc1, 0x3e, 0x21, 0x91, 0x5f, + 0x16, 0x00, 0x19, 0xc3, 0xdc, 0x0a, 0x06, 0x17, 0xcd, 0x9b, 0x0e, 0x0e, + 0x08, 0xc5, 0xe5, 0x78, 0xe6, 0x07, 0x78, 0x20, 0x0c, 0xeb, 0x21, 0xe0, + 0xf8, 0x19, 0xeb, 0x01, 0x20, 0x00, 0x3d, 0xed, 0xb0, 0xeb, 0x21, 0xe0, + 0xff, 0x19, 0xeb, 0x47, 0xe6, 0x07, 0x0f, 0x0f, 0x0f, 0x4f, 0x78, 0x06, + 0x00, 0xed, 0xb0, 0x06, 0x07, 0x09, 0xe6, 0xf8, 0x20, 0xdb, 0xe1, 0x24, + 0xc1, 0x0d, 0x20, 0xcd, 0xcd, 0x88, 0x0e, 0x21, 0xe0, 0xff, 0x19, 0xeb, + 0xed, 0xb0, 0x06, 0x01, 0xc5, 0xcd, 0x9b, 0x0e, 0x0e, 0x08, 0xc5, 0xe5, + 0x78, 0xe6, 0x07, 0x0f, 0x0f, 0x0f, 0x4f, 0x78, 0x06, 0x00, 0x0d, 0x54, + 0x5d, 0x36, 0x00, 0x13, 0xed, 0xb0, 0x11, 0x01, 0x07, 0x19, 0x3d, 0xe6, + 0xf8, 0x47, 0x20, 0xe5, 0xe1, 0x24, 0xc1, 0x0d, 0x20, 0xdc, 0xcd, 0x88, + 0x0e, 0x62, 0x6b, 0x13, 0x3a, 0x8d, 0x5c, 0xfd, 0xcb, 0x02, 0x46, 0x28, + 0x03, 0x3a, 0x48, 0x5c, 0x77, 0x0b, 0xed, 0xb0, 0xc1, 0x0e, 0x21, 0xc9, + 0x7c, 0x0f, 0x0f, 0x0f, 0x3d, 0xf6, 0x50, 0x67, 0xeb, 0x61, 0x68, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x44, 0x4d, 0xc9, 0x3e, 0x18, 0x90, 0x57, 0x0f, + 0x0f, 0x0f, 0xe6, 0xe0, 0x6f, 0x7a, 0xe6, 0x18, 0xf6, 0x40, 0x67, 0xc9, + 0xf3, 0x06, 0xb0, 0x21, 0x00, 0x40, 0xe5, 0xc5, 0xcd, 0xf4, 0x0e, 0xc1, + 0xe1, 0x24, 0x7c, 0xe6, 0x07, 0x20, 0x0a, 0x7d, 0xc6, 0x20, 0x6f, 0x3f, + 0x9f, 0xe6, 0xf8, 0x84, 0x67, 0x10, 0xe7, 0x18, 0x0d, 0xf3, 0x21, 0x00, + 0x5b, 0x06, 0x08, 0xc5, 0xcd, 0xf4, 0x0e, 0xc1, 0x10, 0xf9, 0x3e, 0x04, + 0xd3, 0xfb, 0xfb, 0x21, 0x00, 0x5b, 0xfd, 0x75, 0x46, 0xaf, 0x47, 0x77, + 0x23, 0x10, 0xfc, 0xfd, 0xcb, 0x30, 0x8e, 0x0e, 0x21, 0xc3, 0xd9, 0x0d, + 0x78, 0xfe, 0x03, 0x9f, 0xe6, 0x02, 0xd3, 0xfb, 0x57, 0xcd, 0x54, 0x1f, + 0x38, 0x0a, 0x3e, 0x04, 0xd3, 0xfb, 0xfb, 0xcd, 0xdf, 0x0e, 0xcf, 0x0c, + 0xdb, 0xfb, 0x87, 0xf8, 0x30, 0xeb, 0x0e, 0x20, 0x5e, 0x23, 0x06, 0x08, + 0xcb, 0x12, 0xcb, 0x13, 0xcb, 0x1a, 0xdb, 0xfb, 0x1f, 0x30, 0xfb, 0x7a, + 0xd3, 0xfb, 0x10, 0xf0, 0x0d, 0x20, 0xe9, 0xc9, 0x2a, 0x3d, 0x5c, 0xe5, + 0x21, 0x7f, 0x10, 0xe5, 0xed, 0x73, 0x3d, 0x5c, 0xcd, 0xd4, 0x15, 0xf5, + 0x16, 0x00, 0xfd, 0x5e, 0xff, 0x21, 0xc8, 0x00, 0xcd, 0xb5, 0x03, 0xf1, + 0x21, 0x38, 0x0f, 0xe5, 0xfe, 0x18, 0x30, 0x31, 0xfe, 0x07, 0x38, 0x2d, + 0xfe, 0x10, 0x38, 0x3a, 0x01, 0x02, 0x00, 0x57, 0xfe, 0x16, 0x38, 0x0c, + 0x03, 0xfd, 0xcb, 0x37, 0x7e, 0xca, 0x1e, 0x10, 0xcd, 0xd4, 0x15, 0x5f, + 0xcd, 0xd4, 0x15, 0xd5, 0x2a, 0x5b, 0x5c, 0xfd, 0xcb, 0x07, 0x86, 0xcd, + 0x55, 0x16, 0xc1, 0x23, 0x70, 0x23, 0x71, 0x18, 0x0a, 0xfd, 0xcb, 0x07, + 0x86, 0x2a, 0x5b, 0x5c, 0xcd, 0x52, 0x16, 0x12, 0x13, 0xed, 0x53, 0x5b, + 0x5c, 0xc9, 0x5f, 0x16, 0x00, 0x21, 0x99, 0x0f, 0x19, 0x5e, 0x19, 0xe5, + 0x2a, 0x5b, 0x5c, 0xc9, 0x09, 0x66, 0x6a, 0x50, 0xb5, 0x70, 0x7e, 0xcf, + 0xd4, 0x2a, 0x49, 0x5c, 0xfd, 0xcb, 0x37, 0x6e, 0xc2, 0x97, 0x10, 0xcd, + 0x6e, 0x19, 0xcd, 0x95, 0x16, 0x7a, 0xb3, 0xca, 0x97, 0x10, 0xe5, 0x23, + 0x4e, 0x23, 0x46, 0x21, 0x0a, 0x00, 0x09, 0x44, 0x4d, 0xcd, 0x05, 0x1f, + 0xcd, 0x97, 0x10, 0x2a, 0x51, 0x5c, 0xe3, 0xe5, 0x3e, 0xff, 0xcd, 0x01, + 0x16, 0xe1, 0x2b, 0xfd, 0x35, 0x0f, 0xcd, 0x55, 0x18, 0xfd, 0x34, 0x0f, + 0x2a, 0x59, 0x5c, 0x23, 0x23, 0x23, 0x23, 0x22, 0x5b, 0x5c, 0xe1, 0xcd, + 0x15, 0x16, 0xc9, 0xfd, 0xcb, 0x37, 0x6e, 0x20, 0x08, 0x21, 0x49, 0x5c, + 0xcd, 0x0f, 0x19, 0x18, 0x6d, 0xfd, 0x36, 0x00, 0x10, 0x18, 0x1d, 0xcd, + 0x31, 0x10, 0x18, 0x05, 0x7e, 0xfe, 0x0d, 0xc8, 0x23, 0x22, 0x5b, 0x5c, + 0xc9, 0xcd, 0x31, 0x10, 0x01, 0x01, 0x00, 0xc3, 0xe8, 0x19, 0xcd, 0xd4, + 0x15, 0xcd, 0xd4, 0x15, 0xe1, 0xe1, 0xe1, 0x22, 0x3d, 0x5c, 0xfd, 0xcb, + 0x00, 0x7e, 0xc0, 0xf9, 0xc9, 0x37, 0xcd, 0x95, 0x11, 0xed, 0x52, 0x19, + 0x23, 0xc1, 0xd8, 0xc5, 0x44, 0x4d, 0x62, 0x6b, 0x23, 0x1a, 0xe6, 0xf0, + 0xfe, 0x10, 0x20, 0x09, 0x23, 0x1a, 0xd6, 0x17, 0xce, 0x00, 0x20, 0x01, + 0x23, 0xa7, 0xed, 0x42, 0x09, 0xeb, 0x38, 0xe6, 0xc9, 0xfd, 0xcb, 0x37, + 0x6e, 0xc0, 0x2a, 0x49, 0x5c, 0xcd, 0x6e, 0x19, 0xeb, 0xcd, 0x95, 0x16, + 0x21, 0x4a, 0x5c, 0xcd, 0x1c, 0x19, 0xcd, 0x95, 0x17, 0x3e, 0x00, 0xc3, + 0x01, 0x16, 0xfd, 0xcb, 0x37, 0x7e, 0x28, 0xa8, 0xc3, 0x81, 0x0f, 0xfd, + 0xcb, 0x30, 0x66, 0x28, 0xa1, 0xfd, 0x36, 0x00, 0xff, 0x16, 0x00, 0xfd, + 0x5e, 0xfe, 0x21, 0x90, 0x1a, 0xcd, 0xb5, 0x03, 0xc3, 0x30, 0x0f, 0xe5, + 0xcd, 0x90, 0x11, 0x2b, 0xcd, 0xe5, 0x19, 0x22, 0x5b, 0x5c, 0xfd, 0x36, + 0x07, 0x00, 0xe1, 0xc9, 0xfd, 0xcb, 0x02, 0x5e, 0xc4, 0x1d, 0x11, 0xa7, + 0xfd, 0xcb, 0x01, 0x6e, 0xc8, 0x3a, 0x08, 0x5c, 0xfd, 0xcb, 0x01, 0xae, + 0xf5, 0xfd, 0xcb, 0x02, 0x6e, 0xc4, 0x6e, 0x0d, 0xf1, 0xfe, 0x20, 0x30, + 0x52, 0xfe, 0x10, 0x30, 0x2d, 0xfe, 0x06, 0x30, 0x0a, 0x47, 0xe6, 0x01, + 0x4f, 0x78, 0x1f, 0xc6, 0x12, 0x18, 0x2a, 0x20, 0x09, 0x21, 0x6a, 0x5c, + 0x3e, 0x08, 0xae, 0x77, 0x18, 0x0e, 0xfe, 0x0e, 0xd8, 0xd6, 0x0d, 0x21, + 0x41, 0x5c, 0xbe, 0x77, 0x20, 0x02, 0x36, 0x00, 0xfd, 0xcb, 0x02, 0xde, + 0xbf, 0xc9, 0x47, 0xe6, 0x07, 0x4f, 0x3e, 0x10, 0xcb, 0x58, 0x20, 0x01, + 0x3c, 0xfd, 0x71, 0xd3, 0x11, 0x0d, 0x11, 0x18, 0x06, 0x3a, 0x0d, 0x5c, + 0x11, 0xa8, 0x10, 0x2a, 0x4f, 0x5c, 0x23, 0x23, 0x73, 0x23, 0x72, 0x37, + 0xc9, 0xcd, 0x4d, 0x0d, 0xfd, 0xcb, 0x02, 0x9e, 0xfd, 0xcb, 0x02, 0xae, + 0x2a, 0x8a, 0x5c, 0xe5, 0x2a, 0x3d, 0x5c, 0xe5, 0x21, 0x67, 0x11, 0xe5, + 0xed, 0x73, 0x3d, 0x5c, 0x2a, 0x82, 0x5c, 0xe5, 0x37, 0xcd, 0x95, 0x11, + 0xeb, 0xcd, 0x7d, 0x18, 0xeb, 0xcd, 0xe1, 0x18, 0x2a, 0x8a, 0x5c, 0xe3, + 0xeb, 0xcd, 0x4d, 0x0d, 0x3a, 0x8b, 0x5c, 0x92, 0x38, 0x26, 0x20, 0x06, + 0x7b, 0xfd, 0x96, 0x50, 0x30, 0x1e, 0x3e, 0x20, 0xd5, 0xcd, 0xf4, 0x09, + 0xd1, 0x18, 0xe9, 0x16, 0x00, 0xfd, 0x5e, 0xfe, 0x21, 0x90, 0x1a, 0xcd, + 0xb5, 0x03, 0xfd, 0x36, 0x00, 0xff, 0xed, 0x5b, 0x8a, 0x5c, 0x18, 0x02, + 0xd1, 0xe1, 0xe1, 0x22, 0x3d, 0x5c, 0xc1, 0xd5, 0xcd, 0xd9, 0x0d, 0xe1, + 0x22, 0x82, 0x5c, 0xfd, 0x36, 0x26, 0x00, 0xc9, 0x2a, 0x61, 0x5c, 0x2b, + 0xa7, 0xed, 0x5b, 0x59, 0x5c, 0xfd, 0xcb, 0x37, 0x6e, 0xc8, 0xed, 0x5b, + 0x61, 0x5c, 0xd8, 0x2a, 0x63, 0x5c, 0xc9, 0x7e, 0xfe, 0x0e, 0x01, 0x06, + 0x00, 0xcc, 0xe8, 0x19, 0x7e, 0x23, 0xfe, 0x0d, 0x20, 0xf1, 0xc9, 0xf3, + 0x3e, 0xff, 0xed, 0x5b, 0xb2, 0x5c, 0xd9, 0xed, 0x4b, 0xb4, 0x5c, 0xed, + 0x5b, 0x38, 0x5c, 0x2a, 0x7b, 0x5c, 0xd9, 0x47, 0x3e, 0x07, 0xd3, 0xfe, + 0x3e, 0x3f, 0xed, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6b, + 0x36, 0x02, 0x2b, 0xbc, 0x20, 0xfa, 0xa7, 0xed, 0x52, 0x19, 0x23, 0x30, + 0x06, 0x35, 0x28, 0x03, 0x35, 0x28, 0xf3, 0x2b, 0xd9, 0xed, 0x43, 0xb4, + 0x5c, 0xed, 0x53, 0x38, 0x5c, 0x22, 0x7b, 0x5c, 0xd9, 0x04, 0x28, 0x19, + 0x22, 0xb4, 0x5c, 0x11, 0xaf, 0x3e, 0x01, 0xa8, 0x00, 0xeb, 0xed, 0xb8, + 0xeb, 0x23, 0x22, 0x7b, 0x5c, 0x2b, 0x01, 0x40, 0x00, 0xed, 0x43, 0x38, + 0x5c, 0x22, 0xb2, 0x5c, 0x21, 0x00, 0x3c, 0x22, 0x36, 0x5c, 0x2a, 0xb2, + 0x5c, 0x36, 0x3e, 0x2b, 0xf9, 0x2b, 0x2b, 0x22, 0x3d, 0x5c, 0xed, 0x56, + 0xfd, 0x21, 0x3a, 0x5c, 0xfb, 0x21, 0xb6, 0x5c, 0x22, 0x4f, 0x5c, 0x11, + 0xaf, 0x15, 0x01, 0x15, 0x00, 0xeb, 0xed, 0xb0, 0xeb, 0x2b, 0x22, 0x57, + 0x5c, 0x23, 0x22, 0x53, 0x5c, 0x22, 0x4b, 0x5c, 0x36, 0x80, 0x23, 0x22, + 0x59, 0x5c, 0x36, 0x0d, 0x23, 0x36, 0x80, 0x23, 0x22, 0x61, 0x5c, 0x22, + 0x63, 0x5c, 0x22, 0x65, 0x5c, 0x3e, 0x38, 0x32, 0x8d, 0x5c, 0x32, 0x8f, + 0x5c, 0x32, 0x48, 0x5c, 0x21, 0x23, 0x05, 0x22, 0x09, 0x5c, 0xfd, 0x35, + 0xc6, 0xfd, 0x35, 0xca, 0x21, 0xc6, 0x15, 0x11, 0x10, 0x5c, 0x01, 0x0e, + 0x00, 0xed, 0xb0, 0xfd, 0xcb, 0x01, 0xce, 0xcd, 0xdf, 0x0e, 0xfd, 0x36, + 0x31, 0x02, 0xcd, 0x6b, 0x0d, 0xaf, 0x11, 0x38, 0x15, 0xcd, 0x0a, 0x0c, + 0xfd, 0xcb, 0x02, 0xee, 0x18, 0x07, 0xfd, 0x36, 0x31, 0x02, 0xcd, 0x95, + 0x17, 0xcd, 0xb0, 0x16, 0x3e, 0x00, 0xcd, 0x01, 0x16, 0xcd, 0x2c, 0x0f, + 0xcd, 0x17, 0x1b, 0xfd, 0xcb, 0x00, 0x7e, 0x20, 0x12, 0xfd, 0xcb, 0x30, + 0x66, 0x28, 0x40, 0x2a, 0x59, 0x5c, 0xcd, 0xa7, 0x11, 0xfd, 0x36, 0x00, + 0xff, 0x18, 0xdd, 0x2a, 0x59, 0x5c, 0x22, 0x5d, 0x5c, 0xcd, 0xfb, 0x19, + 0x78, 0xb1, 0xc2, 0x5d, 0x15, 0xdf, 0xfe, 0x0d, 0x28, 0xc0, 0xfd, 0xcb, + 0x30, 0x46, 0xc4, 0xaf, 0x0d, 0xcd, 0x6e, 0x0d, 0x3e, 0x19, 0xfd, 0x96, + 0x4f, 0x32, 0x8c, 0x5c, 0xfd, 0xcb, 0x01, 0xfe, 0xfd, 0x36, 0x00, 0xff, + 0xfd, 0x36, 0x0a, 0x01, 0xcd, 0x8a, 0x1b, 0x76, 0xfd, 0xcb, 0x01, 0xae, + 0xfd, 0xcb, 0x30, 0x4e, 0xc4, 0xcd, 0x0e, 0x3a, 0x3a, 0x5c, 0x3c, 0xf5, + 0x21, 0x00, 0x00, 0xfd, 0x74, 0x37, 0xfd, 0x74, 0x26, 0x22, 0x0b, 0x5c, + 0x21, 0x01, 0x00, 0x22, 0x16, 0x5c, 0xcd, 0xb0, 0x16, 0xfd, 0xcb, 0x37, + 0xae, 0xcd, 0x6e, 0x0d, 0xfd, 0xcb, 0x02, 0xee, 0xf1, 0x47, 0xfe, 0x0a, + 0x38, 0x02, 0xc6, 0x07, 0xcd, 0xef, 0x15, 0x3e, 0x20, 0xd7, 0x78, 0x11, + 0x91, 0x13, 0xcd, 0x0a, 0x0c, 0xaf, 0x11, 0x36, 0x15, 0xcd, 0x0a, 0x0c, + 0xed, 0x4b, 0x45, 0x5c, 0xcd, 0x1b, 0x1a, 0x3e, 0x3a, 0xd7, 0xfd, 0x4e, + 0x0d, 0x06, 0x00, 0xcd, 0x1b, 0x1a, 0xcd, 0x97, 0x10, 0x3a, 0x3a, 0x5c, + 0x3c, 0x28, 0x1b, 0xfe, 0x09, 0x28, 0x04, 0xfe, 0x15, 0x20, 0x03, 0xfd, + 0x34, 0x0d, 0x01, 0x03, 0x00, 0x11, 0x70, 0x5c, 0x21, 0x44, 0x5c, 0xcb, + 0x7e, 0x28, 0x01, 0x09, 0xed, 0xb8, 0xfd, 0x36, 0x0a, 0xff, 0xfd, 0xcb, + 0x01, 0x9e, 0xc3, 0xac, 0x12, 0x80, 0x4f, 0xcb, 0x4e, 0x45, 0x58, 0x54, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x46, 0x4f, 0xd2, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x66, 0x6f, 0x75, 0x6e, 0xe4, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x20, 0x77, 0x72, 0x6f, 0x6e, 0xe7, 0x4f, 0x75, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0xf9, 0x4f, 0x75, + 0x74, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0xee, 0x4e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x6f, 0x20, 0x62, 0x69, + 0xe7, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x6f, 0x75, 0x74, 0x20, 0x47, 0x4f, 0x53, 0x55, 0xc2, 0x45, 0x6e, 0x64, + 0x20, 0x6f, 0x66, 0x20, 0x66, 0x69, 0x6c, 0xe5, 0x53, 0x54, 0x4f, 0x50, + 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0xf4, 0x49, 0x6e, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x61, 0x72, 0x67, 0x75, 0x6d, 0x65, + 0x6e, 0xf4, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x20, 0x6f, 0x75, + 0x74, 0x20, 0x6f, 0x66, 0x20, 0x72, 0x61, 0x6e, 0x67, 0xe5, 0x4e, 0x6f, + 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x42, 0x41, + 0x53, 0x49, 0xc3, 0x42, 0x52, 0x45, 0x41, 0x4b, 0x20, 0x2d, 0x20, 0x43, + 0x4f, 0x4e, 0x54, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0xf3, 0x4f, + 0x75, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x44, 0x41, 0x54, 0xc1, 0x49, 0x6e, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, + 0x61, 0x6d, 0xe5, 0x4e, 0x6f, 0x20, 0x72, 0x6f, 0x6f, 0x6d, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x6c, 0x69, 0x6e, 0xe5, 0x53, 0x54, 0x4f, 0x50, 0x20, + 0x69, 0x6e, 0x20, 0x49, 0x4e, 0x50, 0x55, 0xd4, 0x46, 0x4f, 0x52, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x4e, 0x45, 0x58, 0xd4, + 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x49, 0x2f, 0x4f, 0x20, + 0x64, 0x65, 0x76, 0x69, 0x63, 0xe5, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x75, 0xf2, 0x42, 0x52, 0x45, 0x41, + 0x4b, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0xed, 0x52, 0x41, 0x4d, 0x54, 0x4f, 0x50, 0x20, 0x6e, 0x6f, 0x20, + 0x67, 0x6f, 0x6f, 0xe4, 0x53, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x20, 0x6c, 0x6f, 0x73, 0xf4, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0xed, 0x46, 0x4e, 0x20, 0x77, + 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x44, 0x45, 0xc6, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x20, 0x65, 0x72, 0x72, 0x6f, + 0xf2, 0x54, 0x61, 0x70, 0x65, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, + 0x67, 0x20, 0x65, 0x72, 0x72, 0x6f, 0xf2, 0x2c, 0xa0, 0x7f, 0x20, 0x31, + 0x39, 0x38, 0x32, 0x20, 0x53, 0x69, 0x6e, 0x63, 0x6c, 0x61, 0x69, 0x72, + 0x20, 0x52, 0x65, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x20, 0x4c, 0x74, + 0xe4, 0x3e, 0x10, 0x01, 0x00, 0x00, 0xc3, 0x13, 0x13, 0xed, 0x43, 0x49, + 0x5c, 0x2a, 0x5d, 0x5c, 0xeb, 0x21, 0x55, 0x15, 0xe5, 0x2a, 0x61, 0x5c, + 0x37, 0xed, 0x52, 0xe5, 0x60, 0x69, 0xcd, 0x6e, 0x19, 0x20, 0x06, 0xcd, + 0xb8, 0x19, 0xcd, 0xe8, 0x19, 0xc1, 0x79, 0x3d, 0xb0, 0x28, 0x28, 0xc5, + 0x03, 0x03, 0x03, 0x03, 0x2b, 0xed, 0x5b, 0x53, 0x5c, 0xd5, 0xcd, 0x55, + 0x16, 0xe1, 0x22, 0x53, 0x5c, 0xc1, 0xc5, 0x13, 0x2a, 0x61, 0x5c, 0x2b, + 0x2b, 0xed, 0xb8, 0x2a, 0x49, 0x5c, 0xeb, 0xc1, 0x70, 0x2b, 0x71, 0x2b, + 0x73, 0x2b, 0x72, 0xf1, 0xc3, 0xa2, 0x12, 0xf4, 0x09, 0xa8, 0x10, 0x4b, + 0xf4, 0x09, 0xc4, 0x15, 0x53, 0x81, 0x0f, 0xc4, 0x15, 0x52, 0xf4, 0x09, + 0xc4, 0x15, 0x50, 0x80, 0xcf, 0x12, 0x01, 0x00, 0x06, 0x00, 0x0b, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x06, 0x00, 0x10, 0x00, 0xfd, 0xcb, 0x02, 0x6e, + 0x20, 0x04, 0xfd, 0xcb, 0x02, 0xde, 0xcd, 0xe6, 0x15, 0xd8, 0x28, 0xfa, + 0xcf, 0x07, 0xd9, 0xe5, 0x2a, 0x51, 0x5c, 0x23, 0x23, 0x18, 0x08, 0x1e, + 0x30, 0x83, 0xd9, 0xe5, 0x2a, 0x51, 0x5c, 0x5e, 0x23, 0x56, 0xeb, 0xcd, + 0x2c, 0x16, 0xe1, 0xd9, 0xc9, 0x87, 0xc6, 0x16, 0x6f, 0x26, 0x5c, 0x5e, + 0x23, 0x56, 0x7a, 0xb3, 0x20, 0x02, 0xcf, 0x17, 0x1b, 0x2a, 0x4f, 0x5c, + 0x19, 0x22, 0x51, 0x5c, 0xfd, 0xcb, 0x30, 0xa6, 0x23, 0x23, 0x23, 0x23, + 0x4e, 0x21, 0x2d, 0x16, 0xcd, 0xdc, 0x16, 0xd0, 0x16, 0x00, 0x5e, 0x19, + 0xe9, 0x4b, 0x06, 0x53, 0x12, 0x50, 0x1b, 0x00, 0xfd, 0xcb, 0x02, 0xc6, + 0xfd, 0xcb, 0x01, 0xae, 0xfd, 0xcb, 0x30, 0xe6, 0x18, 0x04, 0xfd, 0xcb, + 0x02, 0x86, 0xfd, 0xcb, 0x01, 0x8e, 0xc3, 0x4d, 0x0d, 0xfd, 0xcb, 0x01, + 0xce, 0xc9, 0x01, 0x01, 0x00, 0xe5, 0xcd, 0x05, 0x1f, 0xe1, 0xcd, 0x64, + 0x16, 0x2a, 0x65, 0x5c, 0xeb, 0xed, 0xb8, 0xc9, 0xf5, 0xe5, 0x21, 0x4b, + 0x5c, 0x3e, 0x0e, 0x5e, 0x23, 0x56, 0xe3, 0xa7, 0xed, 0x52, 0x19, 0xe3, + 0x30, 0x09, 0xd5, 0xeb, 0x09, 0xeb, 0x72, 0x2b, 0x73, 0x23, 0xd1, 0x23, + 0x3d, 0x20, 0xe8, 0xeb, 0xd1, 0xf1, 0xa7, 0xed, 0x52, 0x44, 0x4d, 0x03, + 0x19, 0xeb, 0xc9, 0x00, 0x00, 0xeb, 0x11, 0x8f, 0x16, 0x7e, 0xe6, 0xc0, + 0x20, 0xf7, 0x56, 0x23, 0x5e, 0xc9, 0x2a, 0x63, 0x5c, 0x2b, 0xcd, 0x55, + 0x16, 0x23, 0x23, 0xc1, 0xed, 0x43, 0x61, 0x5c, 0xc1, 0xeb, 0x23, 0xc9, + 0x2a, 0x59, 0x5c, 0x36, 0x0d, 0x22, 0x5b, 0x5c, 0x23, 0x36, 0x80, 0x23, + 0x22, 0x61, 0x5c, 0x2a, 0x61, 0x5c, 0x22, 0x63, 0x5c, 0x2a, 0x63, 0x5c, + 0x22, 0x65, 0x5c, 0xe5, 0x21, 0x92, 0x5c, 0x22, 0x68, 0x5c, 0xe1, 0xc9, + 0xed, 0x5b, 0x59, 0x5c, 0xc3, 0xe5, 0x19, 0x23, 0x7e, 0xa7, 0xc8, 0xb9, + 0x23, 0x20, 0xf8, 0x37, 0xc9, 0xcd, 0x1e, 0x17, 0xcd, 0x01, 0x17, 0x01, + 0x00, 0x00, 0x11, 0xe2, 0xa3, 0xeb, 0x19, 0x38, 0x07, 0x01, 0xd4, 0x15, + 0x09, 0x4e, 0x23, 0x46, 0xeb, 0x71, 0x23, 0x70, 0xc9, 0xe5, 0x2a, 0x4f, + 0x5c, 0x09, 0x23, 0x23, 0x23, 0x4e, 0xeb, 0x21, 0x16, 0x17, 0xcd, 0xdc, + 0x16, 0x4e, 0x06, 0x00, 0x09, 0xe9, 0x4b, 0x05, 0x53, 0x03, 0x50, 0x01, + 0xe1, 0xc9, 0xcd, 0x94, 0x1e, 0xfe, 0x10, 0x38, 0x02, 0xcf, 0x17, 0xc6, + 0x03, 0x07, 0x21, 0x10, 0x5c, 0x4f, 0x06, 0x00, 0x09, 0x4e, 0x23, 0x46, + 0x2b, 0xc9, 0xef, 0x01, 0x38, 0xcd, 0x1e, 0x17, 0x78, 0xb1, 0x28, 0x16, + 0xeb, 0x2a, 0x4f, 0x5c, 0x09, 0x23, 0x23, 0x23, 0x7e, 0xeb, 0xfe, 0x4b, + 0x28, 0x08, 0xfe, 0x53, 0x28, 0x04, 0xfe, 0x50, 0x20, 0xcf, 0xcd, 0x5d, + 0x17, 0x73, 0x23, 0x72, 0xc9, 0xe5, 0xcd, 0xf1, 0x2b, 0x78, 0xb1, 0x20, + 0x02, 0xcf, 0x0e, 0xc5, 0x1a, 0xe6, 0xdf, 0x4f, 0x21, 0x7a, 0x17, 0xcd, + 0xdc, 0x16, 0x30, 0xf1, 0x4e, 0x06, 0x00, 0x09, 0xc1, 0xe9, 0x4b, 0x06, + 0x53, 0x08, 0x50, 0x0a, 0x00, 0x1e, 0x01, 0x18, 0x06, 0x1e, 0x06, 0x18, + 0x02, 0x1e, 0x10, 0x0b, 0x78, 0xb1, 0x20, 0xd5, 0x57, 0xe1, 0xc9, 0x18, + 0x90, 0xed, 0x73, 0x3f, 0x5c, 0xfd, 0x36, 0x02, 0x10, 0xcd, 0xaf, 0x0d, + 0xfd, 0xcb, 0x02, 0xc6, 0xfd, 0x46, 0x31, 0xcd, 0x44, 0x0e, 0xfd, 0xcb, + 0x02, 0x86, 0xfd, 0xcb, 0x30, 0xc6, 0x2a, 0x49, 0x5c, 0xed, 0x5b, 0x6c, + 0x5c, 0xa7, 0xed, 0x52, 0x19, 0x38, 0x22, 0xd5, 0xcd, 0x6e, 0x19, 0x11, + 0xc0, 0x02, 0xeb, 0xed, 0x52, 0xe3, 0xcd, 0x6e, 0x19, 0xc1, 0xc5, 0xcd, + 0xb8, 0x19, 0xc1, 0x09, 0x38, 0x0e, 0xeb, 0x56, 0x23, 0x5e, 0x2b, 0xed, + 0x53, 0x6c, 0x5c, 0x18, 0xed, 0x22, 0x6c, 0x5c, 0x2a, 0x6c, 0x5c, 0xcd, + 0x6e, 0x19, 0x28, 0x01, 0xeb, 0xcd, 0x33, 0x18, 0xfd, 0xcb, 0x02, 0xa6, + 0xc9, 0x3e, 0x03, 0x18, 0x02, 0x3e, 0x02, 0xfd, 0x36, 0x02, 0x00, 0xcd, + 0x30, 0x25, 0xc4, 0x01, 0x16, 0xdf, 0xcd, 0x70, 0x20, 0x38, 0x14, 0xdf, + 0xfe, 0x3b, 0x28, 0x04, 0xfe, 0x2c, 0x20, 0x06, 0xe7, 0xcd, 0x82, 0x1c, + 0x18, 0x08, 0xcd, 0xe6, 0x1c, 0x18, 0x03, 0xcd, 0xde, 0x1c, 0xcd, 0xee, + 0x1b, 0xcd, 0x99, 0x1e, 0x78, 0xe6, 0x3f, 0x67, 0x69, 0x22, 0x49, 0x5c, + 0xcd, 0x6e, 0x19, 0x1e, 0x01, 0xcd, 0x55, 0x18, 0xd7, 0xfd, 0xcb, 0x02, + 0x66, 0x28, 0xf6, 0x3a, 0x6b, 0x5c, 0xfd, 0x96, 0x4f, 0x20, 0xee, 0xab, + 0xc8, 0xe5, 0xd5, 0x21, 0x6c, 0x5c, 0xcd, 0x0f, 0x19, 0xd1, 0xe1, 0x18, + 0xe0, 0xed, 0x4b, 0x49, 0x5c, 0xcd, 0x80, 0x19, 0x16, 0x3e, 0x28, 0x05, + 0x11, 0x00, 0x00, 0xcb, 0x13, 0xfd, 0x73, 0x2d, 0x7e, 0xfe, 0x40, 0xc1, + 0xd0, 0xc5, 0xcd, 0x28, 0x1a, 0x23, 0x23, 0x23, 0xfd, 0xcb, 0x01, 0x86, + 0x7a, 0xa7, 0x28, 0x05, 0xd7, 0xfd, 0xcb, 0x01, 0xc6, 0xd5, 0xeb, 0xfd, + 0xcb, 0x30, 0x96, 0x21, 0x3b, 0x5c, 0xcb, 0x96, 0xfd, 0xcb, 0x37, 0x6e, + 0x28, 0x02, 0xcb, 0xd6, 0x2a, 0x5f, 0x5c, 0xa7, 0xed, 0x52, 0x20, 0x05, + 0x3e, 0x3f, 0xcd, 0xc1, 0x18, 0xcd, 0xe1, 0x18, 0xeb, 0x7e, 0xcd, 0xb6, + 0x18, 0x23, 0xfe, 0x0d, 0x28, 0x06, 0xeb, 0xcd, 0x37, 0x19, 0x18, 0xe0, + 0xd1, 0xc9, 0xfe, 0x0e, 0xc0, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x7e, + 0xc9, 0xd9, 0x2a, 0x8f, 0x5c, 0xe5, 0xcb, 0xbc, 0xcb, 0xfd, 0x22, 0x8f, + 0x5c, 0x21, 0x91, 0x5c, 0x56, 0xd5, 0x36, 0x00, 0xcd, 0xf4, 0x09, 0xe1, + 0xfd, 0x74, 0x57, 0xe1, 0x22, 0x8f, 0x5c, 0xd9, 0xc9, 0x2a, 0x5b, 0x5c, + 0xa7, 0xed, 0x52, 0xc0, 0x3a, 0x41, 0x5c, 0xcb, 0x07, 0x28, 0x04, 0xc6, + 0x43, 0x18, 0x16, 0x21, 0x3b, 0x5c, 0xcb, 0x9e, 0x3e, 0x4b, 0xcb, 0x56, + 0x28, 0x0b, 0xcb, 0xde, 0x3c, 0xfd, 0xcb, 0x30, 0x5e, 0x28, 0x02, 0x3e, + 0x43, 0xd5, 0xcd, 0xc1, 0x18, 0xd1, 0xc9, 0x5e, 0x23, 0x56, 0xe5, 0xeb, + 0x23, 0xcd, 0x6e, 0x19, 0xcd, 0x95, 0x16, 0xe1, 0xfd, 0xcb, 0x37, 0x6e, + 0xc0, 0x72, 0x2b, 0x73, 0xc9, 0x7b, 0xa7, 0xf8, 0x18, 0x0d, 0xaf, 0x09, + 0x3c, 0x38, 0xfc, 0xed, 0x42, 0x3d, 0x28, 0xf1, 0xc3, 0xef, 0x15, 0xcd, + 0x1b, 0x2d, 0x30, 0x30, 0xfe, 0x21, 0x38, 0x2c, 0xfd, 0xcb, 0x01, 0x96, + 0xfe, 0xcb, 0x28, 0x24, 0xfe, 0x3a, 0x20, 0x0e, 0xfd, 0xcb, 0x37, 0x6e, + 0x20, 0x16, 0xfd, 0xcb, 0x30, 0x56, 0x28, 0x14, 0x18, 0x0e, 0xfe, 0x22, + 0x20, 0x0a, 0xf5, 0x3a, 0x6a, 0x5c, 0xee, 0x04, 0x32, 0x6a, 0x5c, 0xf1, + 0xfd, 0xcb, 0x01, 0xd6, 0xd7, 0xc9, 0xe5, 0x2a, 0x53, 0x5c, 0x54, 0x5d, + 0xc1, 0xcd, 0x80, 0x19, 0xd0, 0xc5, 0xcd, 0xb8, 0x19, 0xeb, 0x18, 0xf4, + 0x7e, 0xb8, 0xc0, 0x23, 0x7e, 0x2b, 0xb9, 0xc9, 0x23, 0x23, 0x23, 0x22, + 0x5d, 0x5c, 0x0e, 0x00, 0x15, 0xc8, 0xe7, 0xbb, 0x20, 0x04, 0xa7, 0xc9, + 0x23, 0x7e, 0xcd, 0xb6, 0x18, 0x22, 0x5d, 0x5c, 0xfe, 0x22, 0x20, 0x01, + 0x0d, 0xfe, 0x3a, 0x28, 0x04, 0xfe, 0xcb, 0x20, 0x04, 0xcb, 0x41, 0x28, + 0xdf, 0xfe, 0x0d, 0x20, 0xe3, 0x15, 0x37, 0xc9, 0xe5, 0x7e, 0xfe, 0x40, + 0x38, 0x17, 0xcb, 0x6f, 0x28, 0x14, 0x87, 0xfa, 0xc7, 0x19, 0x3f, 0x01, + 0x05, 0x00, 0x30, 0x02, 0x0e, 0x12, 0x17, 0x23, 0x7e, 0x30, 0xfb, 0x18, + 0x06, 0x23, 0x23, 0x4e, 0x23, 0x46, 0x23, 0x09, 0xd1, 0xa7, 0xed, 0x52, + 0x44, 0x4d, 0x19, 0xeb, 0xc9, 0xcd, 0xdd, 0x19, 0xc5, 0x78, 0x2f, 0x47, + 0x79, 0x2f, 0x4f, 0x03, 0xcd, 0x64, 0x16, 0xeb, 0xe1, 0x19, 0xd5, 0xed, + 0xb0, 0xe1, 0xc9, 0x2a, 0x59, 0x5c, 0x2b, 0x22, 0x5d, 0x5c, 0xe7, 0x21, + 0x92, 0x5c, 0x22, 0x65, 0x5c, 0xcd, 0x3b, 0x2d, 0xcd, 0xa2, 0x2d, 0x38, + 0x04, 0x21, 0xf0, 0xd8, 0x09, 0xda, 0x8a, 0x1c, 0xc3, 0xc5, 0x16, 0xd5, + 0xe5, 0xaf, 0xcb, 0x78, 0x20, 0x20, 0x60, 0x69, 0x1e, 0xff, 0x18, 0x08, + 0xd5, 0x56, 0x23, 0x5e, 0xe5, 0xeb, 0x1e, 0x20, 0x01, 0x18, 0xfc, 0xcd, + 0x2a, 0x19, 0x01, 0x9c, 0xff, 0xcd, 0x2a, 0x19, 0x0e, 0xf6, 0xcd, 0x2a, + 0x19, 0x7d, 0xcd, 0xef, 0x15, 0xe1, 0xd1, 0xc9, 0xb1, 0xcb, 0xbc, 0xbf, + 0xc4, 0xaf, 0xb4, 0x93, 0x91, 0x92, 0x95, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x7f, 0x81, 0x2e, 0x6c, 0x6e, 0x70, 0x48, 0x94, 0x56, 0x3f, + 0x41, 0x2b, 0x17, 0x1f, 0x37, 0x77, 0x44, 0x0f, 0x59, 0x2b, 0x43, 0x2d, + 0x51, 0x3a, 0x6d, 0x42, 0x0d, 0x49, 0x5c, 0x44, 0x15, 0x5d, 0x01, 0x3d, + 0x02, 0x06, 0x00, 0x67, 0x1e, 0x06, 0xcb, 0x05, 0xf0, 0x1c, 0x06, 0x00, + 0xed, 0x1e, 0x00, 0xee, 0x1c, 0x00, 0x23, 0x1f, 0x04, 0x3d, 0x06, 0xcc, + 0x06, 0x05, 0x03, 0x1d, 0x04, 0x00, 0xab, 0x1d, 0x05, 0xcd, 0x1f, 0x05, + 0x89, 0x20, 0x05, 0x02, 0x2c, 0x05, 0xb2, 0x1b, 0x00, 0xb7, 0x11, 0x03, + 0xa1, 0x1e, 0x05, 0xf9, 0x17, 0x08, 0x00, 0x80, 0x1e, 0x03, 0x4f, 0x1e, + 0x00, 0x5f, 0x1e, 0x03, 0xac, 0x1e, 0x00, 0x6b, 0x0d, 0x09, 0x00, 0xdc, + 0x22, 0x06, 0x00, 0x3a, 0x1f, 0x05, 0xed, 0x1d, 0x05, 0x27, 0x1e, 0x03, + 0x42, 0x1e, 0x09, 0x05, 0x82, 0x23, 0x00, 0xac, 0x0e, 0x05, 0xc9, 0x1f, + 0x05, 0xf5, 0x17, 0x0b, 0x0b, 0x0b, 0x0b, 0x08, 0x00, 0xf8, 0x03, 0x09, + 0x05, 0x20, 0x23, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x00, 0x7a, + 0x1e, 0x06, 0x00, 0x94, 0x22, 0x05, 0x60, 0x1f, 0x06, 0x2c, 0x0a, 0x00, + 0x36, 0x17, 0x06, 0x00, 0xe5, 0x16, 0x0a, 0x00, 0x93, 0x17, 0x0a, 0x2c, + 0x0a, 0x00, 0x93, 0x17, 0x0a, 0x00, 0x93, 0x17, 0x00, 0x93, 0x17, 0xfd, + 0xcb, 0x01, 0xbe, 0xcd, 0xfb, 0x19, 0xaf, 0x32, 0x47, 0x5c, 0x3d, 0x32, + 0x3a, 0x5c, 0x18, 0x01, 0xe7, 0xcd, 0xbf, 0x16, 0xfd, 0x34, 0x0d, 0xfa, + 0x8a, 0x1c, 0xdf, 0x06, 0x00, 0xfe, 0x0d, 0x28, 0x7a, 0xfe, 0x3a, 0x28, + 0xeb, 0x21, 0x76, 0x1b, 0xe5, 0x4f, 0xe7, 0x79, 0xd6, 0xce, 0xda, 0x8a, + 0x1c, 0x4f, 0x21, 0x48, 0x1a, 0x09, 0x4e, 0x09, 0x18, 0x03, 0x2a, 0x74, + 0x5c, 0x7e, 0x23, 0x22, 0x74, 0x5c, 0x01, 0x52, 0x1b, 0xc5, 0x4f, 0xfe, + 0x20, 0x30, 0x0c, 0x21, 0x01, 0x1c, 0x06, 0x00, 0x09, 0x4e, 0x09, 0xe5, + 0xdf, 0x05, 0xc9, 0xdf, 0xb9, 0xc2, 0x8a, 0x1c, 0xe7, 0xc9, 0xcd, 0x54, + 0x1f, 0x38, 0x02, 0xcf, 0x14, 0xfd, 0xcb, 0x0a, 0x7e, 0x20, 0x71, 0x2a, + 0x42, 0x5c, 0xcb, 0x7c, 0x28, 0x14, 0x21, 0xfe, 0xff, 0x22, 0x45, 0x5c, + 0x2a, 0x61, 0x5c, 0x2b, 0xed, 0x5b, 0x59, 0x5c, 0x1b, 0x3a, 0x44, 0x5c, + 0x18, 0x33, 0xcd, 0x6e, 0x19, 0x3a, 0x44, 0x5c, 0x28, 0x19, 0xa7, 0x20, + 0x43, 0x47, 0x7e, 0xe6, 0xc0, 0x78, 0x28, 0x0f, 0xcf, 0xff, 0xc1, 0xcd, + 0x30, 0x25, 0xc8, 0x2a, 0x55, 0x5c, 0x3e, 0xc0, 0xa6, 0xc0, 0xaf, 0xfe, + 0x01, 0xce, 0x00, 0x56, 0x23, 0x5e, 0xed, 0x53, 0x45, 0x5c, 0x23, 0x5e, + 0x23, 0x56, 0xeb, 0x19, 0x23, 0x22, 0x55, 0x5c, 0xeb, 0x22, 0x5d, 0x5c, + 0x57, 0x1e, 0x00, 0xfd, 0x36, 0x0a, 0xff, 0x15, 0xfd, 0x72, 0x0d, 0xca, + 0x28, 0x1b, 0x14, 0xcd, 0x8b, 0x19, 0x28, 0x08, 0xcf, 0x16, 0xcd, 0x30, + 0x25, 0xc0, 0xc1, 0xc1, 0xdf, 0xfe, 0x0d, 0x28, 0xba, 0xfe, 0x3a, 0xca, + 0x28, 0x1b, 0xc3, 0x8a, 0x1c, 0x0f, 0x1d, 0x4b, 0x09, 0x67, 0x0b, 0x7b, + 0x8e, 0x71, 0xb4, 0x81, 0xcf, 0xcd, 0xde, 0x1c, 0xbf, 0xc1, 0xcc, 0xee, + 0x1b, 0xeb, 0x2a, 0x74, 0x5c, 0x4e, 0x23, 0x46, 0xeb, 0xc5, 0xc9, 0xcd, + 0xb2, 0x28, 0xfd, 0x36, 0x37, 0x00, 0x30, 0x08, 0xfd, 0xcb, 0x37, 0xce, + 0x20, 0x18, 0xcf, 0x01, 0xcc, 0x96, 0x29, 0xfd, 0xcb, 0x01, 0x76, 0x20, + 0x0d, 0xaf, 0xcd, 0x30, 0x25, 0xc4, 0xf1, 0x2b, 0x21, 0x71, 0x5c, 0xb6, + 0x77, 0xeb, 0xed, 0x43, 0x72, 0x5c, 0x22, 0x4d, 0x5c, 0xc9, 0xc1, 0xcd, + 0x56, 0x1c, 0xcd, 0xee, 0x1b, 0xc9, 0x3a, 0x3b, 0x5c, 0xf5, 0xcd, 0xfb, + 0x24, 0xf1, 0xfd, 0x56, 0x01, 0xaa, 0xe6, 0x40, 0x20, 0x24, 0xcb, 0x7a, + 0xc2, 0xff, 0x2a, 0xc9, 0xcd, 0xb2, 0x28, 0xf5, 0x79, 0xf6, 0x9f, 0x3c, + 0x20, 0x14, 0xf1, 0x18, 0xa9, 0xe7, 0xcd, 0x82, 0x1c, 0xfe, 0x2c, 0x20, + 0x09, 0xe7, 0xcd, 0xfb, 0x24, 0xfd, 0xcb, 0x01, 0x76, 0xc0, 0xcf, 0x0b, + 0xcd, 0xfb, 0x24, 0xfd, 0xcb, 0x01, 0x76, 0xc8, 0x18, 0xf4, 0xfd, 0xcb, + 0x01, 0x7e, 0xfd, 0xcb, 0x02, 0x86, 0xc4, 0x4d, 0x0d, 0xf1, 0x3a, 0x74, + 0x5c, 0xd6, 0x13, 0xcd, 0xfc, 0x21, 0xcd, 0xee, 0x1b, 0x2a, 0x8f, 0x5c, + 0x22, 0x8d, 0x5c, 0x21, 0x91, 0x5c, 0x7e, 0x07, 0xae, 0xe6, 0xaa, 0xae, + 0x77, 0xc9, 0xcd, 0x30, 0x25, 0x28, 0x13, 0xfd, 0xcb, 0x02, 0x86, 0xcd, + 0x4d, 0x0d, 0x21, 0x90, 0x5c, 0x7e, 0xf6, 0xf8, 0x77, 0xfd, 0xcb, 0x57, + 0xb6, 0xdf, 0xcd, 0xe2, 0x21, 0x18, 0x9f, 0xc3, 0x05, 0x06, 0xfe, 0x0d, + 0x28, 0x04, 0xfe, 0x3a, 0x20, 0x9c, 0xcd, 0x30, 0x25, 0xc8, 0xef, 0xa0, + 0x38, 0xc9, 0xcf, 0x08, 0xc1, 0xcd, 0x30, 0x25, 0x28, 0x0a, 0xef, 0x02, + 0x38, 0xeb, 0xcd, 0xe9, 0x34, 0xda, 0xb3, 0x1b, 0xc3, 0x29, 0x1b, 0xfe, + 0xcd, 0x20, 0x09, 0xe7, 0xcd, 0x82, 0x1c, 0xcd, 0xee, 0x1b, 0x18, 0x06, + 0xcd, 0xee, 0x1b, 0xef, 0xa1, 0x38, 0xef, 0xc0, 0x02, 0x01, 0xe0, 0x01, + 0x38, 0xcd, 0xff, 0x2a, 0x22, 0x68, 0x5c, 0x2b, 0x7e, 0xcb, 0xfe, 0x01, + 0x06, 0x00, 0x09, 0x07, 0x38, 0x06, 0x0e, 0x0d, 0xcd, 0x55, 0x16, 0x23, + 0xe5, 0xef, 0x02, 0x02, 0x38, 0xe1, 0xeb, 0x0e, 0x0a, 0xed, 0xb0, 0x2a, + 0x45, 0x5c, 0xeb, 0x73, 0x23, 0x72, 0xfd, 0x56, 0x0d, 0x14, 0x23, 0x72, + 0xcd, 0xda, 0x1d, 0xd0, 0xfd, 0x46, 0x38, 0x2a, 0x45, 0x5c, 0x22, 0x42, + 0x5c, 0x3a, 0x47, 0x5c, 0xed, 0x44, 0x57, 0x2a, 0x5d, 0x5c, 0x1e, 0xf3, + 0xc5, 0xed, 0x4b, 0x55, 0x5c, 0xcd, 0x86, 0x1d, 0xed, 0x43, 0x55, 0x5c, + 0xc1, 0x38, 0x11, 0xe7, 0xf6, 0x20, 0xb8, 0x28, 0x03, 0xe7, 0x18, 0xe8, + 0xe7, 0x3e, 0x01, 0x92, 0x32, 0x44, 0x5c, 0xc9, 0xcf, 0x11, 0x7e, 0xfe, + 0x3a, 0x28, 0x18, 0x23, 0x7e, 0xe6, 0xc0, 0x37, 0xc0, 0x46, 0x23, 0x4e, + 0xed, 0x43, 0x42, 0x5c, 0x23, 0x4e, 0x23, 0x46, 0xe5, 0x09, 0x44, 0x4d, + 0xe1, 0x16, 0x00, 0xc5, 0xcd, 0x8b, 0x19, 0xc1, 0xd0, 0x18, 0xe0, 0xfd, + 0xcb, 0x37, 0x4e, 0xc2, 0x2e, 0x1c, 0x2a, 0x4d, 0x5c, 0xcb, 0x7e, 0x28, + 0x1f, 0x23, 0x22, 0x68, 0x5c, 0xef, 0xe0, 0xe2, 0x0f, 0xc0, 0x02, 0x38, + 0xcd, 0xda, 0x1d, 0xd8, 0x2a, 0x68, 0x5c, 0x11, 0x0f, 0x00, 0x19, 0x5e, + 0x23, 0x56, 0x23, 0x66, 0xeb, 0xc3, 0x73, 0x1e, 0xcf, 0x00, 0xef, 0xe1, + 0xe0, 0xe2, 0x36, 0x00, 0x02, 0x01, 0x03, 0x37, 0x00, 0x04, 0x38, 0xa7, + 0xc9, 0x38, 0x37, 0xc9, 0xe7, 0xcd, 0x1f, 0x1c, 0xcd, 0x30, 0x25, 0x28, + 0x29, 0xdf, 0x22, 0x5f, 0x5c, 0x2a, 0x57, 0x5c, 0x7e, 0xfe, 0x2c, 0x28, + 0x09, 0x1e, 0xe4, 0xcd, 0x86, 0x1d, 0x30, 0x02, 0xcf, 0x0d, 0xcd, 0x77, + 0x00, 0xcd, 0x56, 0x1c, 0xdf, 0x22, 0x57, 0x5c, 0x2a, 0x5f, 0x5c, 0xfd, + 0x36, 0x26, 0x00, 0xcd, 0x78, 0x00, 0xdf, 0xfe, 0x2c, 0x28, 0xc9, 0xcd, + 0xee, 0x1b, 0xc9, 0xcd, 0x30, 0x25, 0x20, 0x0b, 0xcd, 0xfb, 0x24, 0xfe, + 0x2c, 0xc4, 0xee, 0x1b, 0xe7, 0x18, 0xf5, 0x3e, 0xe4, 0x47, 0xed, 0xb9, + 0x11, 0x00, 0x02, 0xc3, 0x8b, 0x19, 0xcd, 0x99, 0x1e, 0x60, 0x69, 0xcd, + 0x6e, 0x19, 0x2b, 0x22, 0x57, 0x5c, 0xc9, 0xcd, 0x99, 0x1e, 0x78, 0xb1, + 0x20, 0x04, 0xed, 0x4b, 0x78, 0x5c, 0xed, 0x43, 0x76, 0x5c, 0xc9, 0x2a, + 0x6e, 0x5c, 0xfd, 0x56, 0x36, 0x18, 0x0c, 0xcd, 0x99, 0x1e, 0x60, 0x69, + 0x16, 0x00, 0x7c, 0xfe, 0xf0, 0x30, 0x2c, 0x22, 0x42, 0x5c, 0xfd, 0x72, + 0x0a, 0xc9, 0xcd, 0x85, 0x1e, 0xed, 0x79, 0xc9, 0xcd, 0x85, 0x1e, 0x02, + 0xc9, 0xcd, 0xd5, 0x2d, 0x38, 0x15, 0x28, 0x02, 0xed, 0x44, 0xf5, 0xcd, + 0x99, 0x1e, 0xf1, 0xc9, 0xcd, 0xd5, 0x2d, 0x18, 0x03, 0xcd, 0xa2, 0x2d, + 0x38, 0x01, 0xc8, 0xcf, 0x0a, 0xcd, 0x67, 0x1e, 0x01, 0x00, 0x00, 0xcd, + 0x45, 0x1e, 0x18, 0x03, 0xcd, 0x99, 0x1e, 0x78, 0xb1, 0x20, 0x04, 0xed, + 0x4b, 0xb2, 0x5c, 0xc5, 0xed, 0x5b, 0x4b, 0x5c, 0x2a, 0x59, 0x5c, 0x2b, + 0xcd, 0xe5, 0x19, 0xcd, 0x6b, 0x0d, 0x2a, 0x65, 0x5c, 0x11, 0x32, 0x00, + 0x19, 0xd1, 0xed, 0x52, 0x30, 0x08, 0x2a, 0xb4, 0x5c, 0xa7, 0xed, 0x52, + 0x30, 0x02, 0xcf, 0x15, 0xeb, 0x22, 0xb2, 0x5c, 0xd1, 0xc1, 0x36, 0x3e, + 0x2b, 0xf9, 0xc5, 0xed, 0x73, 0x3d, 0x5c, 0xeb, 0xe9, 0xd1, 0xfd, 0x66, + 0x0d, 0x24, 0xe3, 0x33, 0xed, 0x4b, 0x45, 0x5c, 0xc5, 0xe5, 0xed, 0x73, + 0x3d, 0x5c, 0xd5, 0xcd, 0x67, 0x1e, 0x01, 0x14, 0x00, 0x2a, 0x65, 0x5c, + 0x09, 0x38, 0x0a, 0xeb, 0x21, 0x50, 0x00, 0x19, 0x38, 0x03, 0xed, 0x72, + 0xd8, 0x2e, 0x03, 0xc3, 0x55, 0x00, 0x01, 0x00, 0x00, 0xcd, 0x05, 0x1f, + 0x44, 0x4d, 0xc9, 0xc1, 0xe1, 0xd1, 0x7a, 0xfe, 0x3e, 0x28, 0x0b, 0x3b, + 0xe3, 0xeb, 0xed, 0x73, 0x3d, 0x5c, 0xc5, 0xc3, 0x73, 0x1e, 0xd5, 0xe5, + 0xcf, 0x06, 0xcd, 0x99, 0x1e, 0x76, 0x0b, 0x78, 0xb1, 0x28, 0x0c, 0x78, + 0xa1, 0x3c, 0x20, 0x01, 0x03, 0xfd, 0xcb, 0x01, 0x6e, 0x28, 0xee, 0xfd, + 0xcb, 0x01, 0xae, 0xc9, 0x3e, 0x7f, 0xdb, 0xfe, 0x1f, 0xd8, 0x3e, 0xfe, + 0xdb, 0xfe, 0x1f, 0xc9, 0xcd, 0x30, 0x25, 0x28, 0x05, 0x3e, 0xce, 0xc3, + 0x39, 0x1e, 0xfd, 0xcb, 0x01, 0xf6, 0xcd, 0x8d, 0x2c, 0x30, 0x16, 0xe7, + 0xfe, 0x24, 0x20, 0x05, 0xfd, 0xcb, 0x01, 0xb6, 0xe7, 0xfe, 0x28, 0x20, + 0x3c, 0xe7, 0xfe, 0x29, 0x28, 0x20, 0xcd, 0x8d, 0x2c, 0xd2, 0x8a, 0x1c, + 0xeb, 0xe7, 0xfe, 0x24, 0x20, 0x02, 0xeb, 0xe7, 0xeb, 0x01, 0x06, 0x00, + 0xcd, 0x55, 0x16, 0x23, 0x23, 0x36, 0x0e, 0xfe, 0x2c, 0x20, 0x03, 0xe7, + 0x18, 0xe0, 0xfe, 0x29, 0x20, 0x13, 0xe7, 0xfe, 0x3d, 0x20, 0x0e, 0xe7, + 0x3a, 0x3b, 0x5c, 0xf5, 0xcd, 0xfb, 0x24, 0xf1, 0xfd, 0xae, 0x01, 0xe6, + 0x40, 0xc2, 0x8a, 0x1c, 0xcd, 0xee, 0x1b, 0xcd, 0x30, 0x25, 0xe1, 0xc8, + 0xe9, 0x3e, 0x03, 0x18, 0x02, 0x3e, 0x02, 0xcd, 0x30, 0x25, 0xc4, 0x01, + 0x16, 0xcd, 0x4d, 0x0d, 0xcd, 0xdf, 0x1f, 0xcd, 0xee, 0x1b, 0xc9, 0xdf, + 0xcd, 0x45, 0x20, 0x28, 0x0d, 0xcd, 0x4e, 0x20, 0x28, 0xfb, 0xcd, 0xfc, + 0x1f, 0xcd, 0x4e, 0x20, 0x28, 0xf3, 0xfe, 0x29, 0xc8, 0xcd, 0xc3, 0x1f, + 0x3e, 0x0d, 0xd7, 0xc9, 0xdf, 0xfe, 0xac, 0x20, 0x0d, 0xcd, 0x79, 0x1c, + 0xcd, 0xc3, 0x1f, 0xcd, 0x07, 0x23, 0x3e, 0x16, 0x18, 0x10, 0xfe, 0xad, + 0x20, 0x12, 0xe7, 0xcd, 0x82, 0x1c, 0xcd, 0xc3, 0x1f, 0xcd, 0x99, 0x1e, + 0x3e, 0x17, 0xd7, 0x79, 0xd7, 0x78, 0xd7, 0xc9, 0xcd, 0xf2, 0x21, 0xd0, + 0xcd, 0x70, 0x20, 0xd0, 0xcd, 0xfb, 0x24, 0xcd, 0xc3, 0x1f, 0xfd, 0xcb, + 0x01, 0x76, 0xcc, 0xf1, 0x2b, 0xc2, 0xe3, 0x2d, 0x78, 0xb1, 0x0b, 0xc8, + 0x1a, 0x13, 0xd7, 0x18, 0xf7, 0xfe, 0x29, 0xc8, 0xfe, 0x0d, 0xc8, 0xfe, + 0x3a, 0xc9, 0xdf, 0xfe, 0x3b, 0x28, 0x14, 0xfe, 0x2c, 0x20, 0x0a, 0xcd, + 0x30, 0x25, 0x28, 0x0b, 0x3e, 0x06, 0xd7, 0x18, 0x06, 0xfe, 0x27, 0xc0, + 0xcd, 0xf5, 0x1f, 0xe7, 0xcd, 0x45, 0x20, 0x20, 0x01, 0xc1, 0xbf, 0xc9, + 0xfe, 0x23, 0x37, 0xc0, 0xe7, 0xcd, 0x82, 0x1c, 0xa7, 0xcd, 0xc3, 0x1f, + 0xcd, 0x94, 0x1e, 0xfe, 0x10, 0xd2, 0x0e, 0x16, 0xcd, 0x01, 0x16, 0xa7, + 0xc9, 0xcd, 0x30, 0x25, 0x28, 0x08, 0x3e, 0x01, 0xcd, 0x01, 0x16, 0xcd, + 0x6e, 0x0d, 0xfd, 0x36, 0x02, 0x01, 0xcd, 0xc1, 0x20, 0xcd, 0xee, 0x1b, + 0xed, 0x4b, 0x88, 0x5c, 0x3a, 0x6b, 0x5c, 0xb8, 0x38, 0x03, 0x0e, 0x21, + 0x47, 0xed, 0x43, 0x88, 0x5c, 0x3e, 0x19, 0x90, 0x32, 0x8c, 0x5c, 0xfd, + 0xcb, 0x02, 0x86, 0xcd, 0xd9, 0x0d, 0xc3, 0x6e, 0x0d, 0xcd, 0x4e, 0x20, + 0x28, 0xfb, 0xfe, 0x28, 0x20, 0x0e, 0xe7, 0xcd, 0xdf, 0x1f, 0xdf, 0xfe, + 0x29, 0xc2, 0x8a, 0x1c, 0xe7, 0xc3, 0xb2, 0x21, 0xfe, 0xca, 0x20, 0x11, + 0xe7, 0xcd, 0x1f, 0x1c, 0xfd, 0xcb, 0x37, 0xfe, 0xfd, 0xcb, 0x01, 0x76, + 0xc2, 0x8a, 0x1c, 0x18, 0x0d, 0xcd, 0x8d, 0x2c, 0xd2, 0xaf, 0x21, 0xcd, + 0x1f, 0x1c, 0xfd, 0xcb, 0x37, 0xbe, 0xcd, 0x30, 0x25, 0xca, 0xb2, 0x21, + 0xcd, 0xbf, 0x16, 0x21, 0x71, 0x5c, 0xcb, 0xb6, 0xcb, 0xee, 0x01, 0x01, + 0x00, 0xcb, 0x7e, 0x20, 0x0b, 0x3a, 0x3b, 0x5c, 0xe6, 0x40, 0x20, 0x02, + 0x0e, 0x03, 0xb6, 0x77, 0xf7, 0x36, 0x0d, 0x79, 0x0f, 0x0f, 0x30, 0x05, + 0x3e, 0x22, 0x12, 0x2b, 0x77, 0x22, 0x5b, 0x5c, 0xfd, 0xcb, 0x37, 0x7e, + 0x20, 0x2c, 0x2a, 0x5d, 0x5c, 0xe5, 0x2a, 0x3d, 0x5c, 0xe5, 0x21, 0x3a, + 0x21, 0xe5, 0xfd, 0xcb, 0x30, 0x66, 0x28, 0x04, 0xed, 0x73, 0x3d, 0x5c, + 0x2a, 0x61, 0x5c, 0xcd, 0xa7, 0x11, 0xfd, 0x36, 0x00, 0xff, 0xcd, 0x2c, + 0x0f, 0xfd, 0xcb, 0x01, 0xbe, 0xcd, 0xb9, 0x21, 0x18, 0x03, 0xcd, 0x2c, + 0x0f, 0xfd, 0x36, 0x22, 0x00, 0xcd, 0xd6, 0x21, 0x20, 0x0a, 0xcd, 0x1d, + 0x11, 0xed, 0x4b, 0x82, 0x5c, 0xcd, 0xd9, 0x0d, 0x21, 0x71, 0x5c, 0xcb, + 0xae, 0xcb, 0x7e, 0xcb, 0xbe, 0x20, 0x1c, 0xe1, 0xe1, 0x22, 0x3d, 0x5c, + 0xe1, 0x22, 0x5f, 0x5c, 0xfd, 0xcb, 0x01, 0xfe, 0xcd, 0xb9, 0x21, 0x2a, + 0x5f, 0x5c, 0xfd, 0x36, 0x26, 0x00, 0x22, 0x5d, 0x5c, 0x18, 0x17, 0x2a, + 0x63, 0x5c, 0xed, 0x5b, 0x61, 0x5c, 0x37, 0xed, 0x52, 0x44, 0x4d, 0xcd, + 0xb2, 0x2a, 0xcd, 0xff, 0x2a, 0x18, 0x03, 0xcd, 0xfc, 0x1f, 0xcd, 0x4e, + 0x20, 0xca, 0xc1, 0x20, 0xc9, 0x2a, 0x61, 0x5c, 0x22, 0x5d, 0x5c, 0xdf, + 0xfe, 0xe2, 0x28, 0x0c, 0x3a, 0x71, 0x5c, 0xcd, 0x59, 0x1c, 0xdf, 0xfe, + 0x0d, 0xc8, 0xcf, 0x0b, 0xcd, 0x30, 0x25, 0xc8, 0xcf, 0x10, 0x2a, 0x51, + 0x5c, 0x23, 0x23, 0x23, 0x23, 0x7e, 0xfe, 0x4b, 0xc9, 0xe7, 0xcd, 0xf2, + 0x21, 0xd8, 0xdf, 0xfe, 0x2c, 0x28, 0xf6, 0xfe, 0x3b, 0x28, 0xf2, 0xc3, + 0x8a, 0x1c, 0xfe, 0xd9, 0xd8, 0xfe, 0xdf, 0x3f, 0xd8, 0xf5, 0xe7, 0xf1, + 0xd6, 0xc9, 0xf5, 0xcd, 0x82, 0x1c, 0xf1, 0xa7, 0xcd, 0xc3, 0x1f, 0xf5, + 0xcd, 0x94, 0x1e, 0x57, 0xf1, 0xd7, 0x7a, 0xd7, 0xc9, 0xd6, 0x11, 0xce, + 0x00, 0x28, 0x1d, 0xd6, 0x02, 0xce, 0x00, 0x28, 0x56, 0xfe, 0x01, 0x7a, + 0x06, 0x01, 0x20, 0x04, 0x07, 0x07, 0x06, 0x04, 0x4f, 0x7a, 0xfe, 0x02, + 0x30, 0x16, 0x79, 0x21, 0x91, 0x5c, 0x18, 0x38, 0x7a, 0x06, 0x07, 0x38, + 0x05, 0x07, 0x07, 0x07, 0x06, 0x38, 0x4f, 0x7a, 0xfe, 0x0a, 0x38, 0x02, + 0xcf, 0x13, 0x21, 0x8f, 0x5c, 0xfe, 0x08, 0x38, 0x0b, 0x7e, 0x28, 0x07, + 0xb0, 0x2f, 0xe6, 0x24, 0x28, 0x01, 0x78, 0x4f, 0x79, 0xcd, 0x6c, 0x22, + 0x3e, 0x07, 0xba, 0x9f, 0xcd, 0x6c, 0x22, 0x07, 0x07, 0xe6, 0x50, 0x47, + 0x3e, 0x08, 0xba, 0x9f, 0xae, 0xa0, 0xae, 0x77, 0x23, 0x78, 0xc9, 0x9f, + 0x7a, 0x0f, 0x06, 0x80, 0x20, 0x03, 0x0f, 0x06, 0x40, 0x4f, 0x7a, 0xfe, + 0x08, 0x28, 0x04, 0xfe, 0x02, 0x30, 0xbd, 0x79, 0x21, 0x8f, 0x5c, 0xcd, + 0x6c, 0x22, 0x79, 0x0f, 0x0f, 0x0f, 0x18, 0xd8, 0xcd, 0x94, 0x1e, 0xfe, + 0x08, 0x30, 0xa9, 0xd3, 0xfe, 0x07, 0x07, 0x07, 0xcb, 0x6f, 0x20, 0x02, + 0xee, 0x07, 0x32, 0x48, 0x5c, 0xc9, 0x3e, 0xaf, 0x90, 0xda, 0xf9, 0x24, + 0x47, 0xa7, 0x1f, 0x37, 0x1f, 0xa7, 0x1f, 0xa8, 0xe6, 0xf8, 0xa8, 0x67, + 0x79, 0x07, 0x07, 0x07, 0xa8, 0xe6, 0xc7, 0xa8, 0x07, 0x07, 0x6f, 0x79, + 0xe6, 0x07, 0xc9, 0xcd, 0x07, 0x23, 0xcd, 0xaa, 0x22, 0x47, 0x04, 0x7e, + 0x07, 0x10, 0xfd, 0xe6, 0x01, 0xc3, 0x28, 0x2d, 0xcd, 0x07, 0x23, 0xcd, + 0xe5, 0x22, 0xc3, 0x4d, 0x0d, 0xed, 0x43, 0x7d, 0x5c, 0xcd, 0xaa, 0x22, + 0x47, 0x04, 0x3e, 0xfe, 0x0f, 0x10, 0xfd, 0x47, 0x7e, 0xfd, 0x4e, 0x57, + 0xcb, 0x41, 0x20, 0x01, 0xa0, 0xcb, 0x51, 0x20, 0x02, 0xa8, 0x2f, 0x77, + 0xc3, 0xdb, 0x0b, 0xcd, 0x14, 0x23, 0x47, 0xc5, 0xcd, 0x14, 0x23, 0x59, + 0xc1, 0x51, 0x4f, 0xc9, 0xcd, 0xd5, 0x2d, 0xda, 0xf9, 0x24, 0x0e, 0x01, + 0xc8, 0x0e, 0xff, 0xc9, 0xdf, 0xfe, 0x2c, 0xc2, 0x8a, 0x1c, 0xe7, 0xcd, + 0x82, 0x1c, 0xcd, 0xee, 0x1b, 0xef, 0x2a, 0x3d, 0x38, 0x7e, 0xfe, 0x81, + 0x30, 0x05, 0xef, 0x02, 0x38, 0x18, 0xa1, 0xef, 0xa3, 0x38, 0x36, 0x83, + 0xef, 0xc5, 0x02, 0x38, 0xcd, 0x7d, 0x24, 0xc5, 0xef, 0x31, 0xe1, 0x04, + 0x38, 0x7e, 0xfe, 0x80, 0x30, 0x08, 0xef, 0x02, 0x02, 0x38, 0xc1, 0xc3, + 0xdc, 0x22, 0xef, 0xc2, 0x01, 0xc0, 0x02, 0x03, 0x01, 0xe0, 0x0f, 0xc0, + 0x01, 0x31, 0xe0, 0x01, 0x31, 0xe0, 0xa0, 0xc1, 0x02, 0x38, 0xfd, 0x34, + 0x62, 0xcd, 0x94, 0x1e, 0x6f, 0xe5, 0xcd, 0x94, 0x1e, 0xe1, 0x67, 0x22, + 0x7d, 0x5c, 0xc1, 0xc3, 0x20, 0x24, 0xdf, 0xfe, 0x2c, 0x28, 0x06, 0xcd, + 0xee, 0x1b, 0xc3, 0x77, 0x24, 0xe7, 0xcd, 0x82, 0x1c, 0xcd, 0xee, 0x1b, + 0xef, 0xc5, 0xa2, 0x04, 0x1f, 0x31, 0x30, 0x30, 0x00, 0x06, 0x02, 0x38, + 0xc3, 0x77, 0x24, 0xc0, 0x02, 0xc1, 0x02, 0x31, 0x2a, 0xe1, 0x01, 0xe1, + 0x2a, 0x0f, 0xe0, 0x05, 0x2a, 0xe0, 0x01, 0x3d, 0x38, 0x7e, 0xfe, 0x81, + 0x30, 0x07, 0xef, 0x02, 0x02, 0x38, 0xc3, 0x77, 0x24, 0xcd, 0x7d, 0x24, + 0xc5, 0xef, 0x02, 0xe1, 0x01, 0x05, 0xc1, 0x02, 0x01, 0x31, 0xe1, 0x04, + 0xc2, 0x02, 0x01, 0x31, 0xe1, 0x04, 0xe2, 0xe5, 0xe0, 0x03, 0xa2, 0x04, + 0x31, 0x1f, 0xc5, 0x02, 0x20, 0xc0, 0x02, 0xc2, 0x02, 0xc1, 0xe5, 0x04, + 0xe0, 0xe2, 0x04, 0x0f, 0xe1, 0x01, 0xc1, 0x02, 0xe0, 0x04, 0xe2, 0xe5, + 0x04, 0x03, 0xc2, 0x2a, 0xe1, 0x2a, 0x0f, 0x02, 0x38, 0x1a, 0xfe, 0x81, + 0xc1, 0xda, 0x77, 0x24, 0xc5, 0xef, 0x01, 0x38, 0x3a, 0x7d, 0x5c, 0xcd, + 0x28, 0x2d, 0xef, 0xc0, 0x0f, 0x01, 0x38, 0x3a, 0x7e, 0x5c, 0xcd, 0x28, + 0x2d, 0xef, 0xc5, 0x0f, 0xe0, 0xe5, 0x38, 0xc1, 0x05, 0x28, 0x3c, 0x18, + 0x14, 0xef, 0xe1, 0x31, 0xe3, 0x04, 0xe2, 0xe4, 0x04, 0x03, 0xc1, 0x02, + 0xe4, 0x04, 0xe2, 0xe3, 0x04, 0x0f, 0xc2, 0x02, 0x38, 0xc5, 0xef, 0xc0, + 0x02, 0xe1, 0x0f, 0x31, 0x38, 0x3a, 0x7d, 0x5c, 0xcd, 0x28, 0x2d, 0xef, + 0x03, 0xe0, 0xe2, 0x0f, 0xc0, 0x01, 0xe0, 0x38, 0x3a, 0x7e, 0x5c, 0xcd, + 0x28, 0x2d, 0xef, 0x03, 0x38, 0xcd, 0xb7, 0x24, 0xc1, 0x10, 0xc6, 0xef, + 0x02, 0x02, 0x01, 0x38, 0x3a, 0x7d, 0x5c, 0xcd, 0x28, 0x2d, 0xef, 0x03, + 0x01, 0x38, 0x3a, 0x7e, 0x5c, 0xcd, 0x28, 0x2d, 0xef, 0x03, 0x38, 0xcd, + 0xb7, 0x24, 0xc3, 0x4d, 0x0d, 0xef, 0x31, 0x28, 0x34, 0x32, 0x00, 0x01, + 0x05, 0xe5, 0x01, 0x05, 0x2a, 0x38, 0xcd, 0xd5, 0x2d, 0x38, 0x06, 0xe6, + 0xfc, 0xc6, 0x04, 0x30, 0x02, 0x3e, 0xfc, 0xf5, 0xcd, 0x28, 0x2d, 0xef, + 0xe5, 0x01, 0x05, 0x31, 0x1f, 0xc4, 0x02, 0x31, 0xa2, 0x04, 0x1f, 0xc1, + 0x01, 0xc0, 0x02, 0x31, 0x04, 0x31, 0x0f, 0xa1, 0x03, 0x1b, 0xc3, 0x02, + 0x38, 0xc1, 0xc9, 0xcd, 0x07, 0x23, 0x79, 0xb8, 0x30, 0x06, 0x69, 0xd5, + 0xaf, 0x5f, 0x18, 0x07, 0xb1, 0xc8, 0x68, 0x41, 0xd5, 0x16, 0x00, 0x60, + 0x78, 0x1f, 0x85, 0x38, 0x03, 0xbc, 0x38, 0x07, 0x94, 0x4f, 0xd9, 0xc1, + 0xc5, 0x18, 0x04, 0x4f, 0xd5, 0xd9, 0xc1, 0x2a, 0x7d, 0x5c, 0x78, 0x84, + 0x47, 0x79, 0x3c, 0x85, 0x38, 0x0d, 0x28, 0x0d, 0x3d, 0x4f, 0xcd, 0xe5, + 0x22, 0xd9, 0x79, 0x10, 0xd9, 0xd1, 0xc9, 0x28, 0xf3, 0xcf, 0x0a, 0xdf, + 0x06, 0x00, 0xc5, 0x4f, 0x21, 0x96, 0x25, 0xcd, 0xdc, 0x16, 0x79, 0xd2, + 0x84, 0x26, 0x06, 0x00, 0x4e, 0x09, 0xe9, 0xcd, 0x74, 0x00, 0x03, 0xfe, + 0x0d, 0xca, 0x8a, 0x1c, 0xfe, 0x22, 0x20, 0xf3, 0xcd, 0x74, 0x00, 0xfe, + 0x22, 0xc9, 0xe7, 0xfe, 0x28, 0x20, 0x06, 0xcd, 0x79, 0x1c, 0xdf, 0xfe, + 0x29, 0xc2, 0x8a, 0x1c, 0xfd, 0xcb, 0x01, 0x7e, 0xc9, 0xcd, 0x07, 0x23, + 0x2a, 0x36, 0x5c, 0x11, 0x00, 0x01, 0x19, 0x79, 0x0f, 0x0f, 0x0f, 0xe6, + 0xe0, 0xa8, 0x5f, 0x79, 0xe6, 0x18, 0xee, 0x40, 0x57, 0x06, 0x60, 0xc5, + 0xd5, 0xe5, 0x1a, 0xae, 0x28, 0x04, 0x3c, 0x20, 0x1a, 0x3d, 0x4f, 0x06, + 0x07, 0x14, 0x23, 0x1a, 0xae, 0xa9, 0x20, 0x0f, 0x10, 0xf7, 0xc1, 0xc1, + 0xc1, 0x3e, 0x80, 0x90, 0x01, 0x01, 0x00, 0xf7, 0x12, 0x18, 0x0a, 0xe1, + 0x11, 0x08, 0x00, 0x19, 0xd1, 0xc1, 0x10, 0xd3, 0x48, 0xc3, 0xb2, 0x2a, + 0xcd, 0x07, 0x23, 0x79, 0x0f, 0x0f, 0x0f, 0x4f, 0xe6, 0xe0, 0xa8, 0x6f, + 0x79, 0xe6, 0x03, 0xee, 0x58, 0x67, 0x7e, 0xc3, 0x28, 0x2d, 0x22, 0x1c, + 0x28, 0x4f, 0x2e, 0xf2, 0x2b, 0x12, 0xa8, 0x56, 0xa5, 0x57, 0xa7, 0x84, + 0xa6, 0x8f, 0xc4, 0xe6, 0xaa, 0xbf, 0xab, 0xc7, 0xa9, 0xce, 0x00, 0xe7, + 0xc3, 0xff, 0x24, 0xdf, 0x23, 0xe5, 0x01, 0x00, 0x00, 0xcd, 0x0f, 0x25, + 0x20, 0x1b, 0xcd, 0x0f, 0x25, 0x28, 0xfb, 0xcd, 0x30, 0x25, 0x28, 0x11, + 0xf7, 0xe1, 0xd5, 0x7e, 0x23, 0x12, 0x13, 0xfe, 0x22, 0x20, 0xf8, 0x7e, + 0x23, 0xfe, 0x22, 0x28, 0xf2, 0x0b, 0xd1, 0x21, 0x3b, 0x5c, 0xcb, 0xb6, + 0xcb, 0x7e, 0xc4, 0xb2, 0x2a, 0xc3, 0x12, 0x27, 0xe7, 0xcd, 0xfb, 0x24, + 0xfe, 0x29, 0xc2, 0x8a, 0x1c, 0xe7, 0xc3, 0x12, 0x27, 0xc3, 0xbd, 0x27, + 0xcd, 0x30, 0x25, 0x28, 0x28, 0xed, 0x4b, 0x76, 0x5c, 0xcd, 0x2b, 0x2d, + 0xef, 0xa1, 0x0f, 0x34, 0x37, 0x16, 0x04, 0x34, 0x80, 0x41, 0x00, 0x00, + 0x80, 0x32, 0x02, 0xa1, 0x03, 0x31, 0x38, 0xcd, 0xa2, 0x2d, 0xed, 0x43, + 0x76, 0x5c, 0x7e, 0xa7, 0x28, 0x03, 0xd6, 0x10, 0x77, 0x18, 0x09, 0xcd, + 0x30, 0x25, 0x28, 0x04, 0xef, 0xa3, 0x38, 0x34, 0xe7, 0xc3, 0xc3, 0x26, + 0x01, 0x5a, 0x10, 0xe7, 0xfe, 0x23, 0xca, 0x0d, 0x27, 0x21, 0x3b, 0x5c, + 0xcb, 0xb6, 0xcb, 0x7e, 0x28, 0x1f, 0xcd, 0x8e, 0x02, 0x0e, 0x00, 0x20, + 0x13, 0xcd, 0x1e, 0x03, 0x30, 0x0e, 0x15, 0x5f, 0xcd, 0x33, 0x03, 0xf5, + 0x01, 0x01, 0x00, 0xf7, 0xf1, 0x12, 0x0e, 0x01, 0x06, 0x00, 0xcd, 0xb2, + 0x2a, 0xc3, 0x12, 0x27, 0xcd, 0x22, 0x25, 0xc4, 0x35, 0x25, 0xe7, 0xc3, + 0xdb, 0x25, 0xcd, 0x22, 0x25, 0xc4, 0x80, 0x25, 0xe7, 0x18, 0x48, 0xcd, + 0x22, 0x25, 0xc4, 0xcb, 0x22, 0xe7, 0x18, 0x3f, 0xcd, 0x88, 0x2c, 0x30, + 0x56, 0xfe, 0x41, 0x30, 0x3c, 0xcd, 0x30, 0x25, 0x20, 0x23, 0xcd, 0x9b, + 0x2c, 0xdf, 0x01, 0x06, 0x00, 0xcd, 0x55, 0x16, 0x23, 0x36, 0x0e, 0x23, + 0xeb, 0x2a, 0x65, 0x5c, 0x0e, 0x05, 0xa7, 0xed, 0x42, 0x22, 0x65, 0x5c, + 0xed, 0xb0, 0xeb, 0x2b, 0xcd, 0x77, 0x00, 0x18, 0x0e, 0xdf, 0x23, 0x7e, + 0xfe, 0x0e, 0x20, 0xfa, 0x23, 0xcd, 0xb4, 0x33, 0x22, 0x5d, 0x5c, 0xfd, + 0xcb, 0x01, 0xf6, 0x18, 0x14, 0xcd, 0xb2, 0x28, 0xda, 0x2e, 0x1c, 0xcc, + 0x96, 0x29, 0x3a, 0x3b, 0x5c, 0xfe, 0xc0, 0x38, 0x04, 0x23, 0xcd, 0xb4, + 0x33, 0x18, 0x33, 0x01, 0xdb, 0x09, 0xfe, 0x2d, 0x28, 0x27, 0x01, 0x18, + 0x10, 0xfe, 0xae, 0x28, 0x20, 0xd6, 0xaf, 0xda, 0x8a, 0x1c, 0x01, 0xf0, + 0x04, 0xfe, 0x14, 0x28, 0x14, 0xd2, 0x8a, 0x1c, 0x06, 0x10, 0xc6, 0xdc, + 0x4f, 0xfe, 0xdf, 0x30, 0x02, 0xcb, 0xb1, 0xfe, 0xee, 0x38, 0x02, 0xcb, + 0xb9, 0xc5, 0xe7, 0xc3, 0xff, 0x24, 0xdf, 0xfe, 0x28, 0x20, 0x0c, 0xfd, + 0xcb, 0x01, 0x76, 0x20, 0x17, 0xcd, 0x52, 0x2a, 0xe7, 0x18, 0xf0, 0x06, + 0x00, 0x4f, 0x21, 0x95, 0x27, 0xcd, 0xdc, 0x16, 0x30, 0x06, 0x4e, 0x21, + 0xed, 0x26, 0x09, 0x46, 0xd1, 0x7a, 0xb8, 0x38, 0x3a, 0xa7, 0xca, 0x18, + 0x00, 0xc5, 0x21, 0x3b, 0x5c, 0x7b, 0xfe, 0xed, 0x20, 0x06, 0xcb, 0x76, + 0x20, 0x02, 0x1e, 0x99, 0xd5, 0xcd, 0x30, 0x25, 0x28, 0x09, 0x7b, 0xe6, + 0x3f, 0x47, 0xef, 0x3b, 0x38, 0x18, 0x09, 0x7b, 0xfd, 0xae, 0x01, 0xe6, + 0x40, 0xc2, 0x8a, 0x1c, 0xd1, 0x21, 0x3b, 0x5c, 0xcb, 0xf6, 0xcb, 0x7b, + 0x20, 0x02, 0xcb, 0xb6, 0xc1, 0x18, 0xc1, 0xd5, 0x79, 0xfd, 0xcb, 0x01, + 0x76, 0x20, 0x15, 0xe6, 0x3f, 0xc6, 0x08, 0x4f, 0xfe, 0x10, 0x20, 0x04, + 0xcb, 0xf1, 0x18, 0x08, 0x38, 0xd7, 0xfe, 0x17, 0x28, 0x02, 0xcb, 0xf9, + 0xc5, 0xe7, 0xc3, 0xff, 0x24, 0x2b, 0xcf, 0x2d, 0xc3, 0x2a, 0xc4, 0x2f, + 0xc5, 0x5e, 0xc6, 0x3d, 0xce, 0x3e, 0xcc, 0x3c, 0xcd, 0xc7, 0xc9, 0xc8, + 0xca, 0xc9, 0xcb, 0xc5, 0xc7, 0xc6, 0xc8, 0x00, 0x06, 0x08, 0x08, 0x0a, + 0x02, 0x03, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0xcd, 0x30, 0x25, + 0x20, 0x35, 0xe7, 0xcd, 0x8d, 0x2c, 0xd2, 0x8a, 0x1c, 0xe7, 0xfe, 0x24, + 0xf5, 0x20, 0x01, 0xe7, 0xfe, 0x28, 0x20, 0x12, 0xe7, 0xfe, 0x29, 0x28, + 0x10, 0xcd, 0xfb, 0x24, 0xdf, 0xfe, 0x2c, 0x20, 0x03, 0xe7, 0x18, 0xf5, + 0xfe, 0x29, 0xc2, 0x8a, 0x1c, 0xe7, 0x21, 0x3b, 0x5c, 0xcb, 0xb6, 0xf1, + 0x28, 0x02, 0xcb, 0xf6, 0xc3, 0x12, 0x27, 0xe7, 0xe6, 0xdf, 0x47, 0xe7, + 0xd6, 0x24, 0x4f, 0x20, 0x01, 0xe7, 0xe7, 0xe5, 0x2a, 0x53, 0x5c, 0x2b, + 0x11, 0xce, 0x00, 0xc5, 0xcd, 0x86, 0x1d, 0xc1, 0x30, 0x02, 0xcf, 0x18, + 0xe5, 0xcd, 0xab, 0x28, 0xe6, 0xdf, 0xb8, 0x20, 0x08, 0xcd, 0xab, 0x28, + 0xd6, 0x24, 0xb9, 0x28, 0x0c, 0xe1, 0x2b, 0x11, 0x00, 0x02, 0xc5, 0xcd, + 0x8b, 0x19, 0xc1, 0x18, 0xd7, 0xa7, 0xcc, 0xab, 0x28, 0xd1, 0xd1, 0xed, + 0x53, 0x5d, 0x5c, 0xcd, 0xab, 0x28, 0xe5, 0xfe, 0x29, 0x28, 0x42, 0x23, + 0x7e, 0xfe, 0x0e, 0x16, 0x40, 0x28, 0x07, 0x2b, 0xcd, 0xab, 0x28, 0x23, + 0x16, 0x00, 0x23, 0xe5, 0xd5, 0xcd, 0xfb, 0x24, 0xf1, 0xfd, 0xae, 0x01, + 0xe6, 0x40, 0x20, 0x2b, 0xe1, 0xeb, 0x2a, 0x65, 0x5c, 0x01, 0x05, 0x00, + 0xed, 0x42, 0x22, 0x65, 0x5c, 0xed, 0xb0, 0xeb, 0x2b, 0xcd, 0xab, 0x28, + 0xfe, 0x29, 0x28, 0x0d, 0xe5, 0xdf, 0xfe, 0x2c, 0x20, 0x0d, 0xe7, 0xe1, + 0xcd, 0xab, 0x28, 0x18, 0xbe, 0xe5, 0xdf, 0xfe, 0x29, 0x28, 0x02, 0xcf, + 0x19, 0xd1, 0xeb, 0x22, 0x5d, 0x5c, 0x2a, 0x0b, 0x5c, 0xe3, 0x22, 0x0b, + 0x5c, 0xd5, 0xe7, 0xe7, 0xcd, 0xfb, 0x24, 0xe1, 0x22, 0x5d, 0x5c, 0xe1, + 0x22, 0x0b, 0x5c, 0xe7, 0xc3, 0x12, 0x27, 0x23, 0x7e, 0xfe, 0x21, 0x38, + 0xfa, 0xc9, 0xfd, 0xcb, 0x01, 0xf6, 0xdf, 0xcd, 0x8d, 0x2c, 0xd2, 0x8a, + 0x1c, 0xe5, 0xe6, 0x1f, 0x4f, 0xe7, 0xe5, 0xfe, 0x28, 0x28, 0x28, 0xcb, + 0xf1, 0xfe, 0x24, 0x28, 0x11, 0xcb, 0xe9, 0xcd, 0x88, 0x2c, 0x30, 0x0f, + 0xcd, 0x88, 0x2c, 0x30, 0x16, 0xcb, 0xb1, 0xe7, 0x18, 0xf6, 0xe7, 0xfd, + 0xcb, 0x01, 0xb6, 0x3a, 0x0c, 0x5c, 0xa7, 0x28, 0x06, 0xcd, 0x30, 0x25, + 0xc2, 0x51, 0x29, 0x41, 0xcd, 0x30, 0x25, 0x20, 0x08, 0x79, 0xe6, 0xe0, + 0xcb, 0xff, 0x4f, 0x18, 0x37, 0x2a, 0x4b, 0x5c, 0x7e, 0xe6, 0x7f, 0x28, + 0x2d, 0xb9, 0x20, 0x22, 0x17, 0x87, 0xf2, 0x3f, 0x29, 0x38, 0x30, 0xd1, + 0xd5, 0xe5, 0x23, 0x1a, 0x13, 0xfe, 0x20, 0x28, 0xfa, 0xf6, 0x20, 0xbe, + 0x28, 0xf4, 0xf6, 0x80, 0xbe, 0x20, 0x06, 0x1a, 0xcd, 0x88, 0x2c, 0x30, + 0x15, 0xe1, 0xc5, 0xcd, 0xb8, 0x19, 0xeb, 0xc1, 0x18, 0xce, 0xcb, 0xf8, + 0xd1, 0xdf, 0xfe, 0x28, 0x28, 0x09, 0xcb, 0xe8, 0x18, 0x0d, 0xd1, 0xd1, + 0xd1, 0xe5, 0xdf, 0xcd, 0x88, 0x2c, 0x30, 0x03, 0xe7, 0x18, 0xf8, 0xe1, + 0xcb, 0x10, 0xcb, 0x70, 0xc9, 0x2a, 0x0b, 0x5c, 0x7e, 0xfe, 0x29, 0xca, + 0xef, 0x28, 0x7e, 0xf6, 0x60, 0x47, 0x23, 0x7e, 0xfe, 0x0e, 0x28, 0x07, + 0x2b, 0xcd, 0xab, 0x28, 0x23, 0xcb, 0xa8, 0x78, 0xb9, 0x28, 0x12, 0x23, + 0x23, 0x23, 0x23, 0x23, 0xcd, 0xab, 0x28, 0xfe, 0x29, 0xca, 0xef, 0x28, + 0xcd, 0xab, 0x28, 0x18, 0xd9, 0xcb, 0x69, 0x20, 0x0c, 0x23, 0xed, 0x5b, + 0x65, 0x5c, 0xcd, 0xc0, 0x33, 0xeb, 0x22, 0x65, 0x5c, 0xd1, 0xd1, 0xaf, + 0x3c, 0xc9, 0xaf, 0x47, 0xcb, 0x79, 0x20, 0x4b, 0xcb, 0x7e, 0x20, 0x0e, + 0x3c, 0x23, 0x4e, 0x23, 0x46, 0x23, 0xeb, 0xcd, 0xb2, 0x2a, 0xdf, 0xc3, + 0x49, 0x2a, 0x23, 0x23, 0x23, 0x46, 0xcb, 0x71, 0x28, 0x0a, 0x05, 0x28, + 0xe8, 0xeb, 0xdf, 0xfe, 0x28, 0x20, 0x61, 0xeb, 0xeb, 0x18, 0x24, 0xe5, + 0xdf, 0xe1, 0xfe, 0x2c, 0x28, 0x20, 0xcb, 0x79, 0x28, 0x52, 0xcb, 0x71, + 0x20, 0x06, 0xfe, 0x29, 0x20, 0x3c, 0xe7, 0xc9, 0xfe, 0x29, 0x28, 0x6c, + 0xfe, 0xcc, 0x20, 0x32, 0xdf, 0x2b, 0x22, 0x5d, 0x5c, 0x18, 0x5e, 0x21, + 0x00, 0x00, 0xe5, 0xe7, 0xe1, 0x79, 0xfe, 0xc0, 0x20, 0x09, 0xdf, 0xfe, + 0x29, 0x28, 0x51, 0xfe, 0xcc, 0x28, 0xe5, 0xc5, 0xe5, 0xcd, 0xee, 0x2a, + 0xe3, 0xeb, 0xcd, 0xcc, 0x2a, 0x38, 0x19, 0x0b, 0xcd, 0xf4, 0x2a, 0x09, + 0xd1, 0xc1, 0x10, 0xb3, 0xcb, 0x79, 0x20, 0x66, 0xe5, 0xcb, 0x71, 0x20, + 0x13, 0x42, 0x4b, 0xdf, 0xfe, 0x29, 0x28, 0x02, 0xcf, 0x02, 0xe7, 0xe1, + 0x11, 0x05, 0x00, 0xcd, 0xf4, 0x2a, 0x09, 0xc9, 0xcd, 0xee, 0x2a, 0xe3, + 0xcd, 0xf4, 0x2a, 0xc1, 0x09, 0x23, 0x42, 0x4b, 0xeb, 0xcd, 0xb1, 0x2a, + 0xdf, 0xfe, 0x29, 0x28, 0x07, 0xfe, 0x2c, 0x20, 0xdb, 0xcd, 0x52, 0x2a, + 0xe7, 0xfe, 0x28, 0x28, 0xf8, 0xfd, 0xcb, 0x01, 0xb6, 0xc9, 0xcd, 0x30, + 0x25, 0xc4, 0xf1, 0x2b, 0xe7, 0xfe, 0x29, 0x28, 0x50, 0xd5, 0xaf, 0xf5, + 0xc5, 0x11, 0x01, 0x00, 0xdf, 0xe1, 0xfe, 0xcc, 0x28, 0x17, 0xf1, 0xcd, + 0xcd, 0x2a, 0xf5, 0x50, 0x59, 0xe5, 0xdf, 0xe1, 0xfe, 0xcc, 0x28, 0x09, + 0xfe, 0x29, 0xc2, 0x8a, 0x1c, 0x62, 0x6b, 0x18, 0x13, 0xe5, 0xe7, 0xe1, + 0xfe, 0x29, 0x28, 0x0c, 0xf1, 0xcd, 0xcd, 0x2a, 0xf5, 0xdf, 0x60, 0x69, + 0xfe, 0x29, 0x20, 0xe6, 0xf1, 0xe3, 0x19, 0x2b, 0xe3, 0xa7, 0xed, 0x52, + 0x01, 0x00, 0x00, 0x38, 0x07, 0x23, 0xa7, 0xfa, 0x20, 0x2a, 0x44, 0x4d, + 0xd1, 0xfd, 0xcb, 0x01, 0xb6, 0xcd, 0x30, 0x25, 0xc8, 0xaf, 0xfd, 0xcb, + 0x01, 0xb6, 0xc5, 0xcd, 0xa9, 0x33, 0xc1, 0x2a, 0x65, 0x5c, 0x77, 0x23, + 0x73, 0x23, 0x72, 0x23, 0x71, 0x23, 0x70, 0x23, 0x22, 0x65, 0x5c, 0xc9, + 0xaf, 0xd5, 0xe5, 0xf5, 0xcd, 0x82, 0x1c, 0xf1, 0xcd, 0x30, 0x25, 0x28, + 0x12, 0xf5, 0xcd, 0x99, 0x1e, 0xd1, 0x78, 0xb1, 0x37, 0x28, 0x05, 0xe1, + 0xe5, 0xa7, 0xed, 0x42, 0x7a, 0xde, 0x00, 0xe1, 0xd1, 0xc9, 0xeb, 0x23, + 0x5e, 0x23, 0x56, 0xc9, 0xcd, 0x30, 0x25, 0xc8, 0xcd, 0xa9, 0x30, 0xda, + 0x15, 0x1f, 0xc9, 0x2a, 0x4d, 0x5c, 0xfd, 0xcb, 0x37, 0x4e, 0x28, 0x5e, + 0x01, 0x05, 0x00, 0x03, 0x23, 0x7e, 0xfe, 0x20, 0x28, 0xfa, 0x30, 0x0b, + 0xfe, 0x10, 0x38, 0x11, 0xfe, 0x16, 0x30, 0x0d, 0x23, 0x18, 0xed, 0xcd, + 0x88, 0x2c, 0x38, 0xe7, 0xfe, 0x24, 0xca, 0xc0, 0x2b, 0x79, 0x2a, 0x59, + 0x5c, 0x2b, 0xcd, 0x55, 0x16, 0x23, 0x23, 0xeb, 0xd5, 0x2a, 0x4d, 0x5c, + 0x1b, 0xd6, 0x06, 0x47, 0x28, 0x11, 0x23, 0x7e, 0xfe, 0x21, 0x38, 0xfa, + 0xf6, 0x20, 0x13, 0x12, 0x10, 0xf4, 0xf6, 0x80, 0x12, 0x3e, 0xc0, 0x2a, + 0x4d, 0x5c, 0xae, 0xf6, 0x20, 0xe1, 0xcd, 0xea, 0x2b, 0xe5, 0xef, 0x02, + 0x38, 0xe1, 0x01, 0x05, 0x00, 0xa7, 0xed, 0x42, 0x18, 0x40, 0xfd, 0xcb, + 0x01, 0x76, 0x28, 0x06, 0x11, 0x06, 0x00, 0x19, 0x18, 0xe7, 0x2a, 0x4d, + 0x5c, 0xed, 0x4b, 0x72, 0x5c, 0xfd, 0xcb, 0x37, 0x46, 0x20, 0x30, 0x78, + 0xb1, 0xc8, 0xe5, 0xf7, 0xd5, 0xc5, 0x54, 0x5d, 0x23, 0x36, 0x20, 0xed, + 0xb8, 0xe5, 0xcd, 0xf1, 0x2b, 0xe1, 0xe3, 0xa7, 0xed, 0x42, 0x09, 0x30, + 0x02, 0x44, 0x4d, 0xe3, 0xeb, 0x78, 0xb1, 0x28, 0x02, 0xed, 0xb0, 0xc1, + 0xd1, 0xe1, 0xeb, 0x78, 0xb1, 0xc8, 0xd5, 0xed, 0xb0, 0xe1, 0xc9, 0x2b, + 0x2b, 0x2b, 0x7e, 0xe5, 0xc5, 0xcd, 0xc6, 0x2b, 0xc1, 0xe1, 0x03, 0x03, + 0x03, 0xc3, 0xe8, 0x19, 0x3e, 0xdf, 0x2a, 0x4d, 0x5c, 0xa6, 0xf5, 0xcd, + 0xf1, 0x2b, 0xeb, 0x09, 0xc5, 0x2b, 0x22, 0x4d, 0x5c, 0x03, 0x03, 0x03, + 0x2a, 0x59, 0x5c, 0x2b, 0xcd, 0x55, 0x16, 0x2a, 0x4d, 0x5c, 0xc1, 0xc5, + 0x03, 0xed, 0xb8, 0xeb, 0x23, 0xc1, 0x70, 0x2b, 0x71, 0xf1, 0x2b, 0x77, + 0x2a, 0x59, 0x5c, 0x2b, 0xc9, 0x2a, 0x65, 0x5c, 0x2b, 0x46, 0x2b, 0x4e, + 0x2b, 0x56, 0x2b, 0x5e, 0x2b, 0x7e, 0x22, 0x65, 0x5c, 0xc9, 0xcd, 0xb2, + 0x28, 0xc2, 0x8a, 0x1c, 0xcd, 0x30, 0x25, 0x20, 0x08, 0xcb, 0xb1, 0xcd, + 0x96, 0x29, 0xcd, 0xee, 0x1b, 0x38, 0x08, 0xc5, 0xcd, 0xb8, 0x19, 0xcd, + 0xe8, 0x19, 0xc1, 0xcb, 0xf9, 0x06, 0x00, 0xc5, 0x21, 0x01, 0x00, 0xcb, + 0x71, 0x20, 0x02, 0x2e, 0x05, 0xeb, 0xe7, 0x26, 0xff, 0xcd, 0xcc, 0x2a, + 0xda, 0x20, 0x2a, 0xe1, 0xc5, 0x24, 0xe5, 0x60, 0x69, 0xcd, 0xf4, 0x2a, + 0xeb, 0xdf, 0xfe, 0x2c, 0x28, 0xe8, 0xfe, 0x29, 0x20, 0xbb, 0xe7, 0xc1, + 0x79, 0x68, 0x26, 0x00, 0x23, 0x23, 0x29, 0x19, 0xda, 0x15, 0x1f, 0xd5, + 0xc5, 0xe5, 0x44, 0x4d, 0x2a, 0x59, 0x5c, 0x2b, 0xcd, 0x55, 0x16, 0x23, + 0x77, 0xc1, 0x0b, 0x0b, 0x0b, 0x23, 0x71, 0x23, 0x70, 0xc1, 0x78, 0x23, + 0x77, 0x62, 0x6b, 0x1b, 0x36, 0x00, 0xcb, 0x71, 0x28, 0x02, 0x36, 0x20, + 0xc1, 0xed, 0xb8, 0xc1, 0x70, 0x2b, 0x71, 0x2b, 0x3d, 0x20, 0xf8, 0xc9, + 0xcd, 0x1b, 0x2d, 0x3f, 0xd8, 0xfe, 0x41, 0x3f, 0xd0, 0xfe, 0x5b, 0xd8, + 0xfe, 0x61, 0x3f, 0xd0, 0xfe, 0x7b, 0xc9, 0xfe, 0xc4, 0x20, 0x19, 0x11, + 0x00, 0x00, 0xe7, 0xd6, 0x31, 0xce, 0x00, 0x20, 0x0a, 0xeb, 0x3f, 0xed, + 0x6a, 0xda, 0xad, 0x31, 0xeb, 0x18, 0xef, 0x42, 0x4b, 0xc3, 0x2b, 0x2d, + 0xfe, 0x2e, 0x28, 0x0f, 0xcd, 0x3b, 0x2d, 0xfe, 0x2e, 0x20, 0x28, 0xe7, + 0xcd, 0x1b, 0x2d, 0x38, 0x22, 0x18, 0x0a, 0xe7, 0xcd, 0x1b, 0x2d, 0xda, + 0x8a, 0x1c, 0xef, 0xa0, 0x38, 0xef, 0xa1, 0xc0, 0x02, 0x38, 0xdf, 0xcd, + 0x22, 0x2d, 0x38, 0x0b, 0xef, 0xe0, 0xa4, 0x05, 0xc0, 0x04, 0x0f, 0x38, + 0xe7, 0x18, 0xef, 0xfe, 0x45, 0x28, 0x03, 0xfe, 0x65, 0xc0, 0x06, 0xff, + 0xe7, 0xfe, 0x2b, 0x28, 0x05, 0xfe, 0x2d, 0x20, 0x02, 0x04, 0xe7, 0xcd, + 0x1b, 0x2d, 0x38, 0xcb, 0xc5, 0xcd, 0x3b, 0x2d, 0xcd, 0xd5, 0x2d, 0xc1, + 0xda, 0xad, 0x31, 0xa7, 0xfa, 0xad, 0x31, 0x04, 0x28, 0x02, 0xed, 0x44, + 0xc3, 0x4f, 0x2d, 0xfe, 0x30, 0xd8, 0xfe, 0x3a, 0x3f, 0xc9, 0xcd, 0x1b, + 0x2d, 0xd8, 0xd6, 0x30, 0x4f, 0x06, 0x00, 0xfd, 0x21, 0x3a, 0x5c, 0xaf, + 0x5f, 0x51, 0x48, 0x47, 0xcd, 0xb6, 0x2a, 0xef, 0x38, 0xa7, 0xc9, 0xf5, + 0xef, 0xa0, 0x38, 0xf1, 0xcd, 0x22, 0x2d, 0xd8, 0xef, 0x01, 0xa4, 0x04, + 0x0f, 0x38, 0xcd, 0x74, 0x00, 0x18, 0xf1, 0x07, 0x0f, 0x30, 0x02, 0x2f, + 0x3c, 0xf5, 0x21, 0x92, 0x5c, 0xcd, 0x0b, 0x35, 0xef, 0xa4, 0x38, 0xf1, + 0xcb, 0x3f, 0x30, 0x0d, 0xf5, 0xef, 0xc1, 0xe0, 0x00, 0x04, 0x04, 0x33, + 0x02, 0x05, 0xe1, 0x38, 0xf1, 0x28, 0x08, 0xf5, 0xef, 0x31, 0x04, 0x38, + 0xf1, 0x18, 0xe5, 0xef, 0x02, 0x38, 0xc9, 0x23, 0x4e, 0x23, 0x7e, 0xa9, + 0x91, 0x5f, 0x23, 0x7e, 0x89, 0xa9, 0x57, 0xc9, 0x0e, 0x00, 0xe5, 0x36, + 0x00, 0x23, 0x71, 0x23, 0x7b, 0xa9, 0x91, 0x77, 0x23, 0x7a, 0x89, 0xa9, + 0x77, 0x23, 0x36, 0x00, 0xe1, 0xc9, 0xef, 0x38, 0x7e, 0xa7, 0x28, 0x05, + 0xef, 0xa2, 0x0f, 0x27, 0x38, 0xef, 0x02, 0x38, 0xe5, 0xd5, 0xeb, 0x46, + 0xcd, 0x7f, 0x2d, 0xaf, 0x90, 0xcb, 0x79, 0x42, 0x4b, 0x7b, 0xd1, 0xe1, + 0xc9, 0x57, 0x17, 0x9f, 0x5f, 0x4f, 0xaf, 0x47, 0xcd, 0xb6, 0x2a, 0xef, + 0x34, 0xef, 0x1a, 0x20, 0x9a, 0x85, 0x04, 0x27, 0x38, 0xcd, 0xa2, 0x2d, + 0xd8, 0xf5, 0x05, 0x04, 0x28, 0x03, 0xf1, 0x37, 0xc9, 0xf1, 0xc9, 0xef, + 0x31, 0x36, 0x00, 0x0b, 0x31, 0x37, 0x00, 0x0d, 0x02, 0x38, 0x3e, 0x30, + 0xd7, 0xc9, 0x2a, 0x38, 0x3e, 0x2d, 0xd7, 0xef, 0xa0, 0xc3, 0xc4, 0xc5, + 0x02, 0x38, 0xd9, 0xe5, 0xd9, 0xef, 0x31, 0x27, 0xc2, 0x03, 0xe2, 0x01, + 0xc2, 0x02, 0x38, 0x7e, 0xa7, 0x20, 0x47, 0xcd, 0x7f, 0x2d, 0x06, 0x10, + 0x7a, 0xa7, 0x20, 0x06, 0xb3, 0x28, 0x09, 0x53, 0x06, 0x08, 0xd5, 0xd9, + 0xd1, 0xd9, 0x18, 0x57, 0xef, 0xe2, 0x38, 0x7e, 0xd6, 0x7e, 0xcd, 0xc1, + 0x2d, 0x57, 0x3a, 0xac, 0x5c, 0x92, 0x32, 0xac, 0x5c, 0x7a, 0xcd, 0x4f, + 0x2d, 0xef, 0x31, 0x27, 0xc1, 0x03, 0xe1, 0x38, 0xcd, 0xd5, 0x2d, 0xe5, + 0x32, 0xa1, 0x5c, 0x3d, 0x17, 0x9f, 0x3c, 0x21, 0xab, 0x5c, 0x77, 0x23, + 0x86, 0x77, 0xe1, 0xc3, 0xcf, 0x2e, 0xd6, 0x80, 0xfe, 0x1c, 0x38, 0x13, + 0xcd, 0xc1, 0x2d, 0xd6, 0x07, 0x47, 0x21, 0xac, 0x5c, 0x86, 0x77, 0x78, + 0xed, 0x44, 0xcd, 0x4f, 0x2d, 0x18, 0x92, 0xeb, 0xcd, 0xba, 0x2f, 0xd9, + 0xcb, 0xfa, 0x7d, 0xd9, 0xd6, 0x80, 0x47, 0xcb, 0x23, 0xcb, 0x12, 0xd9, + 0xcb, 0x13, 0xcb, 0x12, 0xd9, 0x21, 0xaa, 0x5c, 0x0e, 0x05, 0x7e, 0x8f, + 0x27, 0x77, 0x2b, 0x0d, 0x20, 0xf8, 0x10, 0xe7, 0xaf, 0x21, 0xa6, 0x5c, + 0x11, 0xa1, 0x5c, 0x06, 0x09, 0xed, 0x6f, 0x0e, 0xff, 0xed, 0x6f, 0x20, + 0x04, 0x0d, 0x0c, 0x20, 0x0a, 0x12, 0x13, 0xfd, 0x34, 0x71, 0xfd, 0x34, + 0x72, 0x0e, 0x00, 0xcb, 0x40, 0x28, 0x01, 0x23, 0x10, 0xe7, 0x3a, 0xab, + 0x5c, 0xd6, 0x09, 0x38, 0x0a, 0xfd, 0x35, 0x71, 0x3e, 0x04, 0xfd, 0xbe, + 0x6f, 0x18, 0x41, 0xef, 0x02, 0xe2, 0x38, 0xeb, 0xcd, 0xba, 0x2f, 0xd9, + 0x3e, 0x80, 0x95, 0x2e, 0x00, 0xcb, 0xfa, 0xd9, 0xcd, 0xdd, 0x2f, 0xfd, + 0x7e, 0x71, 0xfe, 0x08, 0x38, 0x06, 0xd9, 0xcb, 0x12, 0xd9, 0x18, 0x20, + 0x01, 0x00, 0x02, 0x7b, 0xcd, 0x8b, 0x2f, 0x5f, 0x7a, 0xcd, 0x8b, 0x2f, + 0x57, 0xc5, 0xd9, 0xc1, 0x10, 0xf1, 0x21, 0xa1, 0x5c, 0x79, 0xfd, 0x4e, + 0x71, 0x09, 0x77, 0xfd, 0x34, 0x71, 0x18, 0xd3, 0xf5, 0x21, 0xa1, 0x5c, + 0xfd, 0x4e, 0x71, 0x06, 0x00, 0x09, 0x41, 0xf1, 0x2b, 0x7e, 0xce, 0x00, + 0x77, 0xa7, 0x28, 0x05, 0xfe, 0x0a, 0x3f, 0x30, 0x08, 0x10, 0xf1, 0x36, + 0x01, 0x04, 0xfd, 0x34, 0x72, 0xfd, 0x70, 0x71, 0xef, 0x02, 0x38, 0xd9, + 0xe1, 0xd9, 0xed, 0x4b, 0xab, 0x5c, 0x21, 0xa1, 0x5c, 0x78, 0xfe, 0x09, + 0x38, 0x04, 0xfe, 0xfc, 0x38, 0x26, 0xa7, 0xcc, 0xef, 0x15, 0xaf, 0x90, + 0xfa, 0x52, 0x2f, 0x47, 0x18, 0x0c, 0x79, 0xa7, 0x28, 0x03, 0x7e, 0x23, + 0x0d, 0xcd, 0xef, 0x15, 0x10, 0xf4, 0x79, 0xa7, 0xc8, 0x04, 0x3e, 0x2e, + 0xd7, 0x3e, 0x30, 0x10, 0xfb, 0x41, 0x18, 0xe6, 0x50, 0x15, 0x06, 0x01, + 0xcd, 0x4a, 0x2f, 0x3e, 0x45, 0xd7, 0x4a, 0x79, 0xa7, 0xf2, 0x83, 0x2f, + 0xed, 0x44, 0x4f, 0x3e, 0x2d, 0x18, 0x02, 0x3e, 0x2b, 0xd7, 0x06, 0x00, + 0xc3, 0x1b, 0x1a, 0xd5, 0x6f, 0x26, 0x00, 0x5d, 0x54, 0x29, 0x29, 0x19, + 0x29, 0x59, 0x19, 0x4c, 0x7d, 0xd1, 0xc9, 0x7e, 0x36, 0x00, 0xa7, 0xc8, + 0x23, 0xcb, 0x7e, 0xcb, 0xfe, 0x2b, 0xc8, 0xc5, 0x01, 0x05, 0x00, 0x09, + 0x41, 0x4f, 0x37, 0x2b, 0x7e, 0x2f, 0xce, 0x00, 0x77, 0x10, 0xf8, 0x79, + 0xc1, 0xc9, 0xe5, 0xf5, 0x4e, 0x23, 0x46, 0x77, 0x23, 0x79, 0x4e, 0xc5, + 0x23, 0x4e, 0x23, 0x46, 0xeb, 0x57, 0x5e, 0xd5, 0x23, 0x56, 0x23, 0x5e, + 0xd5, 0xd9, 0xd1, 0xe1, 0xc1, 0xd9, 0x23, 0x56, 0x23, 0x5e, 0xf1, 0xe1, + 0xc9, 0xa7, 0xc8, 0xfe, 0x21, 0x30, 0x16, 0xc5, 0x47, 0xd9, 0xcb, 0x2d, + 0xcb, 0x1a, 0xcb, 0x1b, 0xd9, 0xcb, 0x1a, 0xcb, 0x1b, 0x10, 0xf2, 0xc1, + 0xd0, 0xcd, 0x04, 0x30, 0xc0, 0xd9, 0xaf, 0x2e, 0x00, 0x57, 0x5d, 0xd9, + 0x11, 0x00, 0x00, 0xc9, 0x1c, 0xc0, 0x14, 0xc0, 0xd9, 0x1c, 0x20, 0x01, + 0x14, 0xd9, 0xc9, 0xeb, 0xcd, 0x6e, 0x34, 0xeb, 0x1a, 0xb6, 0x20, 0x26, + 0xd5, 0x23, 0xe5, 0x23, 0x5e, 0x23, 0x56, 0x23, 0x23, 0x23, 0x7e, 0x23, + 0x4e, 0x23, 0x46, 0xe1, 0xeb, 0x09, 0xeb, 0x8e, 0x0f, 0xce, 0x00, 0x20, + 0x0b, 0x9f, 0x77, 0x23, 0x73, 0x23, 0x72, 0x2b, 0x2b, 0x2b, 0xd1, 0xc9, + 0x2b, 0xd1, 0xcd, 0x93, 0x32, 0xd9, 0xe5, 0xd9, 0xd5, 0xe5, 0xcd, 0x9b, + 0x2f, 0x47, 0xeb, 0xcd, 0x9b, 0x2f, 0x4f, 0xb8, 0x30, 0x03, 0x78, 0x41, + 0xeb, 0xf5, 0x90, 0xcd, 0xba, 0x2f, 0xcd, 0xdd, 0x2f, 0xf1, 0xe1, 0x77, + 0xe5, 0x68, 0x61, 0x19, 0xd9, 0xeb, 0xed, 0x4a, 0xeb, 0x7c, 0x8d, 0x6f, + 0x1f, 0xad, 0xd9, 0xeb, 0xe1, 0x1f, 0x30, 0x08, 0x3e, 0x01, 0xcd, 0xdd, + 0x2f, 0x34, 0x28, 0x23, 0xd9, 0x7d, 0xe6, 0x80, 0xd9, 0x23, 0x77, 0x2b, + 0x28, 0x1f, 0x7b, 0xed, 0x44, 0x3f, 0x5f, 0x7a, 0x2f, 0xce, 0x00, 0x57, + 0xd9, 0x7b, 0x2f, 0xce, 0x00, 0x5f, 0x7a, 0x2f, 0xce, 0x00, 0x30, 0x07, + 0x1f, 0xd9, 0x34, 0xca, 0xad, 0x31, 0xd9, 0x57, 0xd9, 0xaf, 0xc3, 0x55, + 0x31, 0xc5, 0x06, 0x10, 0x7c, 0x4d, 0x21, 0x00, 0x00, 0x29, 0x38, 0x0a, + 0xcb, 0x11, 0x17, 0x30, 0x03, 0x19, 0x38, 0x02, 0x10, 0xf3, 0xc1, 0xc9, + 0xcd, 0xe9, 0x34, 0xd8, 0x23, 0xae, 0xcb, 0xfe, 0x2b, 0xc9, 0x1a, 0xb6, + 0x20, 0x22, 0xd5, 0xe5, 0xd5, 0xcd, 0x7f, 0x2d, 0xeb, 0xe3, 0x41, 0xcd, + 0x7f, 0x2d, 0x78, 0xa9, 0x4f, 0xe1, 0xcd, 0xa9, 0x30, 0xeb, 0xe1, 0x38, + 0x0a, 0x7a, 0xb3, 0x20, 0x01, 0x4f, 0xcd, 0x8e, 0x2d, 0xd1, 0xc9, 0xd1, + 0xcd, 0x93, 0x32, 0xaf, 0xcd, 0xc0, 0x30, 0xd8, 0xd9, 0xe5, 0xd9, 0xd5, + 0xeb, 0xcd, 0xc0, 0x30, 0xeb, 0x38, 0x5a, 0xe5, 0xcd, 0xba, 0x2f, 0x78, + 0xa7, 0xed, 0x62, 0xd9, 0xe5, 0xed, 0x62, 0xd9, 0x06, 0x21, 0x18, 0x11, + 0x30, 0x05, 0x19, 0xd9, 0xed, 0x5a, 0xd9, 0xd9, 0xcb, 0x1c, 0xcb, 0x1d, + 0xd9, 0xcb, 0x1c, 0xcb, 0x1d, 0xd9, 0xcb, 0x18, 0xcb, 0x19, 0xd9, 0xcb, + 0x19, 0x1f, 0x10, 0xe4, 0xeb, 0xd9, 0xeb, 0xd9, 0xc1, 0xe1, 0x78, 0x81, + 0x20, 0x01, 0xa7, 0x3d, 0x3f, 0x17, 0x3f, 0x1f, 0xf2, 0x46, 0x31, 0x30, + 0x68, 0xa7, 0x3c, 0x20, 0x08, 0x38, 0x06, 0xd9, 0xcb, 0x7a, 0xd9, 0x20, + 0x5c, 0x77, 0xd9, 0x78, 0xd9, 0x30, 0x15, 0x7e, 0xa7, 0x3e, 0x80, 0x28, + 0x01, 0xaf, 0xd9, 0xa2, 0xcd, 0xfb, 0x2f, 0x07, 0x77, 0x38, 0x2e, 0x23, + 0x77, 0x2b, 0x18, 0x29, 0x06, 0x20, 0xd9, 0xcb, 0x7a, 0xd9, 0x20, 0x12, + 0x07, 0xcb, 0x13, 0xcb, 0x12, 0xd9, 0xcb, 0x13, 0xcb, 0x12, 0xd9, 0x35, + 0x28, 0xd7, 0x10, 0xea, 0x18, 0xd7, 0x17, 0x30, 0x0c, 0xcd, 0x04, 0x30, + 0x20, 0x07, 0xd9, 0x16, 0x80, 0xd9, 0x34, 0x28, 0x18, 0xe5, 0x23, 0xd9, + 0xd5, 0xd9, 0xc1, 0x78, 0x17, 0xcb, 0x16, 0x1f, 0x77, 0x23, 0x71, 0x23, + 0x72, 0x23, 0x73, 0xe1, 0xd1, 0xd9, 0xe1, 0xd9, 0xc9, 0xcf, 0x05, 0xcd, + 0x93, 0x32, 0xeb, 0xaf, 0xcd, 0xc0, 0x30, 0x38, 0xf4, 0xeb, 0xcd, 0xc0, + 0x30, 0xd8, 0xd9, 0xe5, 0xd9, 0xd5, 0xe5, 0xcd, 0xba, 0x2f, 0xd9, 0xe5, + 0x60, 0x69, 0xd9, 0x61, 0x68, 0xaf, 0x06, 0xdf, 0x18, 0x10, 0x17, 0xcb, + 0x11, 0xd9, 0xcb, 0x11, 0xcb, 0x10, 0xd9, 0x29, 0xd9, 0xed, 0x6a, 0xd9, + 0x38, 0x10, 0xed, 0x52, 0xd9, 0xed, 0x52, 0xd9, 0x30, 0x0f, 0x19, 0xd9, + 0xed, 0x5a, 0xd9, 0xa7, 0x18, 0x08, 0xa7, 0xed, 0x52, 0xd9, 0xed, 0x52, + 0xd9, 0x37, 0x04, 0xfa, 0xd2, 0x31, 0xf5, 0x28, 0xe1, 0x5f, 0x51, 0xd9, + 0x59, 0x50, 0xf1, 0xcb, 0x18, 0xf1, 0xcb, 0x18, 0xd9, 0xc1, 0xe1, 0x78, + 0x91, 0xc3, 0x3d, 0x31, 0x7e, 0xa7, 0xc8, 0xfe, 0x81, 0x30, 0x06, 0x36, + 0x00, 0x3e, 0x20, 0x18, 0x51, 0xfe, 0x91, 0x20, 0x1a, 0x23, 0x23, 0x23, + 0x3e, 0x80, 0xa6, 0x2b, 0xb6, 0x2b, 0x20, 0x03, 0x3e, 0x80, 0xae, 0x2b, + 0x20, 0x36, 0x77, 0x23, 0x36, 0xff, 0x2b, 0x3e, 0x18, 0x18, 0x33, 0x30, + 0x2c, 0xd5, 0x2f, 0xc6, 0x91, 0x23, 0x56, 0x23, 0x5e, 0x2b, 0x2b, 0x0e, + 0x00, 0xcb, 0x7a, 0x28, 0x01, 0x0d, 0xcb, 0xfa, 0x06, 0x08, 0x90, 0x80, + 0x38, 0x04, 0x5a, 0x16, 0x00, 0x90, 0x28, 0x07, 0x47, 0xcb, 0x3a, 0xcb, + 0x1b, 0x10, 0xfa, 0xcd, 0x8e, 0x2d, 0xd1, 0xc9, 0x7e, 0xd6, 0xa0, 0xf0, + 0xed, 0x44, 0xd5, 0xeb, 0x2b, 0x47, 0xcb, 0x38, 0xcb, 0x38, 0xcb, 0x38, + 0x28, 0x05, 0x36, 0x00, 0x2b, 0x10, 0xfb, 0xe6, 0x07, 0x28, 0x09, 0x47, + 0x3e, 0xff, 0xcb, 0x27, 0x10, 0xfc, 0xa6, 0x77, 0xeb, 0xd1, 0xc9, 0xcd, + 0x96, 0x32, 0xeb, 0x7e, 0xa7, 0xc0, 0xd5, 0xcd, 0x7f, 0x2d, 0xaf, 0x23, + 0x77, 0x2b, 0x77, 0x06, 0x91, 0x7a, 0xa7, 0x20, 0x08, 0xb3, 0x42, 0x28, + 0x10, 0x53, 0x58, 0x06, 0x89, 0xeb, 0x05, 0x29, 0x30, 0xfc, 0xcb, 0x09, + 0xcb, 0x1c, 0xcb, 0x1d, 0xeb, 0x2b, 0x73, 0x2b, 0x72, 0x2b, 0x70, 0xd1, + 0xc9, 0x00, 0xb0, 0x00, 0x40, 0xb0, 0x00, 0x01, 0x30, 0x00, 0xf1, 0x49, + 0x0f, 0xda, 0xa2, 0x40, 0xb0, 0x00, 0x0a, 0x8f, 0x36, 0x3c, 0x34, 0xa1, + 0x33, 0x0f, 0x30, 0xca, 0x30, 0xaf, 0x31, 0x51, 0x38, 0x1b, 0x35, 0x24, + 0x35, 0x3b, 0x35, 0x3b, 0x35, 0x3b, 0x35, 0x3b, 0x35, 0x3b, 0x35, 0x3b, + 0x35, 0x14, 0x30, 0x2d, 0x35, 0x3b, 0x35, 0x3b, 0x35, 0x3b, 0x35, 0x3b, + 0x35, 0x3b, 0x35, 0x3b, 0x35, 0x9c, 0x35, 0xde, 0x35, 0xbc, 0x34, 0x45, + 0x36, 0x6e, 0x34, 0x69, 0x36, 0xde, 0x35, 0x74, 0x36, 0xb5, 0x37, 0xaa, + 0x37, 0xda, 0x37, 0x33, 0x38, 0x43, 0x38, 0xe2, 0x37, 0x13, 0x37, 0xc4, + 0x36, 0xaf, 0x36, 0x4a, 0x38, 0x92, 0x34, 0x6a, 0x34, 0xac, 0x34, 0xa5, + 0x34, 0xb3, 0x34, 0x1f, 0x36, 0xc9, 0x35, 0x01, 0x35, 0xc0, 0x33, 0xa0, + 0x36, 0x86, 0x36, 0xc6, 0x33, 0x7a, 0x36, 0x06, 0x35, 0xf9, 0x34, 0x9b, + 0x36, 0x83, 0x37, 0x14, 0x32, 0xa2, 0x33, 0x4f, 0x2d, 0x97, 0x32, 0x49, + 0x34, 0x1b, 0x34, 0x2d, 0x34, 0x0f, 0x34, 0xcd, 0xbf, 0x35, 0x78, 0x32, + 0x67, 0x5c, 0xd9, 0xe3, 0xd9, 0xed, 0x53, 0x65, 0x5c, 0xd9, 0x7e, 0x23, + 0xe5, 0xa7, 0xf2, 0x80, 0x33, 0x57, 0xe6, 0x60, 0x0f, 0x0f, 0x0f, 0x0f, + 0xc6, 0x7c, 0x6f, 0x7a, 0xe6, 0x1f, 0x18, 0x0e, 0xfe, 0x18, 0x30, 0x08, + 0xd9, 0x01, 0xfb, 0xff, 0x54, 0x5d, 0x09, 0xd9, 0x07, 0x6f, 0x11, 0xd7, + 0x32, 0x26, 0x00, 0x19, 0x5e, 0x23, 0x56, 0x21, 0x65, 0x33, 0xe3, 0xd5, + 0xd9, 0xed, 0x4b, 0x66, 0x5c, 0xc9, 0xf1, 0x3a, 0x67, 0x5c, 0xd9, 0x18, + 0xc3, 0xd5, 0xe5, 0x01, 0x05, 0x00, 0xcd, 0x05, 0x1f, 0xe1, 0xd1, 0xc9, + 0xed, 0x5b, 0x65, 0x5c, 0xcd, 0xc0, 0x33, 0xed, 0x53, 0x65, 0x5c, 0xc9, + 0xcd, 0xa9, 0x33, 0xed, 0xb0, 0xc9, 0x62, 0x6b, 0xcd, 0xa9, 0x33, 0xd9, + 0xe5, 0xd9, 0xe3, 0xc5, 0x7e, 0xe6, 0xc0, 0x07, 0x07, 0x4f, 0x0c, 0x7e, + 0xe6, 0x3f, 0x20, 0x02, 0x23, 0x7e, 0xc6, 0x50, 0x12, 0x3e, 0x05, 0x91, + 0x23, 0x13, 0x06, 0x00, 0xed, 0xb0, 0xc1, 0xe3, 0xd9, 0xe1, 0xd9, 0x47, + 0xaf, 0x05, 0xc8, 0x12, 0x13, 0x18, 0xfa, 0xa7, 0xc8, 0xf5, 0xd5, 0x11, + 0x00, 0x00, 0xcd, 0xc8, 0x33, 0xd1, 0xf1, 0x3d, 0x18, 0xf2, 0x4f, 0x07, + 0x07, 0x81, 0x4f, 0x06, 0x00, 0x09, 0xc9, 0xd5, 0x2a, 0x68, 0x5c, 0xcd, + 0x06, 0x34, 0xcd, 0xc0, 0x33, 0xe1, 0xc9, 0x62, 0x6b, 0xd9, 0xe5, 0x21, + 0xc5, 0x32, 0xd9, 0xcd, 0xf7, 0x33, 0xcd, 0xc8, 0x33, 0xd9, 0xe1, 0xd9, + 0xc9, 0xe5, 0xeb, 0x2a, 0x68, 0x5c, 0xcd, 0x06, 0x34, 0xeb, 0xcd, 0xc0, + 0x33, 0xeb, 0xe1, 0xc9, 0x06, 0x05, 0x1a, 0x4e, 0xeb, 0x12, 0x71, 0x23, + 0x13, 0x10, 0xf7, 0xeb, 0xc9, 0x47, 0xcd, 0x5e, 0x33, 0x31, 0x0f, 0xc0, + 0x02, 0xa0, 0xc2, 0x31, 0xe0, 0x04, 0xe2, 0xc1, 0x03, 0x38, 0xcd, 0xc6, + 0x33, 0xcd, 0x62, 0x33, 0x0f, 0x01, 0xc2, 0x02, 0x35, 0xee, 0xe1, 0x03, + 0x38, 0xc9, 0x06, 0xff, 0x18, 0x06, 0xcd, 0xe9, 0x34, 0xd8, 0x06, 0x00, + 0x7e, 0xa7, 0x28, 0x0b, 0x23, 0x78, 0xe6, 0x80, 0xb6, 0x17, 0x3f, 0x1f, + 0x77, 0x2b, 0xc9, 0xd5, 0xe5, 0xcd, 0x7f, 0x2d, 0xe1, 0x78, 0xb1, 0x2f, + 0x4f, 0xcd, 0x8e, 0x2d, 0xd1, 0xc9, 0xcd, 0xe9, 0x34, 0xd8, 0xd5, 0x11, + 0x01, 0x00, 0x23, 0xcb, 0x16, 0x2b, 0x9f, 0x4f, 0xcd, 0x8e, 0x2d, 0xd1, + 0xc9, 0xcd, 0x99, 0x1e, 0xed, 0x78, 0x18, 0x04, 0xcd, 0x99, 0x1e, 0x0a, + 0xc3, 0x28, 0x2d, 0xcd, 0x99, 0x1e, 0x21, 0x2b, 0x2d, 0xe5, 0xc5, 0xc9, + 0xcd, 0xf1, 0x2b, 0x0b, 0x78, 0xb1, 0x20, 0x23, 0x1a, 0xcd, 0x8d, 0x2c, + 0x38, 0x09, 0xd6, 0x90, 0x38, 0x19, 0xfe, 0x15, 0x30, 0x15, 0x3c, 0x3d, + 0x87, 0x87, 0x87, 0xfe, 0xa8, 0x30, 0x0c, 0xed, 0x4b, 0x7b, 0x5c, 0x81, + 0x4f, 0x30, 0x01, 0x04, 0xc3, 0x2b, 0x2d, 0xcf, 0x09, 0xe5, 0xc5, 0x47, + 0x7e, 0x23, 0xb6, 0x23, 0xb6, 0x23, 0xb6, 0x78, 0xc1, 0xe1, 0xc0, 0x37, + 0xc9, 0xcd, 0xe9, 0x34, 0xd8, 0x3e, 0xff, 0x18, 0x06, 0xcd, 0xe9, 0x34, + 0x18, 0x05, 0xaf, 0x23, 0xae, 0x2b, 0x07, 0xe5, 0x3e, 0x00, 0x77, 0x23, + 0x77, 0x23, 0x17, 0x77, 0x1f, 0x23, 0x77, 0x23, 0x77, 0xe1, 0xc9, 0xeb, + 0xcd, 0xe9, 0x34, 0xeb, 0xd8, 0x37, 0x18, 0xe7, 0xeb, 0xcd, 0xe9, 0x34, + 0xeb, 0xd0, 0xa7, 0x18, 0xde, 0xeb, 0xcd, 0xe9, 0x34, 0xeb, 0xd0, 0xd5, + 0x1b, 0xaf, 0x12, 0x1b, 0x12, 0xd1, 0xc9, 0x78, 0xd6, 0x08, 0xcb, 0x57, + 0x20, 0x01, 0x3d, 0x0f, 0x30, 0x08, 0xf5, 0xe5, 0xcd, 0x3c, 0x34, 0xd1, + 0xeb, 0xf1, 0xcb, 0x57, 0x20, 0x07, 0x0f, 0xf5, 0xcd, 0x0f, 0x30, 0x18, + 0x33, 0x0f, 0xf5, 0xcd, 0xf1, 0x2b, 0xd5, 0xc5, 0xcd, 0xf1, 0x2b, 0xe1, + 0x7c, 0xb5, 0xe3, 0x78, 0x20, 0x0b, 0xb1, 0xc1, 0x28, 0x04, 0xf1, 0x3f, + 0x18, 0x16, 0xf1, 0x18, 0x13, 0xb1, 0x28, 0x0d, 0x1a, 0x96, 0x38, 0x09, + 0x20, 0xed, 0x0b, 0x13, 0x23, 0xe3, 0x2b, 0x18, 0xdf, 0xc1, 0xf1, 0xa7, + 0xf5, 0xef, 0xa0, 0x38, 0xf1, 0xf5, 0xdc, 0x01, 0x35, 0xf1, 0xf5, 0xd4, + 0xf9, 0x34, 0xf1, 0x0f, 0xd4, 0x01, 0x35, 0xc9, 0xcd, 0xf1, 0x2b, 0xd5, + 0xc5, 0xcd, 0xf1, 0x2b, 0xe1, 0xe5, 0xd5, 0xc5, 0x09, 0x44, 0x4d, 0xf7, + 0xcd, 0xb2, 0x2a, 0xc1, 0xe1, 0x78, 0xb1, 0x28, 0x02, 0xed, 0xb0, 0xc1, + 0xe1, 0x78, 0xb1, 0x28, 0x02, 0xed, 0xb0, 0x2a, 0x65, 0x5c, 0x11, 0xfb, + 0xff, 0xe5, 0x19, 0xd1, 0xc9, 0xcd, 0xd5, 0x2d, 0x38, 0x0e, 0x20, 0x0c, + 0xf5, 0x01, 0x01, 0x00, 0xf7, 0xf1, 0x12, 0xcd, 0xb2, 0x2a, 0xeb, 0xc9, + 0xcf, 0x0a, 0x2a, 0x5d, 0x5c, 0xe5, 0x78, 0xc6, 0xe3, 0x9f, 0xf5, 0xcd, + 0xf1, 0x2b, 0xd5, 0x03, 0xf7, 0xe1, 0xed, 0x53, 0x5d, 0x5c, 0xd5, 0xed, + 0xb0, 0xeb, 0x2b, 0x36, 0x0d, 0xfd, 0xcb, 0x01, 0xbe, 0xcd, 0xfb, 0x24, + 0xdf, 0xfe, 0x0d, 0x20, 0x07, 0xe1, 0xf1, 0xfd, 0xae, 0x01, 0xe6, 0x40, + 0xc2, 0x8a, 0x1c, 0x22, 0x5d, 0x5c, 0xfd, 0xcb, 0x01, 0xfe, 0xcd, 0xfb, + 0x24, 0xe1, 0x22, 0x5d, 0x5c, 0x18, 0xa0, 0x01, 0x01, 0x00, 0xf7, 0x22, + 0x5b, 0x5c, 0xe5, 0x2a, 0x51, 0x5c, 0xe5, 0x3e, 0xff, 0xcd, 0x01, 0x16, + 0xcd, 0xe3, 0x2d, 0xe1, 0xcd, 0x15, 0x16, 0xd1, 0x2a, 0x5b, 0x5c, 0xa7, + 0xed, 0x52, 0x44, 0x4d, 0xcd, 0xb2, 0x2a, 0xeb, 0xc9, 0xcd, 0x94, 0x1e, + 0xfe, 0x10, 0xd2, 0x9f, 0x1e, 0x2a, 0x51, 0x5c, 0xe5, 0xcd, 0x01, 0x16, + 0xcd, 0xe6, 0x15, 0x01, 0x00, 0x00, 0x30, 0x03, 0x0c, 0xf7, 0x12, 0xcd, + 0xb2, 0x2a, 0xe1, 0xcd, 0x15, 0x16, 0xc3, 0xbf, 0x35, 0xcd, 0xf1, 0x2b, + 0x78, 0xb1, 0x28, 0x01, 0x1a, 0xc3, 0x28, 0x2d, 0xcd, 0xf1, 0x2b, 0xc3, + 0x2b, 0x2d, 0xd9, 0xe5, 0x21, 0x67, 0x5c, 0x35, 0xe1, 0x20, 0x04, 0x23, + 0xd9, 0xc9, 0xd9, 0x5e, 0x7b, 0x17, 0x9f, 0x57, 0x19, 0xd9, 0xc9, 0x13, + 0x13, 0x1a, 0x1b, 0x1b, 0xa7, 0x20, 0xef, 0xd9, 0x23, 0xd9, 0xc9, 0xf1, + 0xd9, 0xe3, 0xd9, 0xc9, 0xef, 0xc0, 0x02, 0x31, 0xe0, 0x05, 0x27, 0xe0, + 0x01, 0xc0, 0x04, 0x03, 0xe0, 0x38, 0xc9, 0xef, 0x31, 0x36, 0x00, 0x04, + 0x3a, 0x38, 0xc9, 0x31, 0x3a, 0xc0, 0x03, 0xe0, 0x01, 0x30, 0x00, 0x03, + 0xa1, 0x03, 0x38, 0xc9, 0xef, 0x3d, 0x34, 0xf1, 0x38, 0xaa, 0x3b, 0x29, + 0x04, 0x31, 0x27, 0xc3, 0x03, 0x31, 0x0f, 0xa1, 0x03, 0x88, 0x13, 0x36, + 0x58, 0x65, 0x66, 0x9d, 0x78, 0x65, 0x40, 0xa2, 0x60, 0x32, 0xc9, 0xe7, + 0x21, 0xf7, 0xaf, 0x24, 0xeb, 0x2f, 0xb0, 0xb0, 0x14, 0xee, 0x7e, 0xbb, + 0x94, 0x58, 0xf1, 0x3a, 0x7e, 0xf8, 0xcf, 0xe3, 0x38, 0xcd, 0xd5, 0x2d, + 0x20, 0x07, 0x38, 0x03, 0x86, 0x30, 0x09, 0xcf, 0x05, 0x38, 0x07, 0x96, + 0x30, 0x04, 0xed, 0x44, 0x77, 0xc9, 0xef, 0x02, 0xa0, 0x38, 0xc9, 0xef, + 0x3d, 0x31, 0x37, 0x00, 0x04, 0x38, 0xcf, 0x09, 0xa0, 0x02, 0x38, 0x7e, + 0x36, 0x80, 0xcd, 0x28, 0x2d, 0xef, 0x34, 0x38, 0x00, 0x03, 0x01, 0x31, + 0x34, 0xf0, 0x4c, 0xcc, 0xcc, 0xcd, 0x03, 0x37, 0x00, 0x08, 0x01, 0xa1, + 0x03, 0x01, 0x38, 0x34, 0xef, 0x01, 0x34, 0xf0, 0x31, 0x72, 0x17, 0xf8, + 0x04, 0x01, 0xa2, 0x03, 0xa2, 0x03, 0x31, 0x34, 0x32, 0x20, 0x04, 0xa2, + 0x03, 0x8c, 0x11, 0xac, 0x14, 0x09, 0x56, 0xda, 0xa5, 0x59, 0x30, 0xc5, + 0x5c, 0x90, 0xaa, 0x9e, 0x70, 0x6f, 0x61, 0xa1, 0xcb, 0xda, 0x96, 0xa4, + 0x31, 0x9f, 0xb4, 0xe7, 0xa0, 0xfe, 0x5c, 0xfc, 0xea, 0x1b, 0x43, 0xca, + 0x36, 0xed, 0xa7, 0x9c, 0x7e, 0x5e, 0xf0, 0x6e, 0x23, 0x80, 0x93, 0x04, + 0x0f, 0x38, 0xc9, 0xef, 0x3d, 0x34, 0xee, 0x22, 0xf9, 0x83, 0x6e, 0x04, + 0x31, 0xa2, 0x0f, 0x27, 0x03, 0x31, 0x0f, 0x31, 0x0f, 0x31, 0x2a, 0xa1, + 0x03, 0x31, 0x37, 0xc0, 0x00, 0x04, 0x02, 0x38, 0xc9, 0xa1, 0x03, 0x01, + 0x36, 0x00, 0x02, 0x1b, 0x38, 0xc9, 0xef, 0x39, 0x2a, 0xa1, 0x03, 0xe0, + 0x00, 0x06, 0x1b, 0x33, 0x03, 0xef, 0x39, 0x31, 0x31, 0x04, 0x31, 0x0f, + 0xa1, 0x03, 0x86, 0x14, 0xe6, 0x5c, 0x1f, 0x0b, 0xa3, 0x8f, 0x38, 0xee, + 0xe9, 0x15, 0x63, 0xbb, 0x23, 0xee, 0x92, 0x0d, 0xcd, 0xed, 0xf1, 0x23, + 0x5d, 0x1b, 0xea, 0x04, 0x38, 0xc9, 0xef, 0x31, 0x1f, 0x01, 0x20, 0x05, + 0x38, 0xc9, 0xcd, 0x97, 0x32, 0x7e, 0xfe, 0x81, 0x38, 0x0e, 0xef, 0xa1, + 0x1b, 0x01, 0x05, 0x31, 0x36, 0xa3, 0x01, 0x00, 0x06, 0x1b, 0x33, 0x03, + 0xef, 0xa0, 0x01, 0x31, 0x31, 0x04, 0x31, 0x0f, 0xa1, 0x03, 0x8c, 0x10, + 0xb2, 0x13, 0x0e, 0x55, 0xe4, 0x8d, 0x58, 0x39, 0xbc, 0x5b, 0x98, 0xfd, + 0x9e, 0x00, 0x36, 0x75, 0xa0, 0xdb, 0xe8, 0xb4, 0x63, 0x42, 0xc4, 0xe6, + 0xb5, 0x09, 0x36, 0xbe, 0xe9, 0x36, 0x73, 0x1b, 0x5d, 0xec, 0xd8, 0xde, + 0x63, 0xbe, 0xf0, 0x61, 0xa1, 0xb3, 0x0c, 0x04, 0x0f, 0x38, 0xc9, 0xef, + 0x31, 0x31, 0x04, 0xa1, 0x03, 0x1b, 0x28, 0xa1, 0x0f, 0x05, 0x24, 0x31, + 0x0f, 0x38, 0xc9, 0xef, 0x22, 0xa3, 0x03, 0x1b, 0x38, 0xc9, 0xef, 0x31, + 0x30, 0x00, 0x1e, 0xa2, 0x38, 0xef, 0x01, 0x31, 0x30, 0x00, 0x07, 0x25, + 0x04, 0x38, 0xc3, 0xc4, 0x36, 0x02, 0x31, 0x30, 0x00, 0x09, 0xa0, 0x01, + 0x37, 0x00, 0x06, 0xa1, 0x01, 0x05, 0x02, 0xa1, 0x38, 0xc9, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00, 0x24, 0x24, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x7e, 0x24, 0x24, 0x7e, 0x24, 0x00, + 0x00, 0x08, 0x3e, 0x28, 0x3e, 0x0a, 0x3e, 0x08, 0x00, 0x62, 0x64, 0x08, + 0x10, 0x26, 0x46, 0x00, 0x00, 0x10, 0x28, 0x10, 0x2a, 0x44, 0x3a, 0x00, + 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x08, + 0x08, 0x08, 0x04, 0x00, 0x00, 0x20, 0x10, 0x10, 0x10, 0x10, 0x20, 0x00, + 0x00, 0x00, 0x14, 0x08, 0x3e, 0x08, 0x14, 0x00, 0x00, 0x00, 0x08, 0x08, + 0x3e, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, + 0x00, 0x3c, 0x46, 0x4a, 0x52, 0x62, 0x3c, 0x00, 0x00, 0x18, 0x28, 0x08, + 0x08, 0x08, 0x3e, 0x00, 0x00, 0x3c, 0x42, 0x02, 0x3c, 0x40, 0x7e, 0x00, + 0x00, 0x3c, 0x42, 0x0c, 0x02, 0x42, 0x3c, 0x00, 0x00, 0x08, 0x18, 0x28, + 0x48, 0x7e, 0x08, 0x00, 0x00, 0x7e, 0x40, 0x7c, 0x02, 0x42, 0x3c, 0x00, + 0x00, 0x3c, 0x40, 0x7c, 0x42, 0x42, 0x3c, 0x00, 0x00, 0x7e, 0x02, 0x04, + 0x08, 0x10, 0x10, 0x00, 0x00, 0x3c, 0x42, 0x3c, 0x42, 0x42, 0x3c, 0x00, + 0x00, 0x3c, 0x42, 0x42, 0x3e, 0x02, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x10, 0x20, + 0x00, 0x00, 0x04, 0x08, 0x10, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x3e, + 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x08, 0x10, 0x00, + 0x00, 0x3c, 0x42, 0x04, 0x08, 0x00, 0x08, 0x00, 0x00, 0x3c, 0x4a, 0x56, + 0x5e, 0x40, 0x3c, 0x00, 0x00, 0x3c, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x00, + 0x00, 0x7c, 0x42, 0x7c, 0x42, 0x42, 0x7c, 0x00, 0x00, 0x3c, 0x42, 0x40, + 0x40, 0x42, 0x3c, 0x00, 0x00, 0x78, 0x44, 0x42, 0x42, 0x44, 0x78, 0x00, + 0x00, 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x7e, 0x00, 0x00, 0x7e, 0x40, 0x7c, + 0x40, 0x40, 0x40, 0x00, 0x00, 0x3c, 0x42, 0x40, 0x4e, 0x42, 0x3c, 0x00, + 0x00, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42, 0x00, 0x00, 0x3e, 0x08, 0x08, + 0x08, 0x08, 0x3e, 0x00, 0x00, 0x02, 0x02, 0x02, 0x42, 0x42, 0x3c, 0x00, + 0x00, 0x44, 0x48, 0x70, 0x48, 0x44, 0x42, 0x00, 0x00, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x7e, 0x00, 0x00, 0x42, 0x66, 0x5a, 0x42, 0x42, 0x42, 0x00, + 0x00, 0x42, 0x62, 0x52, 0x4a, 0x46, 0x42, 0x00, 0x00, 0x3c, 0x42, 0x42, + 0x42, 0x42, 0x3c, 0x00, 0x00, 0x7c, 0x42, 0x42, 0x7c, 0x40, 0x40, 0x00, + 0x00, 0x3c, 0x42, 0x42, 0x52, 0x4a, 0x3c, 0x00, 0x00, 0x7c, 0x42, 0x42, + 0x7c, 0x44, 0x42, 0x00, 0x00, 0x3c, 0x40, 0x3c, 0x02, 0x42, 0x3c, 0x00, + 0x00, 0xfe, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x42, 0x42, 0x42, + 0x42, 0x42, 0x3c, 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, + 0x00, 0x42, 0x42, 0x42, 0x42, 0x5a, 0x24, 0x00, 0x00, 0x42, 0x24, 0x18, + 0x18, 0x24, 0x42, 0x00, 0x00, 0x82, 0x44, 0x28, 0x10, 0x10, 0x10, 0x00, + 0x00, 0x7e, 0x04, 0x08, 0x10, 0x20, 0x7e, 0x00, 0x00, 0x0e, 0x08, 0x08, + 0x08, 0x08, 0x0e, 0x00, 0x00, 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, + 0x00, 0x70, 0x10, 0x10, 0x10, 0x10, 0x70, 0x00, 0x00, 0x10, 0x38, 0x54, + 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x1c, 0x22, 0x78, 0x20, 0x20, 0x7e, 0x00, 0x00, 0x00, 0x38, 0x04, + 0x3c, 0x44, 0x3c, 0x00, 0x00, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x3c, 0x00, + 0x00, 0x00, 0x1c, 0x20, 0x20, 0x20, 0x1c, 0x00, 0x00, 0x04, 0x04, 0x3c, + 0x44, 0x44, 0x3c, 0x00, 0x00, 0x00, 0x38, 0x44, 0x78, 0x40, 0x3c, 0x00, + 0x00, 0x0c, 0x10, 0x18, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x3c, 0x44, + 0x44, 0x3c, 0x04, 0x38, 0x00, 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x00, + 0x00, 0x10, 0x00, 0x30, 0x10, 0x10, 0x38, 0x00, 0x00, 0x04, 0x00, 0x04, + 0x04, 0x04, 0x24, 0x18, 0x00, 0x20, 0x28, 0x30, 0x30, 0x28, 0x24, 0x00, + 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x68, 0x54, + 0x54, 0x54, 0x54, 0x00, 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, + 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x78, 0x44, + 0x44, 0x78, 0x40, 0x40, 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x06, + 0x00, 0x00, 0x1c, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x38, 0x40, + 0x38, 0x04, 0x78, 0x00, 0x00, 0x10, 0x38, 0x10, 0x10, 0x10, 0x0c, 0x00, + 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x44, 0x44, + 0x28, 0x28, 0x10, 0x00, 0x00, 0x00, 0x44, 0x54, 0x54, 0x54, 0x28, 0x00, + 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x44, 0x44, + 0x44, 0x3c, 0x04, 0x38, 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00, + 0x00, 0x0e, 0x08, 0x30, 0x08, 0x08, 0x0e, 0x00, 0x00, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x00, 0x00, 0x70, 0x10, 0x0c, 0x10, 0x10, 0x70, 0x00, + 0x00, 0x14, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x42, 0x99, 0xa1, + 0xa1, 0x99, 0x42, 0x3c +}; \ No newline at end of file diff --git a/khan/roms/khan128_roms.txt b/khan/roms/khan128_roms.txt new file mode 100644 index 0000000..c0c7719 --- /dev/null +++ b/khan/roms/khan128_roms.txt @@ -0,0 +1,4 @@ +ROM_48 = sos48.rom +ROM_128_0 = sos128_0.rom +ROM_128_1 = sos128_1.rom + diff --git a/khan/roms/khan_roms.txt b/khan/roms/khan_roms.txt new file mode 100644 index 0000000..be85b46 --- /dev/null +++ b/khan/roms/khan_roms.txt @@ -0,0 +1 @@ +ROM_48 = sos48.rom diff --git a/khan/roms/roms.h b/khan/roms/roms.h new file mode 100644 index 0000000..2ec772d --- /dev/null +++ b/khan/roms/roms.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef ROMS_H +#define ROMS_H + +#include +#include "../../std.h" + +typedef struct { + const char *name; + const uint8_t *data_z; + const uint data_z_size; + const uint data_size; +} embedded_rom_t; + +extern embedded_rom_t embedded_roms[]; +extern int embedded_rom_count; +#endif diff --git a/khan/sdl_keys.h b/khan/sdl_keys.h new file mode 100644 index 0000000..bcc3bb8 --- /dev/null +++ b/khan/sdl_keys.h @@ -0,0 +1,429 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2019 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef _SDL_KEYS_H +#define _SDL_KEYS_H + +#include "pico/types.h" +/** + * \brief The SDL keyboard scancode representation. + * + * Values of this type are used to represent keyboard keys, among other places + * in the \link SDL_Keysym::scancode key.keysym.scancode \endlink field of the + * SDL_Event structure. + * + * The values in this enumeration are based on the USB usage page standard: + * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf + */ +typedef enum +{ + SDL_SCANCODE_UNKNOWN = 0, + + /** + * \name Usage page 0x07 + * + * These values are from usage page 0x07 (USB keyboard page). + */ + /* @{ */ + + SDL_SCANCODE_A = 4, + SDL_SCANCODE_B = 5, + SDL_SCANCODE_C = 6, + SDL_SCANCODE_D = 7, + SDL_SCANCODE_E = 8, + SDL_SCANCODE_F = 9, + SDL_SCANCODE_G = 10, + SDL_SCANCODE_H = 11, + SDL_SCANCODE_I = 12, + SDL_SCANCODE_J = 13, + SDL_SCANCODE_K = 14, + SDL_SCANCODE_L = 15, + SDL_SCANCODE_M = 16, + SDL_SCANCODE_N = 17, + SDL_SCANCODE_O = 18, + SDL_SCANCODE_P = 19, + SDL_SCANCODE_Q = 20, + SDL_SCANCODE_R = 21, + SDL_SCANCODE_S = 22, + SDL_SCANCODE_T = 23, + SDL_SCANCODE_U = 24, + SDL_SCANCODE_V = 25, + SDL_SCANCODE_W = 26, + SDL_SCANCODE_X = 27, + SDL_SCANCODE_Y = 28, + SDL_SCANCODE_Z = 29, + + SDL_SCANCODE_1 = 30, + SDL_SCANCODE_2 = 31, + SDL_SCANCODE_3 = 32, + SDL_SCANCODE_4 = 33, + SDL_SCANCODE_5 = 34, + SDL_SCANCODE_6 = 35, + SDL_SCANCODE_7 = 36, + SDL_SCANCODE_8 = 37, + SDL_SCANCODE_9 = 38, + SDL_SCANCODE_0 = 39, + + SDL_SCANCODE_RETURN = 40, + SDL_SCANCODE_ESCAPE = 41, + SDL_SCANCODE_BACKSPACE = 42, + SDL_SCANCODE_TAB = 43, + SDL_SCANCODE_SPACE = 44, + + SDL_SCANCODE_MINUS = 45, + SDL_SCANCODE_EQUALS = 46, + SDL_SCANCODE_LEFTBRACKET = 47, + SDL_SCANCODE_RIGHTBRACKET = 48, + SDL_SCANCODE_BACKSLASH = 49, /**< Located at the lower left of the return + * key on ISO keyboards and at the right end + * of the QWERTY row on ANSI keyboards. + * Produces REVERSE SOLIDUS (backslash) and + * VERTICAL LINE in a US layout, REVERSE + * SOLIDUS and VERTICAL LINE in a UK Mac + * layout, NUMBER SIGN and TILDE in a UK + * Windows layout, DOLLAR SIGN and POUND SIGN + * in a Swiss German layout, NUMBER SIGN and + * APOSTROPHE in a German layout, GRAVE + * ACCENT and POUND SIGN in a French Mac + * layout, and ASTERISK and MICRO SIGN in a + * French Windows layout. + */ + SDL_SCANCODE_NONUSHASH = 50, /**< ISO USB keyboards actually use this code + * instead of 49 for the same key, but all + * OSes I've seen treat the two codes + * identically. So, as an implementor, unless + * your keyboard generates both of those + * codes and your OS treats them differently, + * you should generate SDL_SCANCODE_BACKSLASH + * instead of this code. As a user, you + * should not rely on this code because SDL + * will never generate it with most (all?) + * keyboards. + */ + SDL_SCANCODE_SEMICOLON = 51, + SDL_SCANCODE_APOSTROPHE = 52, + SDL_SCANCODE_GRAVE = 53, /**< Located in the top left corner (on both ANSI + * and ISO keyboards). Produces GRAVE ACCENT and + * TILDE in a US Windows layout and in US and UK + * Mac layouts on ANSI keyboards, GRAVE ACCENT + * and NOT SIGN in a UK Windows layout, SECTION + * SIGN and PLUS-MINUS SIGN in US and UK Mac + * layouts on ISO keyboards, SECTION SIGN and + * DEGREE SIGN in a Swiss German layout (Mac: + * only on ISO keyboards), CIRCUMFLEX ACCENT and + * DEGREE SIGN in a German layout (Mac: only on + * ISO keyboards), SUPERSCRIPT TWO and TILDE in a + * French Windows layout, COMMERCIAL AT and + * NUMBER SIGN in a French Mac layout on ISO + * keyboards, and LESS-THAN SIGN and GREATER-THAN + * SIGN in a Swiss German, German, or French Mac + * layout on ANSI keyboards. + */ + SDL_SCANCODE_COMMA = 54, + SDL_SCANCODE_PERIOD = 55, + SDL_SCANCODE_SLASH = 56, + + SDL_SCANCODE_CAPSLOCK = 57, + + SDL_SCANCODE_F1 = 58, + SDL_SCANCODE_F2 = 59, + SDL_SCANCODE_F3 = 60, + SDL_SCANCODE_F4 = 61, + SDL_SCANCODE_F5 = 62, + SDL_SCANCODE_F6 = 63, + SDL_SCANCODE_F7 = 64, + SDL_SCANCODE_F8 = 65, + SDL_SCANCODE_F9 = 66, + SDL_SCANCODE_F10 = 67, + SDL_SCANCODE_F11 = 68, + SDL_SCANCODE_F12 = 69, + + SDL_SCANCODE_PRINTSCREEN = 70, + SDL_SCANCODE_SCROLLLOCK = 71, + SDL_SCANCODE_PAUSE = 72, + SDL_SCANCODE_INSERT = 73, /**< insert on PC, help on some Mac keyboards (but + does send code 73, not 117) */ + SDL_SCANCODE_HOME = 74, + SDL_SCANCODE_PAGEUP = 75, + SDL_SCANCODE_DELETE = 76, + SDL_SCANCODE_END = 77, + SDL_SCANCODE_PAGEDOWN = 78, + SDL_SCANCODE_RIGHT = 79, + SDL_SCANCODE_LEFT = 80, + SDL_SCANCODE_DOWN = 81, + SDL_SCANCODE_UP = 82, + + SDL_SCANCODE_NUMLOCKCLEAR = 83, /**< num lock on PC, clear on Mac keyboards + */ + SDL_SCANCODE_KP_DIVIDE = 84, + SDL_SCANCODE_KP_MULTIPLY = 85, + SDL_SCANCODE_KP_MINUS = 86, + SDL_SCANCODE_KP_PLUS = 87, + SDL_SCANCODE_KP_ENTER = 88, + SDL_SCANCODE_KP_1 = 89, + SDL_SCANCODE_KP_2 = 90, + SDL_SCANCODE_KP_3 = 91, + SDL_SCANCODE_KP_4 = 92, + SDL_SCANCODE_KP_5 = 93, + SDL_SCANCODE_KP_6 = 94, + SDL_SCANCODE_KP_7 = 95, + SDL_SCANCODE_KP_8 = 96, + SDL_SCANCODE_KP_9 = 97, + SDL_SCANCODE_KP_0 = 98, + SDL_SCANCODE_KP_PERIOD = 99, + + SDL_SCANCODE_NONUSBACKSLASH = 100, /**< This is the additional key that ISO + * keyboards have over ANSI ones, + * located between left shift and Y. + * Produces GRAVE ACCENT and TILDE in a + * US or UK Mac layout, REVERSE SOLIDUS + * (backslash) and VERTICAL LINE in a + * US or UK Windows layout, and + * LESS-THAN SIGN and GREATER-THAN SIGN + * in a Swiss German, German, or French + * layout. */ + SDL_SCANCODE_APPLICATION = 101, /**< windows contextual menu, compose */ + SDL_SCANCODE_POWER = 102, /**< The USB document says this is a status flag, + * not a physical key - but some Mac keyboards + * do have a power key. */ + SDL_SCANCODE_KP_EQUALS = 103, + SDL_SCANCODE_F13 = 104, + SDL_SCANCODE_F14 = 105, + SDL_SCANCODE_F15 = 106, + SDL_SCANCODE_F16 = 107, + SDL_SCANCODE_F17 = 108, + SDL_SCANCODE_F18 = 109, + SDL_SCANCODE_F19 = 110, + SDL_SCANCODE_F20 = 111, + SDL_SCANCODE_F21 = 112, + SDL_SCANCODE_F22 = 113, + SDL_SCANCODE_F23 = 114, + SDL_SCANCODE_F24 = 115, + SDL_SCANCODE_EXECUTE = 116, + SDL_SCANCODE_HELP = 117, + SDL_SCANCODE_MENU = 118, + SDL_SCANCODE_SELECT = 119, + SDL_SCANCODE_STOP = 120, + SDL_SCANCODE_AGAIN = 121, /**< redo */ + SDL_SCANCODE_UNDO = 122, + SDL_SCANCODE_CUT = 123, + SDL_SCANCODE_COPY = 124, + SDL_SCANCODE_PASTE = 125, + SDL_SCANCODE_FIND = 126, + SDL_SCANCODE_MUTE = 127, + SDL_SCANCODE_VOLUMEUP = 128, + SDL_SCANCODE_VOLUMEDOWN = 129, +/* not sure whether there's a reason to enable these */ +/* SDL_SCANCODE_LOCKINGCAPSLOCK = 130, */ +/* SDL_SCANCODE_LOCKINGNUMLOCK = 131, */ +/* SDL_SCANCODE_LOCKINGSCROLLLOCK = 132, */ + SDL_SCANCODE_KP_COMMA = 133, + SDL_SCANCODE_KP_EQUALSAS400 = 134, + + SDL_SCANCODE_INTERNATIONAL1 = 135, /**< used on Asian keyboards, see + footnotes in USB doc */ + SDL_SCANCODE_INTERNATIONAL2 = 136, + SDL_SCANCODE_INTERNATIONAL3 = 137, /**< Yen */ + SDL_SCANCODE_INTERNATIONAL4 = 138, + SDL_SCANCODE_INTERNATIONAL5 = 139, + SDL_SCANCODE_INTERNATIONAL6 = 140, + SDL_SCANCODE_INTERNATIONAL7 = 141, + SDL_SCANCODE_INTERNATIONAL8 = 142, + SDL_SCANCODE_INTERNATIONAL9 = 143, + SDL_SCANCODE_LANG1 = 144, /**< Hangul/English toggle */ + SDL_SCANCODE_LANG2 = 145, /**< Hanja conversion */ + SDL_SCANCODE_LANG3 = 146, /**< Katakana */ + SDL_SCANCODE_LANG4 = 147, /**< Hiragana */ + SDL_SCANCODE_LANG5 = 148, /**< Zenkaku/Hankaku */ + SDL_SCANCODE_LANG6 = 149, /**< reserved */ + SDL_SCANCODE_LANG7 = 150, /**< reserved */ + SDL_SCANCODE_LANG8 = 151, /**< reserved */ + SDL_SCANCODE_LANG9 = 152, /**< reserved */ + + SDL_SCANCODE_ALTERASE = 153, /**< Erase-Eaze */ + SDL_SCANCODE_SYSREQ = 154, + SDL_SCANCODE_CANCEL = 155, + SDL_SCANCODE_CLEAR = 156, + SDL_SCANCODE_PRIOR = 157, + SDL_SCANCODE_RETURN2 = 158, + SDL_SCANCODE_SEPARATOR = 159, + SDL_SCANCODE_OUT = 160, + SDL_SCANCODE_OPER = 161, + SDL_SCANCODE_CLEARAGAIN = 162, + SDL_SCANCODE_CRSEL = 163, + SDL_SCANCODE_EXSEL = 164, + + SDL_SCANCODE_KP_00 = 176, + SDL_SCANCODE_KP_000 = 177, + SDL_SCANCODE_THOUSANDSSEPARATOR = 178, + SDL_SCANCODE_DECIMALSEPARATOR = 179, + SDL_SCANCODE_CURRENCYUNIT = 180, + SDL_SCANCODE_CURRENCYSUBUNIT = 181, + SDL_SCANCODE_KP_LEFTPAREN = 182, + SDL_SCANCODE_KP_RIGHTPAREN = 183, + SDL_SCANCODE_KP_LEFTBRACE = 184, + SDL_SCANCODE_KP_RIGHTBRACE = 185, + SDL_SCANCODE_KP_TAB = 186, + SDL_SCANCODE_KP_BACKSPACE = 187, + SDL_SCANCODE_KP_A = 188, + SDL_SCANCODE_KP_B = 189, + SDL_SCANCODE_KP_C = 190, + SDL_SCANCODE_KP_D = 191, + SDL_SCANCODE_KP_E = 192, + SDL_SCANCODE_KP_F = 193, + SDL_SCANCODE_KP_XOR = 194, + SDL_SCANCODE_KP_POWER = 195, + SDL_SCANCODE_KP_PERCENT = 196, + SDL_SCANCODE_KP_LESS = 197, + SDL_SCANCODE_KP_GREATER = 198, + SDL_SCANCODE_KP_AMPERSAND = 199, + SDL_SCANCODE_KP_DBLAMPERSAND = 200, + SDL_SCANCODE_KP_VERTICALBAR = 201, + SDL_SCANCODE_KP_DBLVERTICALBAR = 202, + SDL_SCANCODE_KP_COLON = 203, + SDL_SCANCODE_KP_HASH = 204, + SDL_SCANCODE_KP_SPACE = 205, + SDL_SCANCODE_KP_AT = 206, + SDL_SCANCODE_KP_EXCLAM = 207, + SDL_SCANCODE_KP_MEMSTORE = 208, + SDL_SCANCODE_KP_MEMRECALL = 209, + SDL_SCANCODE_KP_MEMCLEAR = 210, + SDL_SCANCODE_KP_MEMADD = 211, + SDL_SCANCODE_KP_MEMSUBTRACT = 212, + SDL_SCANCODE_KP_MEMMULTIPLY = 213, + SDL_SCANCODE_KP_MEMDIVIDE = 214, + SDL_SCANCODE_KP_PLUSMINUS = 215, + SDL_SCANCODE_KP_CLEAR = 216, + SDL_SCANCODE_KP_CLEARENTRY = 217, + SDL_SCANCODE_KP_BINARY = 218, + SDL_SCANCODE_KP_OCTAL = 219, + SDL_SCANCODE_KP_DECIMAL = 220, + SDL_SCANCODE_KP_HEXADECIMAL = 221, + + SDL_SCANCODE_LCTRL = 224, + SDL_SCANCODE_LSHIFT = 225, + SDL_SCANCODE_LALT = 226, /**< alt, option */ + SDL_SCANCODE_LGUI = 227, /**< windows, command (apple), meta */ + SDL_SCANCODE_RCTRL = 228, + SDL_SCANCODE_RSHIFT = 229, + SDL_SCANCODE_RALT = 230, /**< alt gr, option */ + SDL_SCANCODE_RGUI = 231, /**< windows, command (apple), meta */ + + SDL_SCANCODE_MODE = 257, /**< I'm not sure if this is really not covered + * by any of the above, but since there's a + * special KMOD_MODE for it I'm adding it here + */ + + /* @} *//* Usage page 0x07 */ + + /** + * \name Usage page 0x0C + * + * These values are mapped from usage page 0x0C (USB consumer page). + */ + /* @{ */ + + SDL_SCANCODE_AUDIONEXT = 258, + SDL_SCANCODE_AUDIOPREV = 259, + SDL_SCANCODE_AUDIOSTOP = 260, + SDL_SCANCODE_AUDIOPLAY = 261, + SDL_SCANCODE_AUDIOMUTE = 262, + SDL_SCANCODE_MEDIASELECT = 263, + SDL_SCANCODE_WWW = 264, + SDL_SCANCODE_MAIL = 265, + SDL_SCANCODE_CALCULATOR = 266, + SDL_SCANCODE_COMPUTER = 267, + SDL_SCANCODE_AC_SEARCH = 268, + SDL_SCANCODE_AC_HOME = 269, + SDL_SCANCODE_AC_BACK = 270, + SDL_SCANCODE_AC_FORWARD = 271, + SDL_SCANCODE_AC_STOP = 272, + SDL_SCANCODE_AC_REFRESH = 273, + SDL_SCANCODE_AC_BOOKMARKS = 274, + + /* @} *//* Usage page 0x0C */ + + /** + * \name Walther keys + * + * These are values that Christian Walther added (for mac keyboard?). + */ + /* @{ */ + + SDL_SCANCODE_BRIGHTNESSDOWN = 275, + SDL_SCANCODE_BRIGHTNESSUP = 276, + SDL_SCANCODE_DISPLAYSWITCH = 277, /**< display mirroring/dual display + switch, video mode switch */ + SDL_SCANCODE_KBDILLUMTOGGLE = 278, + SDL_SCANCODE_KBDILLUMDOWN = 279, + SDL_SCANCODE_KBDILLUMUP = 280, + SDL_SCANCODE_EJECT = 281, + SDL_SCANCODE_SLEEP = 282, + + SDL_SCANCODE_APP1 = 283, + SDL_SCANCODE_APP2 = 284, + + /* @} *//* Walther keys */ + + /** + * \name Usage page 0x0C (additional media keys) + * + * These values are mapped from usage page 0x0C (USB consumer page). + */ + /* @{ */ + + SDL_SCANCODE_AUDIOREWIND = 285, + SDL_SCANCODE_AUDIOFASTFORWARD = 286, + + /* @} *//* Usage page 0x0C (additional media keys) */ + + /* Add any other keys here. */ + + SDL_NUM_SCANCODES = 512 /**< not a key, just marks the number of scancodes + for array bounds */ +} SDL_Scancode; + +/** + * \brief Enumeration of valid key mods (possibly OR'd together). + */ +typedef enum +{ + KMOD_NONE = 0x0000, + KMOD_LSHIFT = 0x0001, + KMOD_RSHIFT = 0x0002, + KMOD_LCTRL = 0x0040, + KMOD_RCTRL = 0x0080, + KMOD_LALT = 0x0100, + KMOD_RALT = 0x0200, + KMOD_LGUI = 0x0400, + KMOD_RGUI = 0x0800, + KMOD_NUM = 0x1000, + KMOD_CAPS = 0x2000, + KMOD_MODE = 0x4000, + KMOD_RESERVED = 0x8000 +} SDL_Keymod; + +#define KMOD_CTRL (KMOD_LCTRL|KMOD_RCTRL) +#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT) +#define KMOD_ALT (KMOD_LALT|KMOD_RALT) +#define KMOD_GUI (KMOD_LGUI|KMOD_RGUI) + +#endif diff --git a/khan/spoon.cpp b/khan/spoon.cpp new file mode 100644 index 0000000..3647690 --- /dev/null +++ b/khan/spoon.cpp @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "z80t.h" + +void spoono() { + Z80t::eZ80t cpu; + cpu.generate_arm(); +} diff --git a/khan/tusb_config.h b/khan/tusb_config.h new file mode 100644 index 0000000..3579384 --- /dev/null +++ b/khan/tusb_config.h @@ -0,0 +1,98 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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 _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_HOST | OPT_MODE_HIGH_SPEED) +#else + #define CFG_TUSB_RHPORT0_MODE OPT_MODE_HOST +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +#define USB_MAX_ENDPOINTS 4 +//-------------------------------------------------------------------- +// CONFIGURATION +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 128 + +#define CFG_TUH_HUB 1 +#define CFG_TUH_CDC 0 +#define CFG_TUH_MSC 0 +#define CFG_TUH_VENDOR 0 + +// max device support (excluding hub device) +//#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports +// note tinyusb is very wasteful on space +#define CFG_TUH_DEVICE_MAX 1 +#define CFG_TUH_HID 4 // typical keyboard + mouse device can have 3-4 HID interfaces +//------------- HID -------------// +#define CFG_TUH_HID_EPIN_BUFSIZE 64 +//#define CFG_TUH_HID_EPOUT_BUFSIZE 64 +// not sure we send much +#define CFG_TUH_HID_EPOUT_BUFSIZE 16 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/khan/ui_font.cpp b/khan/ui_font.cpp new file mode 100644 index 0000000..11d8778 --- /dev/null +++ b/khan/ui_font.cpp @@ -0,0 +1,1275 @@ +#include "khan_lib.h" + +/*********************************************************************************** + * AtlantisInternational.ttf 12 px Font in U+0020 ( ) .. U+007e (~) range with 1 bpp +***********************************************************************************/ + +/*Store the image of the letters (glyph)*/ +const uint8_t atlantis_glyph_bitmap[] = + { + /*Unicode: U+0020 ( ) , Width: 4 */ + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x00, //.... + +/*Unicode: U+0021 (!) , Width: 1 */ + 0x00, //. + 0x80, //% + 0x80, //% + 0x80, //% + 0x80, //% + 0x80, //% + 0x00, //. + 0x80, //% + 0x00, //. + +/*Unicode: U+0022 (") , Width: 3 */ + 0xa0, //%.% + 0xa0, //%.% + 0xa0, //%.% + 0x00, //... + 0x00, //... + 0x00, //... + 0x00, //... + 0x00, //... + 0x00, //... + +/*Unicode: U+0023 (#) , Width: 6 */ + 0x28, //..%.%. + 0x28, //..%.%. + 0xfc, //%%%%%% + 0x50, //.%.%.. + 0x50, //.%.%.. + 0xfc, //%%%%%% + 0x50, //.%.%.. + 0x50, //.%.%.. + 0x00, //...... + +/*Unicode: U+0024 ($) , Width: 5 */ + 0x20, //..%.. + 0x70, //.%%%. + 0xa8, //%.%.% + 0xa0, //%.%.. + 0x70, //.%%%. + 0x28, //..%.% + 0xa8, //%.%.% + 0x70, //.%%%. + 0x20, //..%.. + +/*Unicode: U+0025 (%) , Width: 8 */ + 0x00, //........ + 0xe4, //%%%..%.. + 0xa8, //%.%.%... + 0xe8, //%%%.%... + 0x17, //...%.%%% + 0x15, //...%.%.% + 0x27, //..%..%%% + 0x20, //..%..... + 0x00, //........ + +/*Unicode: U+0026 (&) , Width: 7 */ + 0x30, //..%%... + 0x48, //.%..%.. + 0x48, //.%..%.. + 0x30, //..%%... + 0x52, //.%.%..% + 0x8c, //%...%%. + 0x8c, //%...%%. + 0x72, //.%%%..% + 0x00, //....... + +/*Unicode: U+0027 (') , Width: 1 */ + 0x00, //. + 0x80, //% + 0x80, //% + 0x00, //. + 0x00, //. + 0x00, //. + 0x00, //. + 0x00, //. + 0x00, //. + +/*Unicode: U+0028 (() , Width: 3 */ + 0x00, //... + 0x20, //..% + 0x40, //.%. + 0x80, //%.. + 0x80, //%.. + 0x80, //%.. + 0x80, //%.. + 0x40, //.%. + 0x20, //..% + +/*Unicode: U+0029 ()) , Width: 3 */ + 0x00, //... + 0x80, //%.. + 0x40, //.%. + 0x20, //..% + 0x20, //..% + 0x20, //..% + 0x20, //..% + 0x40, //.%. + 0x80, //%.. + +/*Unicode: U+002a (*) , Width: 5 */ + 0x00, //..... + 0xa8, //%.%.% + 0x70, //.%%%. + 0xa8, //%.%.% + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x00, //..... + +/*Unicode: U+002b (+) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x20, //..%.. + 0x20, //..%.. + 0xf8, //%%%%% + 0x20, //..%.. + 0x20, //..%.. + 0x00, //..... + 0x00, //..... + +/*Unicode: U+002c (,) , Width: 2 */ + 0x00, //.. + 0x00, //.. + 0x00, //.. + 0x00, //.. + 0x00, //.. + 0x00, //.. + 0x40, //.% + 0x40, //.% + 0x80, //%. + +/*Unicode: U+002d (-) , Width: 3 */ + 0x00, //... + 0x00, //... + 0x00, //... + 0x00, //... + 0x00, //... + 0xe0, //%%% + 0x00, //... + 0x00, //... + 0x00, //... + +/*Unicode: U+002e (.) , Width: 1 */ + 0x00, //. + 0x00, //. + 0x00, //. + 0x00, //. + 0x00, //. + 0x00, //. + 0x80, //% + 0x80, //% + 0x00, //. + +/*Unicode: U+002f (/) , Width: 3 */ + 0x00, //... + 0x20, //..% + 0x20, //..% + 0x40, //.%. + 0x40, //.%. + 0x40, //.%. + 0x80, //%.. + 0x80, //%.. + 0x00, //... + +/*Unicode: U+0030 (0) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0031 (1) , Width: 5 */ + 0x00, //..... + 0x20, //..%.. + 0x60, //.%%.. + 0x20, //..%.. + 0x20, //..%.. + 0x20, //..%.. + 0x20, //..%.. + 0xf8, //%%%%% + 0x00, //..... + +/*Unicode: U+0032 (2) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x08, //....% + 0x10, //...%. + 0x20, //..%.. + 0x40, //.%... + 0xf8, //%%%%% + 0x00, //..... + +/*Unicode: U+0033 (3) , Width: 5 */ + 0x00, //..... + 0xf0, //%%%%. + 0x08, //....% + 0x08, //....% + 0x30, //..%%. + 0x08, //....% + 0x08, //....% + 0xf0, //%%%%. + 0x00, //..... + +/*Unicode: U+0034 (4) , Width: 5 */ + 0x00, //..... + 0x10, //...%. + 0x30, //..%%. + 0x50, //.%.%. + 0x90, //%..%. + 0xf8, //%%%%% + 0x10, //...%. + 0x10, //...%. + 0x00, //..... + +/*Unicode: U+0035 (5) , Width: 5 */ + 0x00, //..... + 0xf8, //%%%%% + 0x80, //%.... + 0x80, //%.... + 0xf0, //%%%%. + 0x08, //....% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0036 (6) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x80, //%.... + 0xf0, //%%%%. + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0037 (7) , Width: 5 */ + 0x00, //..... + 0xf8, //%%%%% + 0x08, //....% + 0x10, //...%. + 0x20, //..%.. + 0x20, //..%.. + 0x40, //.%... + 0x40, //.%... + 0x00, //..... + +/*Unicode: U+0038 (8) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0039 (9) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0x78, //.%%%% + 0x08, //....% + 0x08, //....% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+003a (:) , Width: 1 */ + 0x00, //. + 0x00, //. + 0x00, //. + 0x80, //% + 0x00, //. + 0x00, //. + 0x00, //. + 0x80, //% + 0x00, //. + +/*Unicode: U+003b (;) , Width: 2 */ + 0x00, //.. + 0x00, //.. + 0x00, //.. + 0x40, //.% + 0x00, //.. + 0x00, //.. + 0x40, //.% + 0x40, //.% + 0x80, //%. + +/*Unicode: U+003c (<) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x18, //...%% + 0x60, //.%%.. + 0x80, //%.... + 0x60, //.%%.. + 0x18, //...%% + 0x00, //..... + +/*Unicode: U+003d (=) , Width: 4 */ + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0xf0, //%%%% + 0x00, //.... + 0xf0, //%%%% + 0x00, //.... + 0x00, //.... + +/*Unicode: U+003e (>) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0xc0, //%%... + 0x30, //..%%. + 0x08, //....% + 0x30, //..%%. + 0xc0, //%%... + 0x00, //..... + +/*Unicode: U+003f (?) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x08, //....% + 0x10, //...%. + 0x20, //..%.. + 0x00, //..... + 0x20, //..%.. + 0x00, //..... + +/*Unicode: U+0040 (@) , Width: 8 */ + 0x00, //........ + 0x3c, //..%%%%.. + 0x42, //.%....%. + 0x9d, //%..%%%.% + 0xa5, //%.%..%.% + 0xa5, //%.%..%.% + 0x9e, //%..%%%%. + 0x41, //.%.....% + 0x3e, //..%%%%%. + +/*Unicode: U+0041 (A) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0xf8, //%%%%% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x00, //..... + +/*Unicode: U+0042 (B) , Width: 5 */ + 0x00, //..... + 0xf0, //%%%%. + 0x88, //%...% + 0x88, //%...% + 0xf0, //%%%%. + 0x88, //%...% + 0x88, //%...% + 0xf0, //%%%%. + 0x00, //..... + +/*Unicode: U+0043 (C) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x80, //%.... + 0x80, //%.... + 0x80, //%.... + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0044 (D) , Width: 5 */ + 0x00, //..... + 0xf0, //%%%%. + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0xf0, //%%%%. + 0x00, //..... + +/*Unicode: U+0045 (E) , Width: 6 */ + 0x00, //...... + 0xf8, //%%%%%. + 0x80, //%..... + 0x80, //%..... + 0xf0, //%%%%.. + 0x80, //%..... + 0x80, //%..... + 0xf8, //%%%%%. + 0x00, //...... + +/*Unicode: U+0046 (F) , Width: 6 */ + 0x00, //...... + 0xf8, //%%%%%. + 0x80, //%..... + 0x80, //%..... + 0xf0, //%%%%.. + 0x80, //%..... + 0x80, //%..... + 0x80, //%..... + 0x00, //...... + +/*Unicode: U+0047 (G) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x80, //%.... + 0xb8, //%.%%% + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0048 (H) , Width: 5 */ + 0x00, //..... + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0xf8, //%%%%% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x00, //..... + +/*Unicode: U+0049 (I) , Width: 3 */ + 0x00, //... + 0xe0, //%%% + 0x40, //.%. + 0x40, //.%. + 0x40, //.%. + 0x40, //.%. + 0x40, //.%. + 0xe0, //%%% + 0x00, //... + +/*Unicode: U+004a (J) , Width: 5 */ + 0x00, //..... + 0x08, //....% + 0x08, //....% + 0x08, //....% + 0x08, //....% + 0x08, //....% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+004b (K) , Width: 5 */ + 0x00, //..... + 0x88, //%...% + 0x90, //%..%. + 0xa0, //%.%.. + 0xc0, //%%... + 0xa0, //%.%.. + 0x90, //%..%. + 0x88, //%...% + 0x00, //..... + +/*Unicode: U+004c (L) , Width: 6 */ + 0x00, //...... + 0x80, //%..... + 0x80, //%..... + 0x80, //%..... + 0x80, //%..... + 0x80, //%..... + 0x80, //%..... + 0xf8, //%%%%%. + 0x00, //...... + +/*Unicode: U+004d (M) , Width: 7 */ + 0x00, //....... + 0x82, //%.....% + 0xc6, //%%...%% + 0xaa, //%.%.%.% + 0x92, //%..%..% + 0x82, //%.....% + 0x82, //%.....% + 0x82, //%.....% + 0x00, //....... + +/*Unicode: U+004e (N) , Width: 6 */ + 0x00, //...... + 0x84, //%....% + 0xc4, //%%...% + 0xa4, //%.%..% + 0x94, //%..%.% + 0x8c, //%...%% + 0x84, //%....% + 0x84, //%....% + 0x00, //...... + +/*Unicode: U+004f (O) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0050 (P) , Width: 5 */ + 0x00, //..... + 0xf0, //%%%%. + 0x88, //%...% + 0x88, //%...% + 0xf0, //%%%%. + 0x80, //%.... + 0x80, //%.... + 0x80, //%.... + 0x00, //..... + +/*Unicode: U+0051 (Q) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x08, //....% + +/*Unicode: U+0052 (R) , Width: 5 */ + 0x00, //..... + 0xf0, //%%%%. + 0x88, //%...% + 0x88, //%...% + 0xf0, //%%%%. + 0xa0, //%.%.. + 0x90, //%..%. + 0x88, //%...% + 0x00, //..... + +/*Unicode: U+0053 (S) , Width: 5 */ + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x80, //%.... + 0x70, //.%%%. + 0x08, //....% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0054 (T) , Width: 5 */ + 0x00, //..... + 0xf8, //%%%%% + 0x20, //..%.. + 0x20, //..%.. + 0x20, //..%.. + 0x20, //..%.. + 0x20, //..%.. + 0x20, //..%.. + 0x00, //..... + +/*Unicode: U+0055 (U) , Width: 5 */ + 0x00, //..... + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0056 (V) , Width: 7 */ + 0x00, //....... + 0x82, //%.....% + 0x44, //.%...%. + 0x44, //.%...%. + 0x28, //..%.%.. + 0x28, //..%.%.. + 0x10, //...%... + 0x10, //...%... + 0x00, //....... + +/*Unicode: U+0057 (W) , Width: 7 */ + 0x00, //....... + 0x82, //%.....% + 0x82, //%.....% + 0x82, //%.....% + 0x92, //%..%..% + 0xaa, //%.%.%.% + 0xc6, //%%...%% + 0x82, //%.....% + 0x00, //....... + +/*Unicode: U+0058 (X) , Width: 5 */ + 0x00, //..... + 0x88, //%...% + 0x88, //%...% + 0x50, //.%.%. + 0x20, //..%.. + 0x50, //.%.%. + 0x88, //%...% + 0x88, //%...% + 0x00, //..... + +/*Unicode: U+0059 (Y) , Width: 5 */ + 0x00, //..... + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x50, //.%.%. + 0x20, //..%.. + 0x20, //..%.. + 0x20, //..%.. + 0x00, //..... + +/*Unicode: U+005a (Z) , Width: 5 */ + 0x00, //..... + 0xf8, //%%%%% + 0x08, //....% + 0x10, //...%. + 0x20, //..%.. + 0x40, //.%... + 0x80, //%.... + 0xf8, //%%%%% + 0x00, //..... + +/*Unicode: U+005b ([) , Width: 2 */ + 0x00, //.. + 0xc0, //%% + 0x80, //%. + 0x80, //%. + 0x80, //%. + 0x80, //%. + 0x80, //%. + 0x80, //%. + 0xc0, //%% + +/*Unicode: U+005c (\) , Width: 3 */ + 0x00, //... + 0x80, //%.. + 0x80, //%.. + 0x40, //.%. + 0x40, //.%. + 0x40, //.%. + 0x20, //..% + 0x20, //..% + 0x00, //... + +/*Unicode: U+005d (]) , Width: 2 */ + 0x00, //.. + 0xc0, //%% + 0x40, //.% + 0x40, //.% + 0x40, //.% + 0x40, //.% + 0x40, //.% + 0x40, //.% + 0xc0, //%% + +/*Unicode: U+005e (^) , Width: 5 */ + 0x00, //..... + 0x20, //..%.. + 0x50, //.%.%. + 0x88, //%...% + 0x88, //%...% + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x00, //..... + +/*Unicode: U+005f (_) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0xf8, //%%%%% + +/*Unicode: U+0060 (`) , Width: 2 */ + 0x00, //.. + 0x80, //%. + 0x40, //.% + 0x00, //.. + 0x00, //.. + 0x00, //.. + 0x00, //.. + 0x00, //.. + 0x00, //.. + +/*Unicode: U+0061 (a) , Width: 6 */ + 0x00, //...... + 0x00, //...... + 0x00, //...... + 0x70, //.%%%.. + 0x08, //....%. + 0x78, //.%%%%. + 0x88, //%...%. + 0x74, //.%%%.% + 0x00, //...... + +/*Unicode: U+0062 (b) , Width: 5 */ + 0x00, //..... + 0x80, //%.... + 0x80, //%.... + 0xf0, //%%%%. + 0x88, //%...% + 0x88, //%...% + 0xc8, //%%..% + 0xb0, //%.%%. + 0x00, //..... + +/*Unicode: U+0063 (c) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x80, //%.... + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0064 (d) , Width: 5 */ + 0x00, //..... + 0x08, //....% + 0x08, //....% + 0x78, //.%%%% + 0x88, //%...% + 0x88, //%...% + 0x98, //%..%% + 0x68, //.%%.% + 0x00, //..... + +/*Unicode: U+0065 (e) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0xf8, //%%%%% + 0x80, //%.... + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0066 (f) , Width: 5 */ + 0x00, //..... + 0x30, //..%%. + 0x40, //.%... + 0xe0, //%%%.. + 0x40, //.%... + 0x40, //.%... + 0x40, //.%... + 0x40, //.%... + 0x00, //..... + +/*Unicode: U+0067 (g) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0x78, //.%%%% + 0x08, //....% + 0x70, //.%%%. + +/*Unicode: U+0068 (h) , Width: 5 */ + 0x00, //..... + 0x80, //%.... + 0x80, //%.... + 0xb0, //%.%%. + 0xc8, //%%..% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x00, //..... + +/*Unicode: U+0069 (i) , Width: 3 */ + 0x00, //... + 0x40, //.%. + 0x00, //... + 0xc0, //%%. + 0x40, //.%. + 0x40, //.%. + 0x40, //.%. + 0xe0, //%%% + 0x00, //... + +/*Unicode: U+006a (j) , Width: 3 */ + 0x00, //... + 0x20, //..% + 0x00, //... + 0x20, //..% + 0x20, //..% + 0x20, //..% + 0x20, //..% + 0x20, //..% + 0xc0, //%%. + +/*Unicode: U+006b (k) , Width: 4 */ + 0x00, //.... + 0x80, //%... + 0x80, //%... + 0x90, //%..% + 0xa0, //%.%. + 0xc0, //%%.. + 0xa0, //%.%. + 0x90, //%..% + 0x00, //.... + +/*Unicode: U+006c (l) , Width: 2 */ + 0x00, //.. + 0xc0, //%% + 0x40, //.% + 0x40, //.% + 0x40, //.% + 0x40, //.% + 0x40, //.% + 0x40, //.% + 0x00, //.. + +/*Unicode: U+006d (m) , Width: 7 */ + 0x00, //....... + 0x00, //....... + 0x00, //....... + 0xec, //%%%.%%. + 0x92, //%..%..% + 0x92, //%..%..% + 0x92, //%..%..% + 0x92, //%..%..% + 0x00, //....... + +/*Unicode: U+006e (n) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0xb0, //%.%%. + 0xc8, //%%..% + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x00, //..... + +/*Unicode: U+006f (o) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x70, //.%%%. + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x70, //.%%%. + 0x00, //..... + +/*Unicode: U+0070 (p) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0xf0, //%%%%. + 0x88, //%...% + 0x88, //%...% + 0xf0, //%%%%. + 0x80, //%.... + 0x80, //%.... + +/*Unicode: U+0071 (q) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x78, //.%%%% + 0x88, //%...% + 0x88, //%...% + 0x78, //.%%%% + 0x08, //....% + 0x08, //....% + +/*Unicode: U+0072 (r) , Width: 4 */ + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0xb0, //%.%% + 0xc0, //%%.. + 0x80, //%... + 0x80, //%... + 0x80, //%... + 0x00, //.... + +/*Unicode: U+0073 (s) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x78, //.%%%% + 0x80, //%.... + 0x70, //.%%%. + 0x08, //....% + 0xf0, //%%%%. + 0x00, //..... + +/*Unicode: U+0074 (t) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x20, //..%.. + 0xf8, //%%%%% + 0x20, //..%.. + 0x20, //..%.. + 0x20, //..%.. + 0x10, //...%. + 0x00, //..... + +/*Unicode: U+0075 (u) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x98, //%..%% + 0x68, //.%%.% + 0x00, //..... + +/*Unicode: U+0076 (v) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x50, //.%.%. + 0x20, //..%.. + 0x00, //..... + +/*Unicode: U+0077 (w) , Width: 7 */ + 0x00, //....... + 0x00, //....... + 0x00, //....... + 0x92, //%..%..% + 0x92, //%..%..% + 0x92, //%..%..% + 0x92, //%..%..% + 0x6c, //.%%.%%. + 0x00, //....... + +/*Unicode: U+0078 (x) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x88, //%...% + 0x50, //.%.%. + 0x20, //..%.. + 0x50, //.%.%. + 0x88, //%...% + 0x00, //..... + +/*Unicode: U+0079 (y) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0x88, //%...% + 0x88, //%...% + 0x88, //%...% + 0x78, //.%%%% + 0x08, //....% + 0x70, //.%%%. + +/*Unicode: U+007a (z) , Width: 5 */ + 0x00, //..... + 0x00, //..... + 0x00, //..... + 0xf8, //%%%%% + 0x10, //...%. + 0x20, //..%.. + 0x40, //.%... + 0xf8, //%%%%% + 0x00, //..... + +/*Unicode: U+007b ({) , Width: 3 */ + 0x00, //... + 0x00, //... + 0x20, //..% + 0x40, //.%. + 0x40, //.%. + 0x80, //%.. + 0x40, //.%. + 0x40, //.%. + 0x20, //..% + +/*Unicode: U+007c (|) , Width: 1 */ + 0x00, //. + 0x80, //% + 0x80, //% + 0x80, //% + 0x80, //% + 0x80, //% + 0x80, //% + 0x80, //% + 0x80, //% + +/*Unicode: U+007d (}) , Width: 3 */ + 0x00, //... + 0x00, //... + 0x80, //%.. + 0x40, //.%. + 0x40, //.%. + 0x20, //..% + 0x40, //.%. + 0x40, //.%. + 0x80, //%.. + +/*Unicode: U+007e (~) , Width: 4 */ + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x50, //.%.% + 0xa0, //%.%. + 0x00, //.... + 0x00, //.... + 0x00, //.... + 0x00, //.... + + }; + +const uint8_t atlantis_glyph_widths[] = + { + 2, //4, /*Unicode: U+0020 ( )*/ + 1, /*Unicode: U+0021 (!)*/ + 3, /*Unicode: U+0022 (")*/ + 6, /*Unicode: U+0023 (#)*/ + 5, /*Unicode: U+0024 ($)*/ + 8, /*Unicode: U+0025 (%)*/ + 7, /*Unicode: U+0026 (&)*/ + 1, /*Unicode: U+0027 (')*/ + 3, /*Unicode: U+0028 (()*/ + 3, /*Unicode: U+0029 ())*/ + 5, /*Unicode: U+002a (*)*/ + 5, /*Unicode: U+002b (+)*/ + 2, /*Unicode: U+002c (,)*/ + 3, /*Unicode: U+002d (-)*/ + 1, /*Unicode: U+002e (.)*/ + 3, /*Unicode: U+002f (/)*/ + 5, /*Unicode: U+0030 (0)*/ + 5, /*Unicode: U+0031 (1)*/ + 5, /*Unicode: U+0032 (2)*/ + 5, /*Unicode: U+0033 (3)*/ + 5, /*Unicode: U+0034 (4)*/ + 5, /*Unicode: U+0035 (5)*/ + 5, /*Unicode: U+0036 (6)*/ + 5, /*Unicode: U+0037 (7)*/ + 5, /*Unicode: U+0038 (8)*/ + 5, /*Unicode: U+0039 (9)*/ + 1, /*Unicode: U+003a (:)*/ + 2, /*Unicode: U+003b (;)*/ + 5, /*Unicode: U+003c (<)*/ + 4, /*Unicode: U+003d (=)*/ + 5, /*Unicode: U+003e (>)*/ + 5, /*Unicode: U+003f (?)*/ + 8, /*Unicode: U+0040 (@)*/ + 5, /*Unicode: U+0041 (A)*/ + 5, /*Unicode: U+0042 (B)*/ + 5, /*Unicode: U+0043 (C)*/ + 5, /*Unicode: U+0044 (D)*/ + 6, /*Unicode: U+0045 (E)*/ + 5, //6 /*Unicode: U+0046 (F)*/ + 5, /*Unicode: U+0047 (G)*/ + 5, /*Unicode: U+0048 (H)*/ + 3, /*Unicode: U+0049 (I)*/ + 5, /*Unicode: U+004a (J)*/ + 5, /*Unicode: U+004b (K)*/ + 6, /*Unicode: U+004c (L)*/ + 7, /*Unicode: U+004d (M)*/ + 6, /*Unicode: U+004e (N)*/ + 5, /*Unicode: U+004f (O)*/ + 5, /*Unicode: U+0050 (P)*/ + 5, /*Unicode: U+0051 (Q)*/ + 5, /*Unicode: U+0052 (R)*/ + 5, /*Unicode: U+0053 (S)*/ + 4, //5, /*Unicode: U+0054 (T)*/ + 5, /*Unicode: U+0055 (U)*/ + 7, /*Unicode: U+0056 (V)*/ + 7, /*Unicode: U+0057 (W)*/ + 5, /*Unicode: U+0058 (X)*/ + 5, /*Unicode: U+0059 (Y)*/ + 5, /*Unicode: U+005a (Z)*/ + 2, /*Unicode: U+005b ([)*/ + 3, /*Unicode: U+005c (\)*/ + 2, /*Unicode: U+005d (])*/ + 5, /*Unicode: U+005e (^)*/ + 5, /*Unicode: U+005f (_)*/ + 2, /*Unicode: U+0060 (`)*/ + 5, //6, /*Unicode: U+0061 (a)*/ + 5, /*Unicode: U+0062 (b)*/ + 5, /*Unicode: U+0063 (c)*/ + 5, /*Unicode: U+0064 (d)*/ + 5, /*Unicode: U+0065 (e)*/ + 4, //5, /*Unicode: U+0066 (f)*/ + 5, /*Unicode: U+0067 (g)*/ + 5, /*Unicode: U+0068 (h)*/ + 3, /*Unicode: U+0069 (i)*/ + 3, /*Unicode: U+006a (j)*/ + 4, /*Unicode: U+006b (k)*/ + 2, /*Unicode: U+006c (l)*/ + 7, /*Unicode: U+006d (m)*/ + 5, /*Unicode: U+006e (n)*/ + 5, /*Unicode: U+006f (o)*/ + 5, /*Unicode: U+0070 (p)*/ + 5, /*Unicode: U+0071 (q)*/ + 4, /*Unicode: U+0072 (r)*/ + 5, /*Unicode: U+0073 (s)*/ + 5, /*Unicode: U+0074 (t)*/ + 5, /*Unicode: U+0075 (u)*/ + 5, /*Unicode: U+0076 (v)*/ + 7, /*Unicode: U+0077 (w)*/ + 5, /*Unicode: U+0078 (x)*/ + 5, /*Unicode: U+0079 (y)*/ + 5, /*Unicode: U+007a (z)*/ + 3, /*Unicode: U+007b ({)*/ + 1, /*Unicode: U+007c (|)*/ + 3, /*Unicode: U+007d (})*/ + 4, /*Unicode: U+007e (~)*/ + }; + + +// note the above have 2 pixels off the top and 1 off the bottom +#if 0 +/*Store the glyph descriptions*/ +static const lv_font_glyph_dsc_t atlantis_glyph_dsc[] = + { + {.w_px = 4, .glyph_index = 0}, /*Unicode: U+0020 ( )*/ + {.w_px = 1, .glyph_index = 12}, /*Unicode: U+0021 (!)*/ + {.w_px = 3, .glyph_index = 24}, /*Unicode: U+0022 (")*/ + {.w_px = 6, .glyph_index = 36}, /*Unicode: U+0023 (#)*/ + {.w_px = 5, .glyph_index = 48}, /*Unicode: U+0024 ($)*/ + {.w_px = 8, .glyph_index = 60}, /*Unicode: U+0025 (%)*/ + {.w_px = 7, .glyph_index = 72}, /*Unicode: U+0026 (&)*/ + {.w_px = 1, .glyph_index = 84}, /*Unicode: U+0027 (')*/ + {.w_px = 3, .glyph_index = 96}, /*Unicode: U+0028 (()*/ + {.w_px = 3, .glyph_index = 108}, /*Unicode: U+0029 ())*/ + {.w_px = 5, .glyph_index = 120}, /*Unicode: U+002a (*)*/ + {.w_px = 5, .glyph_index = 132}, /*Unicode: U+002b (+)*/ + {.w_px = 2, .glyph_index = 144}, /*Unicode: U+002c (,)*/ + {.w_px = 3, .glyph_index = 156}, /*Unicode: U+002d (-)*/ + {.w_px = 1, .glyph_index = 168}, /*Unicode: U+002e (.)*/ + {.w_px = 3, .glyph_index = 180}, /*Unicode: U+002f (/)*/ + {.w_px = 5, .glyph_index = 192}, /*Unicode: U+0030 (0)*/ + {.w_px = 5, .glyph_index = 204}, /*Unicode: U+0031 (1)*/ + {.w_px = 5, .glyph_index = 216}, /*Unicode: U+0032 (2)*/ + {.w_px = 5, .glyph_index = 228}, /*Unicode: U+0033 (3)*/ + {.w_px = 5, .glyph_index = 240}, /*Unicode: U+0034 (4)*/ + {.w_px = 5, .glyph_index = 252}, /*Unicode: U+0035 (5)*/ + {.w_px = 5, .glyph_index = 264}, /*Unicode: U+0036 (6)*/ + {.w_px = 5, .glyph_index = 276}, /*Unicode: U+0037 (7)*/ + {.w_px = 5, .glyph_index = 288}, /*Unicode: U+0038 (8)*/ + {.w_px = 5, .glyph_index = 300}, /*Unicode: U+0039 (9)*/ + {.w_px = 1, .glyph_index = 312}, /*Unicode: U+003a (:)*/ + {.w_px = 2, .glyph_index = 324}, /*Unicode: U+003b (;)*/ + {.w_px = 5, .glyph_index = 336}, /*Unicode: U+003c (<)*/ + {.w_px = 4, .glyph_index = 348}, /*Unicode: U+003d (=)*/ + {.w_px = 5, .glyph_index = 360}, /*Unicode: U+003e (>)*/ + {.w_px = 5, .glyph_index = 372}, /*Unicode: U+003f (?)*/ + {.w_px = 8, .glyph_index = 384}, /*Unicode: U+0040 (@)*/ + {.w_px = 5, .glyph_index = 396}, /*Unicode: U+0041 (A)*/ + {.w_px = 5, .glyph_index = 408}, /*Unicode: U+0042 (B)*/ + {.w_px = 5, .glyph_index = 420}, /*Unicode: U+0043 (C)*/ + {.w_px = 5, .glyph_index = 432}, /*Unicode: U+0044 (D)*/ + {.w_px = 6, .glyph_index = 444}, /*Unicode: U+0045 (E)*/ + {.w_px = 6, .glyph_index = 456}, /*Unicode: U+0046 (F)*/ + {.w_px = 5, .glyph_index = 468}, /*Unicode: U+0047 (G)*/ + {.w_px = 5, .glyph_index = 480}, /*Unicode: U+0048 (H)*/ + {.w_px = 3, .glyph_index = 492}, /*Unicode: U+0049 (I)*/ + {.w_px = 5, .glyph_index = 504}, /*Unicode: U+004a (J)*/ + {.w_px = 5, .glyph_index = 516}, /*Unicode: U+004b (K)*/ + {.w_px = 6, .glyph_index = 528}, /*Unicode: U+004c (L)*/ + {.w_px = 7, .glyph_index = 540}, /*Unicode: U+004d (M)*/ + {.w_px = 6, .glyph_index = 552}, /*Unicode: U+004e (N)*/ + {.w_px = 5, .glyph_index = 564}, /*Unicode: U+004f (O)*/ + {.w_px = 5, .glyph_index = 576}, /*Unicode: U+0050 (P)*/ + {.w_px = 5, .glyph_index = 588}, /*Unicode: U+0051 (Q)*/ + {.w_px = 5, .glyph_index = 600}, /*Unicode: U+0052 (R)*/ + {.w_px = 5, .glyph_index = 612}, /*Unicode: U+0053 (S)*/ + {.w_px = 5, .glyph_index = 624}, /*Unicode: U+0054 (T)*/ + {.w_px = 5, .glyph_index = 636}, /*Unicode: U+0055 (U)*/ + {.w_px = 7, .glyph_index = 648}, /*Unicode: U+0056 (V)*/ + {.w_px = 7, .glyph_index = 660}, /*Unicode: U+0057 (W)*/ + {.w_px = 5, .glyph_index = 672}, /*Unicode: U+0058 (X)*/ + {.w_px = 5, .glyph_index = 684}, /*Unicode: U+0059 (Y)*/ + {.w_px = 5, .glyph_index = 696}, /*Unicode: U+005a (Z)*/ + {.w_px = 2, .glyph_index = 708}, /*Unicode: U+005b ([)*/ + {.w_px = 3, .glyph_index = 720}, /*Unicode: U+005c (\)*/ + {.w_px = 2, .glyph_index = 732}, /*Unicode: U+005d (])*/ + {.w_px = 5, .glyph_index = 744}, /*Unicode: U+005e (^)*/ + {.w_px = 5, .glyph_index = 756}, /*Unicode: U+005f (_)*/ + {.w_px = 2, .glyph_index = 768}, /*Unicode: U+0060 (`)*/ + {.w_px = 6, .glyph_index = 780}, /*Unicode: U+0061 (a)*/ + {.w_px = 5, .glyph_index = 792}, /*Unicode: U+0062 (b)*/ + {.w_px = 5, .glyph_index = 804}, /*Unicode: U+0063 (c)*/ + {.w_px = 5, .glyph_index = 816}, /*Unicode: U+0064 (d)*/ + {.w_px = 5, .glyph_index = 828}, /*Unicode: U+0065 (e)*/ + {.w_px = 5, .glyph_index = 840}, /*Unicode: U+0066 (f)*/ + {.w_px = 5, .glyph_index = 852}, /*Unicode: U+0067 (g)*/ + {.w_px = 5, .glyph_index = 864}, /*Unicode: U+0068 (h)*/ + {.w_px = 3, .glyph_index = 876}, /*Unicode: U+0069 (i)*/ + {.w_px = 3, .glyph_index = 888}, /*Unicode: U+006a (j)*/ + {.w_px = 4, .glyph_index = 900}, /*Unicode: U+006b (k)*/ + {.w_px = 2, .glyph_index = 912}, /*Unicode: U+006c (l)*/ + {.w_px = 7, .glyph_index = 924}, /*Unicode: U+006d (m)*/ + {.w_px = 5, .glyph_index = 936}, /*Unicode: U+006e (n)*/ + {.w_px = 5, .glyph_index = 948}, /*Unicode: U+006f (o)*/ + {.w_px = 5, .glyph_index = 960}, /*Unicode: U+0070 (p)*/ + {.w_px = 5, .glyph_index = 972}, /*Unicode: U+0071 (q)*/ + {.w_px = 4, .glyph_index = 984}, /*Unicode: U+0072 (r)*/ + {.w_px = 5, .glyph_index = 996}, /*Unicode: U+0073 (s)*/ + {.w_px = 5, .glyph_index = 1008}, /*Unicode: U+0074 (t)*/ + {.w_px = 5, .glyph_index = 1020}, /*Unicode: U+0075 (u)*/ + {.w_px = 5, .glyph_index = 1032}, /*Unicode: U+0076 (v)*/ + {.w_px = 7, .glyph_index = 1044}, /*Unicode: U+0077 (w)*/ + {.w_px = 5, .glyph_index = 1056}, /*Unicode: U+0078 (x)*/ + {.w_px = 5, .glyph_index = 1068}, /*Unicode: U+0079 (y)*/ + {.w_px = 5, .glyph_index = 1080}, /*Unicode: U+007a (z)*/ + {.w_px = 3, .glyph_index = 1092}, /*Unicode: U+007b ({)*/ + {.w_px = 1, .glyph_index = 1104}, /*Unicode: U+007c (|)*/ + {.w_px = 3, .glyph_index = 1116}, /*Unicode: U+007d (})*/ + {.w_px = 4, .glyph_index = 1128}, /*Unicode: U+007e (~)*/ + }; + + +lv_font_t atlantis = + { + .unicode_first = 32, /*First Unicode letter in this font*/ + .unicode_last = 126, /*Last Unicode letter in this font*/ + .h_px = 12, /*Font height in pixels*/ + .glyph_bitmap = atlantis_glyph_bitmap, /*Bitmap of glyphs*/ + .glyph_dsc = atlantis_glyph_dsc, /*Description of glyphs*/ + .glyph_cnt = 95, /*Number of glyphs in the font*/ + .unicode_list = NULL, /*Every character in the font from 'unicode_first' to 'unicode_last'*/ + .get_bitmap = lv_font_get_bitmap_continuous, /*Function pointer to get glyph's bitmap*/ + .get_width = lv_font_get_width_continuous, /*Function pointer to get glyph's width*/ + .bpp = 1, /*Bit per pixel*/ + .monospace = 0, /*Fix width (0: if not used)*/ + .next_page = NULL, /*Pointer to a font extension*/ + }; +#endif diff --git a/khan/z80arm.cpp b/khan/z80arm.cpp new file mode 100644 index 0000000..3d2b3aa --- /dev/null +++ b/khan/z80arm.cpp @@ -0,0 +1,175 @@ +/* +Portable ZX-Spectrum emulator. +Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifdef USE_Z80_ARM + +#ifdef USE_BANKED_MEMORY_ACCESS +// we want somewhere we can dump writes to ROM +#include "hardware/structs/xip_ctrl.h" +#endif +#include "../std.h" +#include "../devices/memory.h" +#include "../devices/ula.h" +#include "../devices/device.h" + +#include "z80arm.h" +#include "../platform/platform.h" +#include "../speccy.h" + +#include "z80khan.h" +#include "hardware/interp.h" + +namespace xZ80 +{ + +//============================================================================= +// eZ80::eZ80 +//----------------------------------------------------------------------------- + eZ80::eZ80(eMemory* _m, eDevices* _d, dword _frame_tacts) + : memory(_m), rom(_d->Get()), ula(_d->Get()), devices(_d) + , frame_tacts(_frame_tacts) +#ifndef NO_USE_REPLAY + fetches(0) +#endif + { + // todo eipos + __builtin_memset(&z80a_resting_state, 0, sizeof(z80a_resting_state)); +#ifndef USE_BANKED_MEMORY_ACCESS + z80a_resting_state.memory_64k = memory->Get(eRom::ROM_48); +#endif +#ifndef NDEBUG +#ifdef ENABLE_BREAKPOINT_IN_DEBUG + z80a_resting_state.bp_addr = -1; +#endif +#endif + } + +#ifdef USE_BANKED_MEMORY_ACCESS + void eZ80::InitMemoryBanks() + { + uint8_t **bank_writes = memory->GetBankWrites(); +#if !PICO_ON_DEVICE + bank_writes[0] = 0; +#else + z80a_resting_state.interp = interp0; + hw_clear_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_ERR_BADWRITE_BITS); + auto bit_dumpster = (uint8_t *)XIP_NOCACHE_NOALLOC_BASE; + bank_writes[0] = bit_dumpster; + + interp0->base[0] = (uintptr_t)memory->GetBankReads(); + interp0->base[1] = 0; + interp1->base[0] = (uintptr_t)bank_writes; + interp1->base[1] = 0; + +#ifndef NDEBUG + printf("%p %p\n", memory->GetBankReads(), bank_writes); + printf("%p %p\n", memory->GetBankReads()[0], bank_writes[0]); + printf("%p %p\n", memory->GetBankReads()[1], bank_writes[1]); + printf("%p %p\n", memory->GetBankReads()[2], bank_writes[2]); + printf("%p %p\n", memory->GetBankReads()[3], bank_writes[3]); +#endif + interp_config c = interp_default_config(); + interp_config_set_shift(&c, 12); + interp_config_set_mask(&c, 2, 3); + interp_set_config(interp0, 0, &c); + interp_set_config(interp1, 0, &c); +#endif + } +#endif +//============================================================================= +// eZ80::Reset +//----------------------------------------------------------------------------- + void eZ80::Reset() + { + assert(!z80a_resting_state.active); +#ifndef NO_USE_FAST_TAPE + HandlerStep(NULL); +#endif +#ifndef USE_HACKED_DEVICE_ABSTRACTION + handler.io = NULL; +#endif + z80a_reset(); + } + void eZ80::Update(int int_len, int* nmi_pending) + { + assert(!z80a_resting_state.active); + //assert(!nmi_pending); + z80a_update(int_len); + } +#ifndef NO_USE_REPLAY + //============================================================================= +// eZ80::Replay +//----------------------------------------------------------------------------- +void eZ80::Replay(int _fetches) +{ + fetches = _fetches; + t = 0; + eipos = -1; + while(fetches > 0) + { + Step(); + } + if(iff1) + Int(); + fetches = 0; +} +#endif +////============================================================================= +//// eZ80::Nmi +////----------------------------------------------------------------------------- +// void eZ80::Nmi() +// { +//#if 1 +// assert(false); +//#else +// push(pc); +// pc = 0x66; +// iff1 = halted = 0; +//#endif +// } + +void z80a_step_hook_func() { +#ifndef NO_USE_FAST_TAPE + static eZ80 *z80; + if (!z80) { + z80 = xPlatform::Handler()->Speccy()->CPU(); + } + assert(z80->HandlerStep()); + z80->HandlerStep()->Z80_Step(z80); +#endif +} + +#ifndef NDEBUG +#ifdef ENABLE_BREAKPOINT_IN_DEBUG + void eZ80::set_breakpoint(int pc_addr) { + assert(!z80a_resting_state.active); + z80a_resting_state.bp_addr = pc_addr; + } +#endif +#endif +}//namespace xZ80 + +#ifndef NDEBUG +void z80a_breakpoint_hit() { + struct _z80a_resting_state *s = &z80a_resting_state; + static int i=0; + printf("%d %d %04x %04x\n", i++, (int)s->t, s->hl, s->af); +} +#endif +#endif diff --git a/khan/z80arm.h b/khan/z80arm.h new file mode 100644 index 0000000..474dc88 --- /dev/null +++ b/khan/z80arm.h @@ -0,0 +1,186 @@ +/* +Portable ZX-Spectrum emulator. +Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef __Z80ARM_H__ +#define __Z80ARM_H__ + +#include "../std.h" +#include "z80khan.h" + +#pragma once + +class eMemory; +class eRom; +class eUla; +class eDevices; + +namespace xZ80 +{ + enum eFlags + { + CF = 0x01, + NF = 0x02, + PV = 0x04, + F3 = 0x08, + HF = 0x10, + F5 = 0x20, + ZF = 0x40, + SF = 0x80 + }; + +//***************************************************************************** +// eZ80 +//----------------------------------------------------------------------------- + void z80a_step_hook_func(); + + class eZ80 + { + public: + eZ80(eMemory* m, eDevices* d, dword frame_tacts); +#ifdef USE_BANKED_MEMORY_ACCESS + void InitMemoryBanks(); // must be called on correct core +#endif + void Reset(); + void Update(int int_len, int* nmi_pending); +#ifndef NO_USE_REPLAY + void Replay(int fetches); +#endif + + dword FrameTacts() const { return frame_tacts; } + dword T() const { + assert(!z80a_resting_state.active); // until we find we need it + return z80a_resting_state.t; + } + +#ifndef USE_HACKED_DEVICE_ABSTRACTION + class eHandlerIo + { + public: + virtual byte Z80_IoRead(word port, int tact) = 0; + }; + void HandlerIo(eHandlerIo* h) { handler.io = h; } + eHandlerIo* HandlerIo() const { return handler.io; } +#endif + + class eHandlerStep + { + public: + virtual void Z80_Step(eZ80* z80) = 0; + }; +#ifndef NO_USE_FAST_TAPE + void HandlerStep(eHandlerStep* h) { + if (h == NULL) { + z80a_resting_state.step_hook_func = 0; + } else { + assert(!z80a_resting_state.active); + z80a_resting_state.step_hook_func = &z80a_step_hook_func; + } + handler.step = h; + } + eHandlerStep* HandlerStep() const { + return handler.step; + } +#endif + + //protected: +// void Int(); + void Nmi(); +// void Step(); +// void StepF(); + +// byte IoRead(word port) const; +// void IoWrite(word port, byte v); +// byte Read(word addr) const; +// byte ReadInc(int& addr) const; +// int Read2(word addr) const; +// int Read2Inc(int& addr) const; +// void Write(word addr, byte v); +// void Write2(word addr, int v); + +// typedef void (eZ80::*CALLFUNC)(); +// typedef byte (eZ80::*CALLFUNCI)(byte); + +#ifndef NDEBUG + void set_breakpoint(int bp_addr); +#endif + protected: + eMemory* memory; + eRom* rom; + eUla* ula; + eDevices* devices; + + struct eHandler { + eHandler() { +#ifndef USE_HACKED_DEVICE_ABSTRACTION + io = NULL; +#endif +#ifndef NO_USE_FAST_TAPE + step = NULL; +#endif + } +#ifndef USE_HACKED_DEVICE_ABSTRACTION + eHandlerIo* io; +#endif +#ifndef NO_USE_FAST_TAPE + eHandlerStep* step; +#endif + } handler; + + int eipos; + const int frame_tacts; // t-states per frame +#ifndef NO_USE_REPLAY + int fetches; // .rzx replay fetches +#endif + + inline struct _z80a_resting_state *get_caller_regs() const { + assert(!z80a_resting_state.active); + return &z80a_resting_state; + } + inline word get_caller_pc() const { + return get_caller_regs()->pc; + } + inline void set_caller_pc(word v) const { + get_caller_regs()->pc = v; + } + inline byte get_caller_a() const { return get_caller_regs()->a; } + inline void set_caller_a(byte v) { get_caller_regs()->a = v; } + inline void set_caller_flag(byte flags) { get_caller_regs()->f |= flags; } + inline byte get_caller_b() const { return get_caller_regs()->b; } + inline void set_caller_b(byte v) { get_caller_regs()->b = v; } + inline byte get_caller_c() const { return get_caller_regs()->c; } + inline void set_caller_bc(word v) { + get_caller_regs()->bc = v; + } + inline word get_caller_de() const { return get_caller_regs()->de; } + inline void set_caller_de(word v) { + get_caller_regs()->de = v; + } + inline word get_caller_ix() const { return get_caller_regs()->ix; } + inline void set_caller_ix(word v) { + get_caller_regs()->ix = v; + } + inline byte get_caller_l() const { return get_caller_regs()->l; } + inline void set_caller_l(byte v) { get_caller_regs()->l = v; } + inline void set_caller_h(byte v) { get_caller_regs()->h = v; } + inline void delta_caller_t(int delta) { get_caller_regs()->t += delta; } + }; + +}//namespace xZ80 + +#endif//__Z80_H__ diff --git a/khan/z80khan.S b/khan/z80khan.S new file mode 100644 index 0000000..985c50c --- /dev/null +++ b/khan/z80khan.S @@ -0,0 +1,1704 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "hardware/regs/sio.h" +.syntax unified +.cpu cortex-m0plus +.thumb + +#define _u(x) x +#define INTERP_OFFSET0(x) ((x) - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP_OFFSET1(x) (INTERP_OFFSET0(x) + SIO_INTERP1_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) + +#undef ENABLE_BREAKPOINT_IN_DEBUG + +#ifndef NDEBUG +#ifdef ENABLE_BREAKPOINT_IN_DEBUG +#define ENABLE_BREAKPOINT +#endif +//#define ENABLE_ACTIVE_ASSERTION +//#define ENABLE_64K_ADDRESS_CHECK +#endif + +// moved everything to data for now (ROM 32K limit), but we need it to be fast anyway +.section .data_z80a,"x" +#define CF 0x01 +#define NF 0x02 +#define PV 0x04 +#define F3 0x08 +#define HF 0x10 +#define F5 0x20 +#define ZF 0x40 +#define SF 0x80 + +#define CF_INDEX 0 +#define NF_INDEX 1 +#define PV_INDEX 2 +#define F3_INDEX 3 +#define HF_INDEX 4 +#define F5_INDEX 5 +#define ZF_INDEX 6 +#define SF_INDEX 7 + + /** + * r0 - + * r1 + * r2 - temp_restricted (a scratch register) + * r3 - temp (either a temporary 16 bit register for intra instruction work, or the current ix/iy for xy functions (none of which need a temp register)) + * r4 - pc + * r5 - memory (either a single 64k block, or an array of 8 pointers to 16K blocks; 4 for read, 4 for write) dependeing on mode + * r6 - t + * r7 - af + * r8 - bc + * r9 - de + * r10 - hl + * r11 - memptr + * r12 - sp + */ + +#define r_temp_restricted r2 +#define r_temp r3 +#define r_ixy r3 + +#define r_pc r4 +#ifndef USE_BANKED_MEMORY_ACCESS +#define memory r5 +#else +#define interp_base r5 +#endif +#define r_t r6 +#define r_af r7 +#define r_bc r8 +#define r_de r9 +#define r_hl r10 +#define r_memptr r11 +#define r_sp r12 + +.macro preserve_only_flags reg flags:vararg + movs \reg, #255 ^ (\flags) + bics r_af, \reg +.endm + +.macro read8_internal addr_reg +#ifdef ENABLE_64K_ADDRESS_CHECK +.ifc r0,\addr_reg + push {r0} +.endif + lsrs r0, \addr_reg, #16 +.ifc r0,\addr_reg + pop {r0} +.endif + beq 8f + bkpt #0 +8: +#endif +#ifndef USE_BANKED_MEMORY_ACCESS + ldrb r0, [memory, \addr_reg] +#else + str \addr_reg, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r1, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r1, [r1] + ldrb r0, [r1, \addr_reg] +#endif +.endm + +.macro write8_internal_r1 +#ifdef ENABLE_64K_ADDRESS_CHECK + lsrs r2, r1, #16 + beq 8f + bkpt #0 +8: +#endif +#ifndef USE_BANKED_MEMORY_ACCESS + lsrs r2, r1, #14 + beq 1f + strb r0, [memory, r1] +1: +#else + // if we can spare 16k we can use it as a dumpground for writes to ROM + str r1, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r2, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r2, [r2] + strb r0, [r2, r1] +#endif +.endm + +// addr_reg may not be r0 +// seems like r2 is spare +.macro read8inc_internal addr_reg +#ifdef ENABLE_64K_ADDRESS_CHECK + lsrs r0, \addr_reg, #16 + beq 8f + bkpt #0 +8: +#endif +#ifndef USE_BANKED_MEMORY_ACCESS + ldrb r0, [memory, \addr_reg] +#else + // todo one offset for read, one for write... we can do a dump bank + str \addr_reg, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r0, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r0, [r0] + ldrb r0, [r0, \addr_reg] +#endif + adds \addr_reg, #1 + uxth \addr_reg, \addr_reg +.endm + +.macro read16_internal_r0 +#ifdef ENABLE_64K_ADDRESS_CHECK + lsrs r1, r0, #16 + beq 8f + bkpt #0 +8: +#endif + lsrs r1, r0, #1 + bcs 1f // unaligned +#ifndef USE_BANKED_MEMORY_ACCESS + ldrh r0, [memory, r0] + b 2f +1: + ldrb r1, [memory, r0] + adds r0, #1 + uxth r0, r0 + ldrb r0, [memory, r0] + lsls r0, #8 + orrs r0, r1 +#else + str r0, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r1, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r1, [r1] + ldrh r0, [r1, r0] + b 2f +1: + str r0, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r1, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r1, [r1] + ldrb r1, [r1, r0] + adds r0, #1 + str r0, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r2, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + uxth r0, r0 ; // we need to wrap the address since we use it as an offset + ldr r2, [r2] + ldrb r0, [r2, r0] + lsls r0, #8 + orrs r0, r1 +#endif +2: +.endm + +.macro read16inc_internal_r1 +#ifdef ENABLE_64K_ADDRESS_CHECK + lsrs r0, r1, #16 + beq 8f + bkpt #0 +8: +#endif + lsrs r0, r1, #1 + bcs 1f // unaligned +#ifndef USE_BANKED_MEMORY_ACCESS + ldrh r0, [memory, r1] + adds r1, #2 + b 2f +1: + ldrb r0, [memory, r1] + adds r1, #1 + uxth r1, r1 + ldrb r2, [memory, r1] + lsls r2, #8 + orrs r0, r2 + adds r1, #1 +#else + str r1, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r0, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r0, [r0] + ldrh r0, [r0, r1] + adds r1, #2 + b 2f +1: + str r1, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r2, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r2, [r2] + ldrb r0, [r2, r1] + adds r1, #1 + str r1, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + uxth r1, r1 + ldr r2, [interp_base, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r2, [r2] + ldrb r2, [r2, r1] + lsls r2, #8 + orrs r0, r2 + adds r1, #1 +#endif +2: + uxth r1, r1 +.endm + +.macro write16_internal_r1 +#ifdef ENABLE_64K_ADDRESS_CHECK + lsrs r2, r1, #16 + beq 8f + bkpt #0 +8: +#endif + lsrs r2, r1, #1 + bcs 1f // unaligned +#ifndef USE_BANKED_MEMORY_ACCESS + lsrs r2, r1, #14 + beq 3f + strh r0, [memory, r1] + b 3f +1: + lsrs r2, r1, #14 + beq 2f // skip first write + strb r0, [memory, r1] +2: + adds r1, #1 + uxth r1, r1 + lsrs r2, r1, #14 + beq 3f // skip second write + lsrs r0, #8 + strb r0, [memory, r1] +3: +#else + str r1, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r2, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r2, [r2] + strh r0, [r2, r1] + b 3f +1: + str r1, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r2, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r2, [r2] + strb r0, [r2, r1] +2: + adds r1, #1 + str r1, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)] + uxth r1, r1 + ldr r2, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r2, [r2] + lsrs r0, #8 + strb r0, [r2, r1] +3: +#endif +.endm + +.macro fetch +#ifndef NO_USE_REPLAY +#error Need: --fetches; +#endif +#ifndef NO_UPDATE_RLOW_IN_FETCH + ldr r2, =z80a_resting_state + ldrb r0, [r2, #(r_low - z80a_resting_state)] + adds r0, #1 + strb r0, [r2, #(r_low - z80a_resting_state)] +#endif + adds r_t, #4 + read8inc_internal r_pc +.endm + +.global z80a_reset +.type z80a_reset,%function +.thumb_func +z80a_reset: + movs r0, #0 + adr r1, z80a_resting_state + str r0, [r1, #(_int_flags - z80a_resting_state)] + str r0, [r1, #(ir_register - z80a_resting_state)] + str r0, [r1, #(_im - z80a_resting_state)] + str r0, [r1, #(saved_r_pc - z80a_resting_state)] + bx lr + +z80a_load_state: + adr r0, saved_register_file_hi +#ifdef ENABLE_ACTIVE_ASSERTION + ldr r1, [r0, #(is_active - saved_register_file_hi)] + cmp r1, #0 + beq 1f + bkpt #0 // assertion fail +1: +#endif + movs r1, #1 + str r1, [r0, #(is_active - saved_register_file_hi)] + ldm r0!, {r1, r2, r3, r4, r5} + mov r_bc, r1 + mov r_de, r2 + mov r_hl, r3 + mov r_memptr, r4 + mov r_sp, r5 + adr r0, saved_register_file_lo + ldm r0!, { r_pc, r5, r_t, r_af} +#ifdef USE_Z80_ARM_OFFSET_T + ldr r2, frame_tacts + subs r_t, r2 +#endif + bx lr + +z80a_unload_state: + adr r0, saved_register_file_lo +#ifdef ENABLE_ACTIVE_ASSERTION + ldr r1, [r0, #(is_active - saved_register_file_lo)] + cmp r1, #1 + beq 1f + bkpt #0 // assertion fail +1: +#endif + movs r1, #0 + str r1, [r0, #(is_active - saved_register_file_lo)] +#ifdef USE_Z80_ARM_OFFSET_T + ldr r2, frame_tacts + adds r_t, r2 +#endif + stm r0!, { r_pc, r5, r_t, r_af} + mov r1, r_bc + mov r2, r_de + mov r3, r_hl + mov r4, r_memptr + mov r5, r_sp + // already at saved_register_file_hi + stm r0!, {r1, r2, r3, r4, r5} + bx lr + +.align 2 +//9: .word z80a_resting_state +.global z80a_resting_state +z80a_resting_state: +// note this layout should match struct in z80a.h +_im: .word 0 +_eipos: .word 0 +_int_flags: + .byte 0 // r_hi; +_iff1: + .byte 0 // iff1; + .byte 0 // iff2; +_halted: + .byte 0 // halted; +// these Z80 registers don't have their own ARM reg +ir_register: +r_low: .byte 0 +i: .byte 0 +.byte 0 +.byte 0 +ix_register: .word 0 +iy_register: .word 0 +// location to save the other registers +saved_register_file_lo: +saved_r_pc: +.word 0 // r4 r_pc +.word 0 // r5 memory +.word 0 // r6 r_t +.word 0 // r7 r_af +// ---- +saved_register_file_hi: +.word 0 // r8 r_bc +.word 0 // r9 r_de +.word 0 // r10 r_hl +.word 0 // r11 r_sp +.word 0 // r12 r_memptr + +.word 0 // alt_af +.word 0 // alt_bc +.word 0 // alt_de +.word 0 // alt_hl +scratch: .word 0 +step_hook_func: .word 0 +is_active: .word 0 // active +#ifdef ENABLE_BREAKPOINT +bp_addr: .word 0 +last_pc: .word 0 +#endif + +.macro dbg_check_breakpoint +#ifdef ENABLE_BREAKPOINT + ldr r0, =z80a_resting_state + ldr r1, [r0, #(bp_addr - z80a_resting_state)] + cmp r_pc, r1 + bne 7f + #if 0 + push {lr} + // want to have data available + bl z80a_unload_state + bl z80a_breakpoint_hit + bl z80a_load_state + pop {r0} + mov lr, r0 + #else + bkpt #0 + #endif +7: + ldr r0, =z80a_resting_state + str r_pc, [r0, #(last_pc - z80a_resting_state)] +#endif +.endm + +.macro step_op_table_in_r_temp +#ifndef NO_USE_DOS +#error rom->Read(pc); +#endif + #(this->*normal_opcodes[Fetch()])(); + fetch + lsls r0, #1 + ldrh r0, [r_temp, r0] + adds r0, r_temp + blx r0 +.endm + +.macro step_op_table_in_r_temp_maybe_neg +#ifndef NO_USE_DOS +#error rom->Read(pc); +#endif + #(this->*normal_opcodes[Fetch()])(); + fetch + lsls r0, #1 + ldrh r0, [r_temp, r0] + sxth r0, r0 + adds r0, r_temp + blx r0 +.endm + +.align 1 + +.global z80a_step +.type z80a_step,%function +.thumb_func +z80a_step: + push {lr} + bl z80a_load_state + dbg_check_breakpoint + ldr r_temp, =op_table + step_op_table_in_r_temp + b z80a_unload_state + pop {pc} + +.section .data_z80a,"x" + +// NOTE: general convention is to preserve anything other than r0-r1 in a function unless it is an explicit side effect + +// r0 = address +// return in r0 +// this is a one instruction thing with single 64K RAM, so inline it everywhere +//#ifndef USE_SINGLE_64K_MEMORY +//read8: +// read8_internal r0 +// bx lr +//#endif + +// r0 = address +// return in r0 +ioread8: + mov r1, r_t + push {r3, lr} + mov r3, r12 + push {r3} +#ifdef USE_Z80_ARM_OFFSET_T + ldr r3, frame_tacts + adds r1, r3 +#endif + // todo caused by code being in data section + ldr r2, =static_device_io_read + blx r2 + pop {r3} + mov r12, r3 + pop {r3, pc} + +// r0 = address +// return value r0 address+1 in r1 +read8inc: + mov r1, r0 + read8inc_internal r1 + bx lr + +// r0 = address +// return value in r0 +read16: + read16_internal_r0 + bx lr + +// r0 = address +// return value r0 address+2 in r1 +read16inc: + mov r1, r0 + read16inc_internal_r1 + bx lr + +// r0 = value +// r1 = address +.thumb_func +write8: + write8_internal_r1 + bx lr + +// r0 = value +// r1 = address +.thumb_func +iowrite8: + mov r2, r_t + push {r3, lr} + mov r3, r12 + push {r3} +#ifdef USE_Z80_ARM_OFFSET_T + ldr r3, frame_tacts + adds r2, r3 +#endif +// todo caused by code being in data section + ldr r3, =static_device_io_write + blx r3 + pop {r3} + mov r12, r3 + pop {r3, pc} + +.ltorg + +// r0 = value +// r1 = address +.thumb_func +write16: + write16_internal_r1 + bx lr + +.thumb_func +_push: + mov r2, r_sp + subs r2, #2 + uxth r2, r2 + mov r_sp, r2 + + lsrs r1, r2, #1 + bcs 1f // misaligned + +#ifndef USE_BANKED_MEMORY_ACCESS + lsrs r1, r2, #14 + beq 3f + strh r0, [memory, r2] + bx lr + +1: + lsrs r1, r2, #14 + beq 2f + strb r0, [memory, r2] +2: + adds r2, #1 + uxth r2, r2 + lsrs r1, r2, #14 + beq 3f + + lsrs r0, #8 + strb r0, [memory, r2] +#else + str r2, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r1, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r1, [r1] + strh r0, [r1, r2] + bx lr +1: + str r2, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r1, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r1, [r1] + strb r0, [r1, r2] +2: + adds r2, #1 + str r2, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)] + uxth r2, r2 + ldr r1, [interp_base, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldr r1, [r1] + lsrs r0, #8 + strb r0, [r1, r2] +#endif +3: + bx lr + +.section .data_z80a_arith,"x" +.thumb_func +// Combined subs ands sbcs8 for size at small speed cost for sub +.thumb_func +sub8: + // clear the z80 carry + movs r1, #CF + bics r_af, r1 + +.thumb_func +sbc8: + // toggle the carry (ARM is reversed for subtract) + movs r1, #CF + eors r_af, r1 + + lsrs r2, r_af, #8 // a + lsls r2, #24 // (a << 24) + lsls r0, #24 // (val << 24) + + push {r3} + + // perform subtraction + + // argggh on thumb this updares flags (it is adds r3, r2, #0) + // todo this should no longer be the case with .unified + mov r3, r2 + // set arm carry to !z80 carry + lsrs r1, r_af, #1 + sbcs r3, r0 // (a << 24) - ((val << 24) + c) + + // save away 4 arm flags in low nibble of r1 + mrs r1, apsr + lsrs r1, #28 + + // low nibble of a in high nibble of of r2 + lsls r2, r2, #4 + + // low nibble of val in high nibble of r0 + lsls r0, #4 + + // set arm carry to !z80 carry + lsrs r_af, #1 + + // set flags based on half carry + sbcs r2, r0 + + // set z80 flags based on saved arm flags + // note arm carry flag is preserved across the next two instructions + adr r2, flag_translate_sub_no_ZF + ldrb r_af, [r2, r1] // a = 0; f = corrsect scv (note z is not corrsect because of ffffff in low 24 bits + + // set the half carry (note we set when ARM carry is clear) + bcs 1f + adds r_af, #HF +1: + // set N flag + adds r_af, #NF + // r1 = result + lsrs r1, r3, #24 + + // need to set zero flag according to result + bne 1f + adds r_af, #ZF +1: + + // set F3, F5 from result + movs r3, #F3|F5 + ands r3, r1 + orrs r_af, r3 + + // ... ands store the result in a + lsls r1, r1, #8 + orrs r_af, r1 + + pop {r3} + bx lr + +.thumb_func +neg8: + lsrs r0, r_af, #8 // a + lsls r0, #24 + + // perform subtraction to set the flags + negs r2, r0 // -a + + mrs r_af, apsr + adr r1, flag_translate_sub + lsrs r_af, #28 + ldrb r_af, [r1, r_af] // a = 0; f = corrsect szcv + + lsrs r1, r2, #16 + orrs r_af, r1 // store result + + // set F3, F5 from result + movs r2, #F3|F5 + lsrs r1, #8 + ands r1, r2 + orrs r_af, r1 + + // ands low nibble of val in high nibble of r0 + lsls r0, #4 + + // set flags based on half carry + negs r1, r0 + + // ARM c flag on subtract is opposite of Z80 one + bcc 1f + + // H is now clear, N is set + adds r_af, #NF + bx lr +1: + // H is now set, N is set + adds r_af, #HF|NF + bx lr + +.thumb_func +cp8: + lsrs r2, r_af, #8 // a + lsls r2, #24 + lsls r0, #24 + + // perform subtraction to set the flags + subs r1, r2, r0 // a - val + + mrs r_af, apsr + adr r1, flag_translate_sub + lsrs r_af, #28 + ldrb r_af, [r1, r_af] // a = 0; f = corrsect szcv + + lsrs r1, r2, #16 + orrs r_af, r1 // restore a + + // set F3, F5 from val + movs r1, #F3|F5 + rev r0, r0 + ands r1, r0 + orrs r_af, r1 + + // restore low nibble of a in high nibble of of r1 + lsls r1, r2, #4 + // ands low nibble of val in high nibble of r0 + lsls r0, #28 + + // set flags based on half carry + subs r1, r0 + + // ARM c flag on subtract is opposite of Z80 one + bcc 1f + + // H is now clear, N is set + adds r_af, #NF + bx lr +1: + // H is now set, N is set + adds r_af, #HF|NF + bx lr + +.thumb_func +adc16: + mov r2, r_hl + mvns r2, r2 + lsls r2, #16 + mvns r2, r2 // (a << 16) + 0x0000ffff + + lsls r0, #16 // (val << 16) + + push {r3} + + // perform addition + + // argggh on thumb this updares flags (it is adds r3, r2, #0) + mov r3, r2 + // set arm carry to z80 carry + lsrs r1, r_af, #1 + adcs r3, r0 // (hl << 16) + (val << 16) + c + 0x0000ffff + + // save away 4 arm flags in low nibble of r1 + mrs r1, apsr + lsrs r1, #28 + + // low 3 nibbles of hl in high nibbles of of r2 ands 1s in bits 0 to 19 + lsls r2, r2, #4 + adds r2, #0xf + + // low 3 nibbles of val in high nibbles of r0 + lsls r0, #4 + + // set arm carry to z80 carry + lsrs r_af, #1 + + // set flags based on half carry + adcs r2, r0 + + // set z80 flags based on saved arm flags + // note arm carry flag is preserved across the next two instructions + adr r2, flag_translate_add_no_ZF + ldrb r2, [r2, r1] // r2 = corrsect scv (note z is not corrsect because of ffffff in low 24 bits + + // set the half carry + bcc 1f + adds r2, #HF +1: + + lsrs r_af, #7 + lsls r_af, #8 // af now a:0 + orrs r_af, r2 + + // r1 = result + lsrs r1, r3, #16 + + // need to set zero flag according to result + bne 1f + adds r_af, #ZF +1: + + // set F3, F5 from result + lsrs r0, r1, #8 + movs r2, #F3|F5 + ands r2, r0 + + orrs r_af, r2 + + mov r_hl, r1 + + pop {r3} + bx lr + +.thumb_func +sbc16: + // toggle the carry (ARM is reversed for subtract) + movs r1, #CF + eors r_af, r1 + + mov r2, r_hl + lsls r2, #16 // (hl << 16) + lsls r0, #16 // (val << 16) + + push {r3} + + // perform subtraction + + // argggh on thumb this updares flags (it is adds r3, r2, #0) + mov r3, r2 + // set arm carry to !z80 carry + lsrs r1, r_af, #1 + sbcs r3, r0 // (hl << 16) - ((val << 16) + c) + + // save away 4 arm flags in low nibble of r1 + mrs r1, apsr + lsrs r1, #28 + + // low 3 nibbles of a in high 3 nibbles of of r2 + lsls r2, r2, #4 + + // low nibble of val in high nibble of r0 + lsls r0, #4 + + // set arm carry to !z80 carry + lsrs r_af, #1 + + // set flags based on half carry + sbcs r2, r0 + + // set z80 flags based on saved arm flags + // note arm carry flag is preserved across the next two instructions + adr r2, flag_translate_sub_no_ZF + ldrb r2, [r2, r1] // r2 = corrsect scv (note z is not corrsect because of ffffff in low 24 bits + + // set the half carry (note we set when ARM carry is clear) + bcs 1f + adds r2, #HF +1: + + lsrs r_af, #7 + lsls r_af, #8 // af now a:0 + orrs r_af, r2 + + // set N flag + adds r_af, #NF + + // r1 = result + lsrs r1, r3, #16 + + // need to set zero flag according to result + bne 1f + adds r_af, #ZF +1: + // set F3, F5 from result hi byte + lsrs r0, r1, #8 + movs r2, #F3|F5 + ands r2, r0 + orrs r_af, r2 + + mov r_hl, r1 + + pop {r3} + bx lr + +.thumb_func +add16: + lsls r2, r1, #16 + lsls r0, #16 + + push {r3} + preserve_only_flags r3, (SF|ZF|PV) + + adds r3, r2, r0 // v0 + v1 + + bcc 1f + adds r_af, #CF +1: + + // restore low nibble of a in high nibble of of r1 + lsls r1, r2, #4 + // ands low nibble of val in high nibble of r0 + lsls r3, r0, #4 + + // set flags based on half carry + adds r1, r3 + + bcc 2f + + movs r1, #HF + orrs r_af, r1 +2: + + pop {r3} + + // redo addition (cheaper than push/pop) ands we now have result in r0 + adds r0, r2 + lsrs r0, #16 + + // set F3, F5 from result + movs r1, #F3|F5 + lsrs r2, r0, #8 + ands r1, r2 + orrs r_af, r1 + + bx lr + +.align 2 +// arm NZCV to z80 ZVC +flag_translate_sub: + .byte 0 | CF + .byte 0 | CF | PV + .byte 0 + .byte 0 | PV + .byte 0 | ZF | CF + .byte 0 | ZF | CF | PV + .byte 0 | ZF + .byte 0 | ZF | | PV + + .byte SF | CF + .byte SF | CF | PV + .byte SF + .byte SF | | PV + .byte SF | ZF | CF + .byte SF | ZF | CF | PV + .byte SF | ZF + .byte SF | ZF | | PV + +.align 2 +// arm NZCV to z80 SVC +flag_translate_sub_no_ZF: + .byte 0 | CF + .byte 0 | CF | PV + .byte 0 + .byte 0 | PV + .byte 0 | CF + .byte 0 | CF | PV + .byte 0 + .byte 0 | PV + + .byte SF | CF + .byte SF | CF | PV + .byte SF + .byte SF | PV + .byte SF | CF + .byte SF | CF | PV + .byte SF + .byte SF | PV + +// Combined adds ands adcs8 for size at small speed cost for add +.thumb_func +add8: + // clear the z80 carry + movs r1, #CF + bics r_af, r1 + +.thumb_func +adc8: + lsrs r2, r_af, #8 // a + mvns r2, r2 + lsls r2, #24 + mvns r2, r2 // (a << 24) + 0x00ffffff + + lsls r0, #24 // (val << 24) + + push {r3} + + // perform addition + + // argggh on thumb this updares flags (it is adds r3, r2, #0) + mov r3, r2 + // set arm carry to z80 carry + lsrs r1, r_af, #1 + adcs r3, r0 // (a << 24) + (val << 24) + c + 0x00ffffff + + // save away 4 arm flags in low nibble of r1 + mrs r1, apsr + lsrs r1, #28 + + // low nibble of a in high nibble of of r2 ands 1s in bits 0 to 27 + lsls r2, r2, #4 + adds r2, #0xf + + // low nibble of val in high nibble of r0 + lsls r0, #4 + + // set arm carry to z80 carry + lsrs r_af, #1 + + // set flags based on half carry + adcs r2, r0 + + // set z80 flags based on saved arm flags + // note arm carry flag is preserved across the next two instructions + adr r2, flag_translate_add_no_ZF + ldrb r_af, [r2, r1] // a = 0; f = corrsect scv (note z is not corrsect because of ffffff in low 24 bits + + // set the half carry + bcc 1f + adds r_af, #HF +1: + + // r1 = result + lsrs r1, r3, #24 + + // need to set zero flag according to result + bne 1f + adds r_af, #ZF +1: + + // set F3, F5 from result + movs r3, #F3|F5 + ands r3, r1 + orrs r_af, r3 + + // ... ands store the result in a + lsls r1, r1, #8 + orrs r_af, r1 + + pop {r3} + bx lr + +.align 2 +flag_translate_add_no_ZF: + .byte 0 + .byte 0 | PV + .byte 0 | CF + .byte 0 | CF | PV + .byte 0 + .byte 0 | PV + .byte 0 | CF + .byte 0 | CF | PV + + .byte SF + .byte SF | PV + .byte SF | CF + .byte SF | CF | PV + .byte SF + .byte SF | PV + .byte SF | CF + .byte SF | CF | PV + +.thumb_func +set_af35_special_r_temp: + // (tempbyte & F3) + ((tempbyte << 4) & F5); + movs r0, #F3 + ands r0, r_temp + orrs r_af, r0 + lsrs r0, r_temp, #2 + bcc 1f + movs r0, #F5 + orrs r_af, r0 +1: + bx lr + +.section .data_z70a_op,"x" +.align 1 +.thumb_func +// instruction prefix hell!! +opDDFD: +/** + byte op1; // last DD/FD prefix + do + { + op1 = opcode; + opcode = Fetch(); + } while((opcode | 0x20) == 0xFD); // opcode == DD/FD +*/ +1: + mov r1, r0 + fetch + cmp r0, #0xed + bne 4f + // ED + ldr r0, =oped + bx r0 +4: + cmp r0, #0xdd + beq 1b + cmp r0, #0xfd + beq 1b + ldr r2, =z80a_resting_state + ldr r_temp, =opxy_table + cmp r1, #0xfd + beq 3f + // DD + cmp r0, #0xcb + bne 4f + ldr r1, [r2, #(ix_register - z80a_resting_state)] + read8inc_internal r_pc + sxtb r0, r0 + adds r0, r1 + mov r_memptr, r0 + b 2f +4: + lsls r0, #1 + ldrh r0, [r_temp, r0] + sxth r0, r0 + adds r0, r_temp + // note r_ixy == r_temp so we can't do this above + ldr r_ixy, [r2, #(ix_register - z80a_resting_state)] + push {lr} + blx r0 + ldr r2, =z80a_resting_state + str r_ixy, [r2, #(ix_register - z80a_resting_state)] + pop {pc} +3: + // FD + cmp r0, #0xcb + bne 4f + ldr r1, [r2, #(iy_register - z80a_resting_state)] + read8inc_internal r_pc + sxtb r0, r0 + adds r0, r1 + mov r_memptr, r0 + b 2f +4: + lsls r0, #1 + ldrh r0, [r_temp, r0] + sxth r0, r0 + adds r0, r_temp + // note r_ixy == r_temp so we can't do this above + ldr r_ixy, [r2, #(iy_register - z80a_resting_state)] + push {lr} + blx r0 + ldr r2, =z80a_resting_state + str r_ixy, [r2, #(iy_register - z80a_resting_state)] + pop {pc} +2: + // (FD/DD)CB + ldr r_temp, =opli_m35_table + // not doing fetch here because of something about not incrementing r (although we don't do that anyway atm) + read8inc_internal r_pc + // this is rolled into the ddcb/ddcbit functions + // adds r_t, #4 + + lsrs r2, r0, #3 + lsls r2, #1 + ldrh r2, [r_temp, r2] + sxth r2, r2 + adds r2, r_temp + + ldr r_temp, =opddcb_X_table + lsrs r1, r0, #7 + // arg no single test for this flag combo; basically this (with the shift above) is 0x40 == (r0 & 0xc0) i.e. is this bitmem() + bcc 1f + bne 1f + // note that bitmem has different wrapper functions that don't update memory (ands increment t differently) + adds r_temp, #(opddcb_bitX_table - opddcb_X_table) +1: + movs r1, #7 + ands r0, r1 + lsls r0, #1 + ldrh r1, [r_temp, r0] + sxth r1, r1 + adds r1, r_temp + bx r1 +.ltorg + +/** + + if(opcode == 0xCB) + { + dword ptr; // pointer to DDCB operands + ptr = ((op1 == 0xDD) ? ix : iy) + (signed char)ReadInc(pc); + memptr = ptr; + // DDCBnnXX,FDCBnnXX increment R by 2, not 3! + opcode = ReadInc(pc); + t += 4; + byte v = (this->*logic_ix_opcodes[opcode])(Read(ptr)); + if((opcode & 0xC0) == 0x40)// bit n,rm + { + t += 8; + return; + } + // select destination register for shift/res/set + (this->*reg_offset[opcode & 7]) = v; // ??? + Write(ptr, v); + t += 11; + return; + } + if(opcode == 0xED) + { + OpED(); + return; + } + // one prefix: DD/FD + op1 == 0xDD ? (this->*ix_opcodes[opcode])() : (this->*iy_opcodes[opcode])(); + */ + +.section .data_z80a_inc,"x" +.thumb_func +inc8: + preserve_only_flags r1, CF + adr r1, _incf + ldrb r1, [r1, r0] + orrs r_af, r1 + adds r0, #1 + uxtb r0, r0 + bx lr + +.section .data_z80a_dec,"x" +.thumb_func +dec8: + preserve_only_flags r1, CF + adr r1, _decf + ldrb r1, [r1, r0] + orrs r_af, r1 + subs r0, #1 + uxtb r0, r0 + bx lr + +.section .data_z80a_logic,"x" +.thumb_func +ands8: + // r0 is value to ands in low 8 + lsrs r_af, #8 + ands r_af, r0 + adr r1, _log_f + ldrb r0, [r1, r_af] + lsls r_af, #8 + movs r1, #HF + orrs r_af, r0 + orrs r_af, r1 + bx lr + +.thumb_func +or8: + // r0 is value to ands in low 8 + lsrs r_af, #8 + orrs r_af, r0 + adr r1, _log_f + ldrb r0, [r1, r_af] + lsls r_af, #8 + orrs r_af, r0 + bx lr + +.thumb_func +xor8: + // r0 is value to ands in low 8 + lsrs r_af, #8 + eors r_af, r0 + adr r1, _log_f + ldrb r0, [r1, r_af] + lsls r_af, #8 + orrs r_af, r0 + bx lr + +.thumb_func +rlc8: + lsrs r_af, #8 + lsls r_af, #8 + + movs r1, #0 + lsls r0, #25 + bcc 1f + movs r1, #1 + orrs r_af, r1 +1: + lsrs r0, #24 + orrs r0, r1 + + adr r1, _log_f + ldrb r1, [r1, r0] + orrs r_af, r1 + + bx lr + +.thumb_func +rl8: + movs r1, #CF + ands r1, r_af + lsrs r_af, #8 + lsls r_af, #8 + + lsls r0, #25 + bcc 1f + movs r2, #CF + orrs r_af, r2 +1: + lsrs r0, #24 + orrs r0, r1 + + adr r1, _log_f + ldrb r1, [r1, r0] + orrs r_af, r1 + + bx lr + +.thumb_func +rr8: + movs r1, #CF + ands r1, r_af + lsrs r_af, #8 + lsls r_af, #8 + + lsrs r0, #1 + bcc 1f + movs r2, #CF + orrs r_af, r2 +1: + lsls r1, #7 + orrs r0, r1 + + adr r1, _log_f + ldrb r1, [r1, r0] + orrs r_af, r1 + + bx lr + +.thumb_func +sla8: + lsrs r_af, #8 + lsls r_af, #8 + + lsls r0, #25 + bcc 1f + movs r1, #CF + orrs r_af, r1 +1: + lsrs r0, #24 + + adr r1, _log_f + ldrb r1, [r1, r0] + orrs r_af, r1 + + bx lr + +.thumb_func +sli8: + movs r1, #CF + lsrs r_af, #8 + lsls r_af, #8 + + lsls r0, #25 + bcc 1f + orrs r_af, r1 +1: + lsrs r0, #24 + orrs r0, r1 + + adr r1, _log_f + ldrb r1, [r1, r0] + orrs r_af, r1 + + bx lr + + +.thumb_func +sra8: + lsrs r_af, #8 + lsls r_af, #8 + + lsls r0, #24 + asrs r0, #25 + + bcc 1f + movs r1, #CF + orrs r_af, r1 +1: + uxtb r0, r0 + + adr r1, _log_f + ldrb r1, [r1, r0] + orrs r_af, r1 + + bx lr + +.thumb_func +srl8: + lsrs r_af, #8 + lsls r_af, #8 + + lsrs r0, #1 + bcc 1f + movs r1, #CF + orrs r_af, r1 +1: + adr r1, _log_f + ldrb r1, [r1, r0] + orrs r_af, r1 + + bx lr + +.thumb_func +rrc8: + lsrs r_af, #8 + lsls r_af, #8 + + lsrs r0, #1 + bcc 1f + movs r1, #CF + orrs r_af, r1 + movs r1, #128 + orrs r0, r1 +1: + + adr r1, _log_f + ldrb r1, [r1, r0] + orrs r_af, r1 + + bx lr + +.section .data_z80a_inc +.align 2 +_incf: + .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08 + .byte 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x10 + .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08 + .byte 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x30 + .byte 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28 + .byte 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x30 + .byte 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28 + .byte 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x10 + .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08 + .byte 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x10 + .byte 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08 + .byte 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x30 + .byte 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28 + .byte 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x30 + .byte 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28 + .byte 0x28,0x28,0x28,0x28,0x28,0x28,0x28,0x94 + .byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88 + .byte 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x90 + .byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88 + .byte 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0xb0 + .byte 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa8 + .byte 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xb0 + .byte 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa8 + .byte 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0x90 + .byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88 + .byte 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0x90 + .byte 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x88 + .byte 0x88,0x88,0x88,0x88,0x88,0x88,0x88,0xb0 + .byte 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa8 + .byte 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xb0 + .byte 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa8 + .byte 0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0xa8,0x50 + +.section .data_z80a_dec +.align 2 +_decf: + .byte 0xba,0x42,0x02,0x02,0x02,0x02,0x02,0x02 + .byte 0x02,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a + .byte 0x1a,0x02,0x02,0x02,0x02,0x02,0x02,0x02 + .byte 0x02,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a + .byte 0x1a,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + .byte 0x22,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a + .byte 0x3a,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + .byte 0x22,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a + .byte 0x3a,0x02,0x02,0x02,0x02,0x02,0x02,0x02 + .byte 0x02,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a + .byte 0x1a,0x02,0x02,0x02,0x02,0x02,0x02,0x02 + .byte 0x02,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a,0x0a + .byte 0x1a,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + .byte 0x22,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a + .byte 0x3a,0x22,0x22,0x22,0x22,0x22,0x22,0x22 + .byte 0x22,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a,0x2a + .byte 0x3e,0x82,0x82,0x82,0x82,0x82,0x82,0x82 + .byte 0x82,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a + .byte 0x9a,0x82,0x82,0x82,0x82,0x82,0x82,0x82 + .byte 0x82,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a + .byte 0x9a,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2 + .byte 0xa2,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa + .byte 0xba,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2 + .byte 0xa2,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa + .byte 0xba,0x82,0x82,0x82,0x82,0x82,0x82,0x82 + .byte 0x82,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a + .byte 0x9a,0x82,0x82,0x82,0x82,0x82,0x82,0x82 + .byte 0x82,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a + .byte 0x9a,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2 + .byte 0xa2,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa + .byte 0xba,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2,0xa2 + .byte 0xa2,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa + +.global _log_f + +.section .data_z80a_logic +.align 2 +_log_f: + .byte 0x44,0x00,0x00,0x04,0x00,0x04,0x04,0x00 + .byte 0x08,0x0c,0x0c,0x08,0x0c,0x08,0x08,0x0c + .byte 0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04 + .byte 0x0c,0x08,0x08,0x0c,0x08,0x0c,0x0c,0x08 + .byte 0x20,0x24,0x24,0x20,0x24,0x20,0x20,0x24 + .byte 0x2c,0x28,0x28,0x2c,0x28,0x2c,0x2c,0x28 + .byte 0x24,0x20,0x20,0x24,0x20,0x24,0x24,0x20 + .byte 0x28,0x2c,0x2c,0x28,0x2c,0x28,0x28,0x2c + .byte 0x00,0x04,0x04,0x00,0x04,0x00,0x00,0x04 + .byte 0x0c,0x08,0x08,0x0c,0x08,0x0c,0x0c,0x08 + .byte 0x04,0x00,0x00,0x04,0x00,0x04,0x04,0x00 + .byte 0x08,0x0c,0x0c,0x08,0x0c,0x08,0x08,0x0c + .byte 0x24,0x20,0x20,0x24,0x20,0x24,0x24,0x20 + .byte 0x28,0x2c,0x2c,0x28,0x2c,0x28,0x28,0x2c + .byte 0x20,0x24,0x24,0x20,0x24,0x20,0x20,0x24 + .byte 0x2c,0x28,0x28,0x2c,0x28,0x2c,0x2c,0x28 + .byte 0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84 + .byte 0x8c,0x88,0x88,0x8c,0x88,0x8c,0x8c,0x88 + .byte 0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80 + .byte 0x88,0x8c,0x8c,0x88,0x8c,0x88,0x88,0x8c + .byte 0xa4,0xa0,0xa0,0xa4,0xa0,0xa4,0xa4,0xa0 + .byte 0xa8,0xac,0xac,0xa8,0xac,0xa8,0xa8,0xac + .byte 0xa0,0xa4,0xa4,0xa0,0xa4,0xa0,0xa0,0xa4 + .byte 0xac,0xa8,0xa8,0xac,0xa8,0xac,0xac,0xa8 + .byte 0x84,0x80,0x80,0x84,0x80,0x84,0x84,0x80 + .byte 0x88,0x8c,0x8c,0x88,0x8c,0x88,0x88,0x8c + .byte 0x80,0x84,0x84,0x80,0x84,0x80,0x80,0x84 + .byte 0x8c,0x88,0x88,0x8c,0x88,0x8c,0x8c,0x88 + .byte 0xa0,0xa4,0xa4,0xa0,0xa4,0xa0,0xa0,0xa4 + .byte 0xac,0xa8,0xa8,0xac,0xa8,0xac,0xac,0xa8 + .byte 0xa4,0xa0,0xa0,0xa4,0xa0,0xa4,0xa4,0xa0 + .byte 0xa8,0xac,0xac,0xa8,0xac,0xa8,0xa8,0xac + +.section .data_z80a + +// note this is down here to be close to op_table +.global z80a_update +.type z80a_update,%function +.thumb_func +// run until end of frame (t <= frame_tacts) or (t <= 0 if USE_Z80_ARM_OFFSET_T - saves a reg) +z80a_update: + push {r4-r7,lr} + mov r0, r8 + mov r1, r9 + mov r2, r10 + mov r3, r11 + push {r0-r3} + + bl z80a_load_state + + ldr r1, =z80a_resting_state + ldrb r0, [r1, #(_halted - z80a_resting_state)] + + //if(!iff1 && halted) + cmp r0, #0 + bne irqcheck_instruction_loop_test + ldrb r0, [r1, #(_iff1 - z80a_resting_state)] + beq irqcheck_instruction_loop_test + // return + b 9f + // INT check separated from main Z80 loop to improve emulation speed + //while(t < int_len) + //{ +irqcheck_instruction_loop: + // if(iff1 && t != eipos) // int enabled in CPU not issued after EI + // { + // Int(); + // break; + // } + ldrb r0, [r1, #(_iff1 - z80a_resting_state)] + cmp r0, #0 + beq 3f + + ldr r0, [r1, #(_eipos - z80a_resting_state)] +#ifdef USE_Z80_ARM_OFFSET_T + ldr r2, frame_tacts + subs r0, r2 +#endif + cmp r_t, r0 + beq 3f + + bl z80a_interrupt + b 2f +3: + + // Step(); + dbg_check_breakpoint + adr r_temp, op_table + step_op_table_in_r_temp + + ldr r1, =z80a_resting_state + + //if(halted) + // break; + ldrb r0, [r1, #(_halted - z80a_resting_state)] + cmp r0, #0 + bne 2f + +irqcheck_instruction_loop_test: + // note r1 should have z80a_resting_state at this point (even for first iteration) + // } +#ifdef USE_Z80_ARM_OFFSET_T + ldr r2, frame_tacts + adds r2, r_t + cmp r2, #32 +#else + cmp r_t, #32 +#endif + blt irqcheck_instruction_loop + +2: + // eipos = -1 + movs r0, #0 + mvns r0, r0 + str r0, [r1, #(_eipos - z80a_resting_state)] + +hook_instruction_loop: + ldr r0, [r1, #(step_hook_func - z80a_resting_state)] + cmp r0, #0 + beq main_instruction_loop_test + bl z80a_unload_state + ldr r1, =z80a_resting_state + ldr r0, [r1, #(step_hook_func - z80a_resting_state)] + + // call hook func + blx r0 + bl z80a_load_state + + dbg_check_breakpoint + adr r_temp, op_table + step_op_table_in_r_temp + ldr r1, =z80a_resting_state +#ifdef USE_Z80_ARM_OFFSET_T + cmp r_t, #0 +#else + ldr r0, frame_tacts + cmp r_t, r0 +#endif + blt hook_instruction_loop + b 8f + +main_instruction_loop: + dbg_check_breakpoint + adr r_temp, op_table + step_op_table_in_r_temp +main_instruction_loop_test: +#ifdef USE_Z80_ARM_OFFSET_T + cmp r_t, #0 +#else + ldr r0, frame_tacts + cmp r_t, r0 +#endif + blt main_instruction_loop + +8: // finished; restore state + ldr r0, frame_tacts + subs r_t, r0 + ldr r1, =z80a_resting_state + ldr r2, [r1, #(_eipos - z80a_resting_state)] + subs r2, r0 + str r2, [r1, #(_eipos - z80a_resting_state)] + + bl z80a_unload_state + +9: // return + pop {r0-r3} + mov r8, r0 + mov r9, r1 + mov r10, r2 + mov r11, r3 + pop {r4-r7, pc} + +.align 2 +.ltorg +// todo externalize +frame_tacts: .word 71680 + +#include "z80khan_gen.S" \ No newline at end of file diff --git a/khan/z80khan.h b/khan/z80khan.h new file mode 100644 index 0000000..1e1b192 --- /dev/null +++ b/khan/z80khan.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2023 Graham Sanderson + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SOFTWARE_Z80KHAN_H +#define SOFTWARE_Z80KHAN_H + +#ifdef __cplusplus +extern "C" { +#endif +// reset the z80 +extern void z80a_reset(); +//// prepare for execution by loading execution state into registers +//extern void z80a_load_state(); +//// save execution state back to memory +//extern void z80a_unload_state(); + +// this is very inefficient as it full restores then saves the processor state +extern void z80a_step(); +// run until t >= FRAME_TACTS (or t >= 0 if USE_Z80_ARM_OFFSET_T) +extern void z80a_update(int int_len); +extern void z80a_breakpoint_hit(); + +#pragma pack(push, 1) + +#ifdef USE_BIG_ENDIAN +#define DECLARE_ZREG16(reg, low, high)\ +union\ +{\ + struct\ + {\ + uint8_t reg##xx;\ + uint8_t reg##x;\ + uint8_t high;\ + uint8_t low;\ + };\ + int reg;\ +}; +#else//USE_BIG_ENDIAN +#define DECLARE_ZREG16(reg, low, high)\ +union\ +{\ + struct\ + {\ + uint8_t low;\ + uint8_t high;\ + uint8_t reg##x;\ + uint8_t reg##xx;\ + };\ + int reg;\ +}; +#endif//USE_BIG_ENDIAN + +extern struct _z80a_caller_regs *z80a_caller_regs; +struct _z80a_resting_state { +// argghhhh had fancy __builtin_offsetof stuff here, but was bitten by the fact that i'm compiling for generation on 64 bit :-( +#define rs_offsetof_im 0 + uint32_t im; +#define rs_offsetof_eipos (rs_offsetof_im + 4) + int32_t eipos; + union { + struct { +#define rs_offsetof_r_hi (rs_offsetof_eipos + 4) + uint8_t r_hi; +#define rs_offsetof_iff1 (rs_offsetof_r_hi + 1) + uint8_t iff1; +#define rs_offsetof_iff2 (rs_offsetof_iff1 + 1) + uint8_t iff2; +#define rs_offsetof_halted (rs_offsetof_iff2 + 1) + uint8_t halted; + }; + uint32_t int_flags; + }; + union { + struct { +#define rs_offsetof_r_low (rs_offsetof_halted + 1) + uint8_t r_low; +#define rs_offsetof_i (rs_offsetof_r_low + 1) + uint8_t i; + }; + uint32_t ir; + }; + uint32_t ix; + uint32_t iy; + + uint32_t pc; +#ifndef USE_BANKED_MEMORY_ACCESS +#ifdef USE_SINGLE_64K_MEMORY + uint8_t *memory_64k; +#else +#if PICO_ON_DEVICE +#error require USE_BANKED_MEMORY_ACCESS for non 64K +#endif +#endif +#else + void *interp; +#endif + int32_t t; + DECLARE_ZREG16(af, f, a) + + DECLARE_ZREG16(bc, c, b) + DECLARE_ZREG16(de, e, d) + DECLARE_ZREG16(hl, l, h) + DECLARE_ZREG16(memptr, mem_l, mem_h) + DECLARE_ZREG16(sp, sp_l, sp_h) +#define rs_offsetof_alt_af 60 + uint32_t alt_af; +#define rs_offsetof_alt_bc (rs_offsetof_alt_af + 4) + uint32_t alt_bc; +#define rs_offsetof_alt_de (rs_offsetof_alt_bc + 4) + uint32_t alt_de; +#define rs_offsetof_alt_hl (rs_offsetof_alt_de + 4) + uint32_t alt_hl; +#define rs_offsetof_scratch (rs_offsetof_alt_hl + 4) + uint32_t scratch; + + void (*step_hook_func)(); // called for each instruction + uint32_t active; // not byte for assembly offset distance reasons. +#ifndef NDEBUG +#ifdef ENABLE_BREAKPOINT_IN_DEBUG + uint32_t bp_addr; + uint32_t last_pc; +#endif +#endif +}; +#pragma pack(pop) + +extern _z80a_resting_state z80a_resting_state; +#ifdef __cplusplus +} +#endif + +#endif //SOFTWARE_Z80KHAN_H diff --git a/khan/z80khan_gen.S b/khan/z80khan_gen.S new file mode 100644 index 0000000..49ca55a --- /dev/null +++ b/khan/z80khan_gen.S @@ -0,0 +1,9265 @@ +// ================================ +// == AUTOGENERATED: DO NOT EDIT == +// ================================ + +// ALWAYS DIFF THIS CODE AFTER GENERATION - SENSITVE TO COMPILER VERSION CHANGES! + +// === BEGIN top level opcodes +op_table: +.short op00 + 1 - op_table +.short op01 + 1 - op_table +.short op02 + 1 - op_table +.short op03 + 1 - op_table +.short op04 + 1 - op_table +.short op05 + 1 - op_table +.short op06 + 1 - op_table +.short op07 + 1 - op_table +.short op08 + 1 - op_table +.short op09 + 1 - op_table +.short op0a + 1 - op_table +.short op0b + 1 - op_table +.short op0c + 1 - op_table +.short op0d + 1 - op_table +.short op0e + 1 - op_table +.short op0f + 1 - op_table +.short op10 + 1 - op_table +.short op11 + 1 - op_table +.short op12 + 1 - op_table +.short op13 + 1 - op_table +.short op14 + 1 - op_table +.short op15 + 1 - op_table +.short op16 + 1 - op_table +.short op17 + 1 - op_table +.short op18 + 1 - op_table +.short op19 + 1 - op_table +.short op1a + 1 - op_table +.short op1b + 1 - op_table +.short op1c + 1 - op_table +.short op1d + 1 - op_table +.short op1e + 1 - op_table +.short op1f + 1 - op_table +.short op20 + 1 - op_table +.short op21 + 1 - op_table +.short op22 + 1 - op_table +.short op23 + 1 - op_table +.short op24 + 1 - op_table +.short op25 + 1 - op_table +.short op26 + 1 - op_table +.short op27 + 1 - op_table +.short op28 + 1 - op_table +.short op29 + 1 - op_table +.short op2a + 1 - op_table +.short op2b + 1 - op_table +.short op2c + 1 - op_table +.short op2d + 1 - op_table +.short op2e + 1 - op_table +.short op2f + 1 - op_table +.short op30 + 1 - op_table +.short op31 + 1 - op_table +.short op32 + 1 - op_table +.short op33 + 1 - op_table +.short op34 + 1 - op_table +.short op35 + 1 - op_table +.short op36 + 1 - op_table +.short op37 + 1 - op_table +.short op38 + 1 - op_table +.short op39 + 1 - op_table +.short op3a + 1 - op_table +.short op3b + 1 - op_table +.short op3c + 1 - op_table +.short op3d + 1 - op_table +.short op3e + 1 - op_table +.short op3f + 1 - op_table +.short op40 + 1 - op_table +.short op41 + 1 - op_table +.short op42 + 1 - op_table +.short op43 + 1 - op_table +.short op44 + 1 - op_table +.short op45 + 1 - op_table +.short op46 + 1 - op_table +.short op47 + 1 - op_table +.short op48 + 1 - op_table +.short op49 + 1 - op_table +.short op4a + 1 - op_table +.short op4b + 1 - op_table +.short op4c + 1 - op_table +.short op4d + 1 - op_table +.short op4e + 1 - op_table +.short op4f + 1 - op_table +.short op50 + 1 - op_table +.short op51 + 1 - op_table +.short op52 + 1 - op_table +.short op53 + 1 - op_table +.short op54 + 1 - op_table +.short op55 + 1 - op_table +.short op56 + 1 - op_table +.short op57 + 1 - op_table +.short op58 + 1 - op_table +.short op59 + 1 - op_table +.short op5a + 1 - op_table +.short op5b + 1 - op_table +.short op5c + 1 - op_table +.short op5d + 1 - op_table +.short op5e + 1 - op_table +.short op5f + 1 - op_table +.short op60 + 1 - op_table +.short op61 + 1 - op_table +.short op62 + 1 - op_table +.short op63 + 1 - op_table +.short op64 + 1 - op_table +.short op65 + 1 - op_table +.short op66 + 1 - op_table +.short op67 + 1 - op_table +.short op68 + 1 - op_table +.short op69 + 1 - op_table +.short op6a + 1 - op_table +.short op6b + 1 - op_table +.short op6c + 1 - op_table +.short op6d + 1 - op_table +.short op6e + 1 - op_table +.short op6f + 1 - op_table +.short op70 + 1 - op_table +.short op71 + 1 - op_table +.short op72 + 1 - op_table +.short op73 + 1 - op_table +.short op74 + 1 - op_table +.short op75 + 1 - op_table +.short op76 + 1 - op_table +.short op77 + 1 - op_table +.short op78 + 1 - op_table +.short op79 + 1 - op_table +.short op7a + 1 - op_table +.short op7b + 1 - op_table +.short op7c + 1 - op_table +.short op7d + 1 - op_table +.short op7e + 1 - op_table +.short op7f + 1 - op_table +.short op80 + 1 - op_table +.short op81 + 1 - op_table +.short op82 + 1 - op_table +.short op83 + 1 - op_table +.short op84 + 1 - op_table +.short op85 + 1 - op_table +.short op86 + 1 - op_table +.short op87 + 1 - op_table +.short op88 + 1 - op_table +.short op89 + 1 - op_table +.short op8a + 1 - op_table +.short op8b + 1 - op_table +.short op8c + 1 - op_table +.short op8d + 1 - op_table +.short op8e + 1 - op_table +.short op8f + 1 - op_table +.short op90 + 1 - op_table +.short op91 + 1 - op_table +.short op92 + 1 - op_table +.short op93 + 1 - op_table +.short op94 + 1 - op_table +.short op95 + 1 - op_table +.short op96 + 1 - op_table +.short op97 + 1 - op_table +.short op98 + 1 - op_table +.short op99 + 1 - op_table +.short op9a + 1 - op_table +.short op9b + 1 - op_table +.short op9c + 1 - op_table +.short op9d + 1 - op_table +.short op9e + 1 - op_table +.short op9f + 1 - op_table +.short opa0 + 1 - op_table +.short opa1 + 1 - op_table +.short opa2 + 1 - op_table +.short opa3 + 1 - op_table +.short opa4 + 1 - op_table +.short opa5 + 1 - op_table +.short opa6 + 1 - op_table +.short opa7 + 1 - op_table +.short opa8 + 1 - op_table +.short opa9 + 1 - op_table +.short opaa + 1 - op_table +.short opab + 1 - op_table +.short opac + 1 - op_table +.short opad + 1 - op_table +.short opae + 1 - op_table +.short opaf + 1 - op_table +.short opb0 + 1 - op_table +.short opb1 + 1 - op_table +.short opb2 + 1 - op_table +.short opb3 + 1 - op_table +.short opb4 + 1 - op_table +.short opb5 + 1 - op_table +.short opb6 + 1 - op_table +.short opb7 + 1 - op_table +.short opb8 + 1 - op_table +.short opb9 + 1 - op_table +.short opba + 1 - op_table +.short opbb + 1 - op_table +.short opbc + 1 - op_table +.short opbd + 1 - op_table +.short opbe + 1 - op_table +.short opbf + 1 - op_table +.short opc0 + 1 - op_table +.short opc1 + 1 - op_table +.short opc2 + 1 - op_table +.short opc3 + 1 - op_table +.short opc4 + 1 - op_table +.short opc5 + 1 - op_table +.short opc6 + 1 - op_table +.short opc7 + 1 - op_table +.short opc8 + 1 - op_table +.short opc9 + 1 - op_table +.short opca + 1 - op_table +.short opcb + 1 - op_table +.short opcc + 1 - op_table +.short opcd + 1 - op_table +.short opce + 1 - op_table +.short opcf + 1 - op_table +.short opd0 + 1 - op_table +.short opd1 + 1 - op_table +.short opd2 + 1 - op_table +.short opd3 + 1 - op_table +.short opd4 + 1 - op_table +.short opd5 + 1 - op_table +.short opd6 + 1 - op_table +.short opd7 + 1 - op_table +.short opd8 + 1 - op_table +.short opd9 + 1 - op_table +.short opda + 1 - op_table +.short opdb + 1 - op_table +.short opdc + 1 - op_table +.short opdd + 1 - op_table +.short opde + 1 - op_table +.short opdf + 1 - op_table +.short ope0 + 1 - op_table +.short ope1 + 1 - op_table +.short ope2 + 1 - op_table +.short ope3 + 1 - op_table +.short ope4 + 1 - op_table +.short ope5 + 1 - op_table +.short ope6 + 1 - op_table +.short ope7 + 1 - op_table +.short ope8 + 1 - op_table +.short ope9 + 1 - op_table +.short opea + 1 - op_table +.short opeb + 1 - op_table +.short opec + 1 - op_table +.short oped + 1 - op_table +.short opee + 1 - op_table +.short opef + 1 - op_table +.short opf0 + 1 - op_table +.short opf1 + 1 - op_table +.short opf2 + 1 - op_table +.short opf3 + 1 - op_table +.short opf4 + 1 - op_table +.short opf5 + 1 - op_table +.short opf6 + 1 - op_table +.short opf7 + 1 - op_table +.short opf8 + 1 - op_table +.short opf9 + 1 - op_table +.short opfa + 1 - op_table +.short opfb + 1 - op_table +.short opfc + 1 - op_table +.short opfd + 1 - op_table +.short opfe + 1 - op_table +.short opff + 1 - op_table +.thumb_func +z80a_interrupt: + movs r_temp, #56 + push {lr} + ldr r0, =z80a_resting_state + ldr r0, [r0, #0] // im + cmp r0, #2 + blt 1f + ldr r0, =z80a_resting_state + ldrb r0, [r0, #13] // i + lsls r0, #8 + movs r1, #0xff + orrs r0, r1 + bl read16 + mov r_temp, r0 + adds r_t, #6 +1: + adds r_t, #13 + mov r0, r_pc + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + movs r0, #0 + ldr r1, =z80a_resting_state + strb r0, [r1, #11] // halted + movs r0, #0 + ldr r1, =z80a_resting_state + strb r0, [r1, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + ldr r0, =z80a_resting_state + ldrb r0, [r0, #12] // r_low + adds r0, #1 + uxth r0, r0 + ldr r1, =z80a_resting_state + strb r0, [r1, #12] // r_low + pop {pc} + +op00: + bx lr + +op01: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_bc, r0 + adds r_t, #6 + pop {pc} + +op02: + lsrs r0, r_af, #8 + lsls r0, #8 + mov r1, r_memptr + uxtb r1, r1 + orrs r0, r1 + mov r_memptr, r0 + mov r0, r_bc + adds r0, #1 + uxtb r0, r0 + mov r1, r_memptr + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_memptr, r1 + adds r_t, #3 + mov r0, r_af + lsrs r0, #8 + mov r1, r_bc + ldr r2, =write8 + bx r2 + +op03: + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_bc, r0 + adds r_t, #2 + bx lr + +op04: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl inc8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +op05: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl dec8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +op06: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + adds r_t, #3 + pop {pc} + +op07: + movs r0, #(SF|ZF|PV) + ands r0, r_af // r0 = f & (SF|ZF|PV) + movs r1, #0 + lsrs r_af, #8 + lsls r_af, #25 + bcc 1f + movs r1, #CF + orrs r0, r1 // r0 = (f & (SF|ZF|PV)) | (a7 ? CF: 0) +1: + lsrs r_af, #24 + orrs r_af, r1 + movs r1, #(F3|F5) // r_af = (a << 1) | (a7 ? 1 : 0) + ands r1, r_af + lsls r_af, #8 // r_af = a' : 0 + orrs r_af, r0 // r_af = a' | f' + orrs r_af, r1 // r_af |= 35 from a' + bx lr + +op08: + mov r_temp, r_af + ldr r0, =z80a_resting_state + ldr r0, [r0, #60] // alt.af + mov r_af, r0 + mov r0, r_temp + ldr r1, =z80a_resting_state + str r0, [r1, #60] // alt.af + bx lr + +op09: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + mov r1, r_bc + push {lr} + bl add16 + mov r_hl, r0 + adds r_t, #7 + pop {pc} + +op0a: + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + read8_internal r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + adds r_t, #3 + bx lr + +op0b: + mov r0, r_bc + subs r0, #1 + uxth r0, r0 + mov r_bc, r0 + adds r_t, #2 + bx lr + +op0c: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl inc8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +op0d: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl dec8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +op0e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + adds r_t, #3 + pop {pc} + +op0f: + movs r0, #(SF|ZF|PV) + ands r0, r_af // r0 = f & (SF|ZF|PV) + lsrs r_af, r_af, #9 + bcc 1f + adds r0, #CF // r0 = (f & (SF|ZF|PV)) | (a0 ? CF: 0) + adds r_af, #128 // r_af = (a0 ? 0x80 : 0) | (a >> 1) +1: + movs r1, #(F3|F5) + ands r1, r_af + lsls r_af, #8 // r_af = a' : 0 + orrs r_af, r0 // r_af = a' | f' + orrs r_af, r1 // r_af |= 35 from a' + bx lr + +op10: + movs r1, #1 + lsls r1, #8 + mov r0, r_bc + subs r0, r1 + uxth r0, r0 + mov r_bc, r0 + mov r0, r_bc + lsrs r0, #8 + cmp r0, #0 + beq 1f + mov r0, r_pc + read8_internal r0 + sxtb r0, r0 + adds r0, #1 + add r_pc, r0 + uxth r_pc, r_pc + mov r_memptr, r_pc + adds r_t, #9 + bx lr + +1: + adds r_pc, #1 + uxth r_pc, r_pc + adds r_t, #4 + bx lr + +op11: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_de, r0 + adds r_t, #6 + pop {pc} + +op12: + lsrs r0, r_af, #8 + lsls r0, #8 + mov r1, r_memptr + uxtb r1, r1 + orrs r0, r1 + mov r_memptr, r0 + mov r0, r_de + adds r0, #1 + uxtb r0, r0 + mov r1, r_memptr + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_memptr, r1 + adds r_t, #3 + mov r0, r_af + lsrs r0, #8 + mov r1, r_de + ldr r2, =write8 + bx r2 + +op13: + mov r0, r_de + adds r0, #1 + uxth r0, r0 + mov r_de, r0 + adds r_t, #2 + bx lr + +op14: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl inc8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +op15: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl dec8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +op16: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + adds r_t, #3 + pop {pc} + +op17: + movs r0, #(SF|ZF|PV) + ands r0, r_af // r0 = f & (SF|ZF|PV) + movs r1, #CF + ands r1, r_af + lsrs r_af, #8 + lsls r_af, #25 + bcc 1f + adds r0, #CF // r0 = (f & (SF|ZF|PV)) | (a7 ? CF: 0) +1: + lsrs r_af, #24 + orrs r_af, r1 + movs r1, #(F3|F5) // r_af = (a << 1) | (c ? 1 : 0) + ands r1, r_af + lsls r_af, #8 // r_af = a' : 0 + orrs r_af, r0 // r_af = a' | f' + orrs r_af, r1 // r_af |= 35 from a' + bx lr + +op18: + mov r0, r_pc + read8_internal r0 + sxtb r0, r0 + adds r0, #1 + add r_pc, r0 + uxth r_pc, r_pc + mov r_memptr, r_pc + adds r_t, #8 + bx lr + +op19: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + mov r1, r_de + push {lr} + bl add16 + mov r_hl, r0 + adds r_t, #7 + pop {pc} + +op1a: + mov r0, r_de + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_de + read8_internal r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + adds r_t, #3 + bx lr + +op1b: + mov r0, r_de + subs r0, #1 + uxth r0, r0 + mov r_de, r0 + adds r_t, #2 + bx lr + +op1c: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl inc8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +op1d: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl dec8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +op1e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + adds r_t, #3 + pop {pc} + +op1f: + movs r0, #(SF|ZF|PV) + ands r0, r_af // r0 = f & (SF|ZF|PV) + lsls r2, r_af, #15 // r2 = (garbage : oldCF) << 15 + lsrs r1, r_af, #9 + bcc 1f + adds r0, #CF // r0 = (f & (SF|ZF|PV)) | (a0 ? CF: 0) +1: + lsls r_af, r1, #8 + orrs r_af, r2 + orrs r_af, r0 + movs r0, #(F3|F5) + uxth r_af, r_af + ands r0, r1 + orrs r_af, r0 + bx lr + +op20: + lsrs r0, r_af, #7 + bcs 3f + mov r0, r_pc + read8_internal r0 + sxtb r0, r0 + adds r0, #1 + add r_pc, r0 + uxth r_pc, r_pc + mov r_memptr, r_pc + adds r_t, #8 + bx lr + +3: + adds r_pc, #1 + uxth r_pc, r_pc + adds r_t, #3 + bx lr + +.ltorg +op21: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_hl, r0 + adds r_t, #6 + pop {pc} + +op22: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + mov r1, r_temp + bl write16 + adds r_t, #12 + pop {pc} + +op23: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_hl, r0 + adds r_t, #2 + bx lr + +op24: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl inc8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +op25: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl dec8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +op26: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + adds r_t, #3 + pop {pc} + +op27: + movs r0, #0 // delta + movs r_temp, #(CF|NF) + ands r_temp, r_af // new CHN flags - CH may be set later + // lo nibble + lsls r1, r_af, #20 + lsrs r1, #28 + lsrs r2, r_af, #HF_INDEX+1 + bcs 1f + cmp r1, #10 + blt 3f +1: + adds r0, #0x06 + cmp r_temp, #NF + blt 1f + subs r1, r0 + b 2f +1: + adds r1, r0 +2: + lsls r1, #28 + bcc 3f + movs r2, #HF + orrs r_temp, r2 +3: + // hi nibble + movs r2, #CF + lsrs r1, r_af, #8 + cmp r1, #0x9a + blt 1f + orrs r_temp, r2 + b 2f +1: + ands r2, r_af + beq 3f +2: + adds r0, #0x60 +3: + lsrs r2, r_af, #NF_INDEX+1 + bcc 1f + subs r1, r0 + b 2f +1: + adds r1, r0 +2: + uxtb r_af, r1 + ldr r2, =_log_f + ldrb r0, [r2, r_af] + lsls r_af, #8 + orrs r_af, r0 + orrs r_af, r_temp + bx lr + +op28: + lsrs r0, r_af, #7 + bcc 2f + mov r0, r_pc + read8_internal r0 + sxtb r0, r0 + adds r0, #1 + add r_pc, r0 + uxth r_pc, r_pc + mov r_memptr, r_pc + adds r_t, #8 + bx lr + +2: + adds r_pc, #1 + uxth r_pc, r_pc + adds r_t, #3 + bx lr + +op29: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + mov r1, r_hl + push {lr} + bl add16 + mov r_hl, r0 + adds r_t, #7 + pop {pc} + +op2a: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_temp + bl read16 + mov r_hl, r0 + adds r_t, #12 + pop {pc} + +op2b: + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + adds r_t, #2 + bx lr + +op2c: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl inc8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +op2d: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl dec8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +op2e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + adds r_t, #3 + pop {pc} + +op2f: + movs r0, #255 + lsls r0, #8 + eors r_af, r0 + preserve_only_flags r0, PV|ZF|SF|CF + adds r_af, #NF|HF + lsrs r0, r_af, #8 + movs r1, #(F3|F5) + ands r0, r1 + orrs r_af, r0 + bx lr + +op30: + lsrs r0, r_af, #1 + bcs 3f + mov r0, r_pc + read8_internal r0 + sxtb r0, r0 + adds r0, #1 + add r_pc, r0 + uxth r_pc, r_pc + mov r_memptr, r_pc + adds r_t, #8 + bx lr + +3: + adds r_pc, #1 + uxth r_pc, r_pc + adds r_t, #3 + bx lr + +op31: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_sp, r0 + adds r_t, #6 + pop {pc} + +op32: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxtb r0, r0 + mov r1, r_memptr + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_memptr, r1 + lsrs r0, r_af, #8 + lsls r0, #8 + mov r1, r_memptr + uxtb r1, r1 + orrs r0, r1 + mov r_memptr, r0 + adds r_t, #9 + mov r0, r_af + lsrs r0, #8 + mov r1, r_temp + bl write8 + pop {pc} + +op33: + mov r0, r_sp + adds r0, #1 + uxth r0, r0 + mov r_sp, r0 + adds r_t, #2 + bx lr + +op34: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl inc8 + mov r_temp, r0 // fine to overwrite hi in r_temp + adds r_t, #7 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + pop {pc} + +op35: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl dec8 + mov r_temp, r0 // fine to overwrite hi in r_temp + adds r_t, #7 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + pop {pc} + +op36: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + mov r1, r_hl + bl write8 + pop {pc} + +op37: + preserve_only_flags r0, PV|ZF|SF + adds r_af, #CF + lsrs r0, r_af, #8 + movs r1, #(F3|F5) + ands r0, r1 + orrs r_af, r0 + bx lr + +op38: + lsrs r0, r_af, #1 + bcc 2f + mov r0, r_pc + read8_internal r0 + sxtb r0, r0 + adds r0, #1 + add r_pc, r0 + uxth r_pc, r_pc + mov r_memptr, r_pc + adds r_t, #8 + bx lr + +2: + adds r_pc, #1 + uxth r_pc, r_pc + adds r_t, #3 + bx lr + +op39: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + mov r1, r_sp + push {lr} + bl add16 + mov r_hl, r0 + adds r_t, #7 + pop {pc} + +op3a: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_temp + read8_internal r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + adds r_t, #9 + pop {pc} + +op3b: + mov r0, r_sp + subs r0, #1 + uxth r0, r0 + mov r_sp, r0 + adds r_t, #2 + bx lr + +op3c: + lsrs r0, r_af, #8 + push {lr} + bl inc8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +op3d: + lsrs r0, r_af, #8 + push {lr} + bl dec8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +op3e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + adds r_t, #3 + pop {pc} + +op3f: + movs r1, #CF + lsrs r0, r_af, #1 + bcc 1f + movs r1, #HF +1: + preserve_only_flags r0, PV|ZF|SF + orrs r_af, r1 + lsrs r0, r_af, #8 + movs r1, #(F3|F5) + ands r0, r1 + orrs r_af, r0 + bx lr + +op40: + bx lr + +.ltorg +op41: + mov r0, r_bc + uxtb r0, r0 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + bx lr + +op42: + mov r0, r_de + lsrs r0, #8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + bx lr + +op43: + mov r0, r_de + uxtb r0, r0 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + bx lr + +op44: + mov r0, r_hl + lsrs r0, #8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + bx lr + +op45: + mov r0, r_hl + uxtb r0, r0 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + bx lr + +op46: + mov r0, r_hl + read8_internal r0 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + adds r_t, #3 + bx lr + +op47: + lsrs r0, r_af, #8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + bx lr + +op48: + mov r0, r_bc + lsrs r0, #8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + bx lr + +op49: + bx lr + +op4a: + mov r0, r_de + lsrs r0, #8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + bx lr + +op4b: + mov r0, r_de + uxtb r0, r0 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + bx lr + +op4c: + mov r0, r_hl + lsrs r0, #8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + bx lr + +op4d: + mov r0, r_hl + uxtb r0, r0 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + bx lr + +op4e: + mov r0, r_hl + read8_internal r0 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + adds r_t, #3 + bx lr + +op4f: + lsrs r0, r_af, #8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + bx lr + +op50: + mov r0, r_bc + lsrs r0, #8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + bx lr + +op51: + mov r0, r_bc + uxtb r0, r0 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + bx lr + +op52: + bx lr + +op53: + mov r0, r_de + uxtb r0, r0 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + bx lr + +op54: + mov r0, r_hl + lsrs r0, #8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + bx lr + +op55: + mov r0, r_hl + uxtb r0, r0 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + bx lr + +op56: + mov r0, r_hl + read8_internal r0 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + adds r_t, #3 + bx lr + +op57: + lsrs r0, r_af, #8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + bx lr + +op58: + mov r0, r_bc + lsrs r0, #8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + bx lr + +op59: + mov r0, r_bc + uxtb r0, r0 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + bx lr + +op5a: + mov r0, r_de + lsrs r0, #8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + bx lr + +op5b: + bx lr + +op5c: + mov r0, r_hl + lsrs r0, #8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + bx lr + +op5d: + mov r0, r_hl + uxtb r0, r0 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + bx lr + +op5e: + mov r0, r_hl + read8_internal r0 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + adds r_t, #3 + bx lr + +op5f: + lsrs r0, r_af, #8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + bx lr + +op60: + mov r0, r_bc + lsrs r0, #8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + bx lr + +.ltorg +op61: + mov r0, r_bc + uxtb r0, r0 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + bx lr + +op62: + mov r0, r_de + lsrs r0, #8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + bx lr + +op63: + mov r0, r_de + uxtb r0, r0 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + bx lr + +op64: + bx lr + +op65: + mov r0, r_hl + uxtb r0, r0 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + bx lr + +op66: + mov r0, r_hl + read8_internal r0 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + adds r_t, #3 + bx lr + +op67: + lsrs r0, r_af, #8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + bx lr + +op68: + mov r0, r_bc + lsrs r0, #8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + bx lr + +op69: + mov r0, r_bc + uxtb r0, r0 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + bx lr + +op6a: + mov r0, r_de + lsrs r0, #8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + bx lr + +op6b: + mov r0, r_de + uxtb r0, r0 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + bx lr + +op6c: + mov r0, r_hl + lsrs r0, #8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + bx lr + +op6d: + bx lr + +op6e: + mov r0, r_hl + read8_internal r0 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + adds r_t, #3 + bx lr + +op6f: + lsrs r0, r_af, #8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + bx lr + +op70: + adds r_t, #3 + mov r0, r_bc + lsrs r0, #8 + mov r1, r_hl + ldr r2, =write8 + bx r2 + +op71: + adds r_t, #3 + mov r0, r_bc // high half of word is ignored later + mov r1, r_hl + ldr r2, =write8 + bx r2 + +op72: + adds r_t, #3 + mov r0, r_de + lsrs r0, #8 + mov r1, r_hl + ldr r2, =write8 + bx r2 + +op73: + adds r_t, #3 + mov r0, r_de // high half of word is ignored later + mov r1, r_hl + ldr r2, =write8 + bx r2 + +op74: + adds r_t, #3 + mov r0, r_hl + lsrs r0, #8 + mov r1, r_hl + ldr r2, =write8 + bx r2 + +op75: + adds r_t, #3 + mov r0, r_hl // high half of word is ignored later + mov r1, r_hl + ldr r2, =write8 + bx r2 + +op76: + movs r0, #1 + ldr r1, =z80a_resting_state + strb r0, [r1, #11] // halted +#ifndef USE_Z80_ARM_OFFSET_T + ldr r0, frame_tacts +#else + movs r0, #0 +#endif + subs r0, r_t + movs r1, #3 + adds r0, r1 + bics r0, r1 + add r_t, r0 +#ifndef NO_UPDATE_RLOW_IN_FETCH + ldr r1, =z80a_resting_state + ldrb r1, [r1, #12] // r_low + lsrs r0, #2 + add r0, r1 + ldr r1, =z80a_resting_state + strb r0, [r1, #12] // r_low +#endif + bx lr + +op77: + adds r_t, #3 + mov r0, r_af + lsrs r0, #8 + mov r1, r_hl + ldr r2, =write8 + bx r2 + +op78: + mov r0, r_bc + lsrs r0, #8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + bx lr + +op79: + mov r0, r_bc + uxtb r0, r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + bx lr + +op7a: + mov r0, r_de + lsrs r0, #8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + bx lr + +op7b: + mov r0, r_de + uxtb r0, r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + bx lr + +op7c: + mov r0, r_hl + lsrs r0, #8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + bx lr + +op7d: + mov r0, r_hl + uxtb r0, r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + bx lr + +op7e: + mov r0, r_hl + read8_internal r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + adds r_t, #3 + bx lr + +op7f: + bx lr + +op80: + mov r0, r_bc + lsrs r0, #8 + ldr r2, =add8 + bx r2 + +.ltorg +op81: + mov r0, r_bc + uxtb r0, r0 + ldr r2, =add8 + bx r2 + +op82: + mov r0, r_de + lsrs r0, #8 + ldr r2, =add8 + bx r2 + +op83: + mov r0, r_de + uxtb r0, r0 + ldr r2, =add8 + bx r2 + +op84: + mov r0, r_hl + lsrs r0, #8 + ldr r2, =add8 + bx r2 + +op85: + mov r0, r_hl + uxtb r0, r0 + ldr r2, =add8 + bx r2 + +op86: + mov r0, r_hl + read8_internal r0 + push {lr} + bl add8 + adds r_t, #3 + pop {pc} + +op87: + lsrs r0, r_af, #8 + ldr r2, =add8 + bx r2 + +op88: + mov r0, r_bc + lsrs r0, #8 + ldr r2, =adc8 + bx r2 + +op89: + mov r0, r_bc + uxtb r0, r0 + ldr r2, =adc8 + bx r2 + +op8a: + mov r0, r_de + lsrs r0, #8 + ldr r2, =adc8 + bx r2 + +op8b: + mov r0, r_de + uxtb r0, r0 + ldr r2, =adc8 + bx r2 + +op8c: + mov r0, r_hl + lsrs r0, #8 + ldr r2, =adc8 + bx r2 + +op8d: + mov r0, r_hl + uxtb r0, r0 + ldr r2, =adc8 + bx r2 + +op8e: + mov r0, r_hl + read8_internal r0 + push {lr} + bl adc8 + adds r_t, #3 + pop {pc} + +op8f: + lsrs r0, r_af, #8 + ldr r2, =adc8 + bx r2 + +op90: + mov r0, r_bc + lsrs r0, #8 + ldr r2, =sub8 + bx r2 + +op91: + mov r0, r_bc + uxtb r0, r0 + ldr r2, =sub8 + bx r2 + +op92: + mov r0, r_de + lsrs r0, #8 + ldr r2, =sub8 + bx r2 + +op93: + mov r0, r_de + uxtb r0, r0 + ldr r2, =sub8 + bx r2 + +op94: + mov r0, r_hl + lsrs r0, #8 + ldr r2, =sub8 + bx r2 + +op95: + mov r0, r_hl + uxtb r0, r0 + ldr r2, =sub8 + bx r2 + +op96: + mov r0, r_hl + read8_internal r0 + push {lr} + bl sub8 + adds r_t, #3 + pop {pc} + +op97: + movs r_af, #66 + bx lr + +op98: + mov r0, r_bc + lsrs r0, #8 + ldr r2, =sbc8 + bx r2 + +op99: + mov r0, r_bc + uxtb r0, r0 + ldr r2, =sbc8 + bx r2 + +op9a: + mov r0, r_de + lsrs r0, #8 + ldr r2, =sbc8 + bx r2 + +op9b: + mov r0, r_de + uxtb r0, r0 + ldr r2, =sbc8 + bx r2 + +op9c: + mov r0, r_hl + lsrs r0, #8 + ldr r2, =sbc8 + bx r2 + +op9d: + mov r0, r_hl + uxtb r0, r0 + ldr r2, =sbc8 + bx r2 + +op9e: + mov r0, r_hl + read8_internal r0 + push {lr} + bl sbc8 + adds r_t, #3 + pop {pc} + +op9f: + lsrs r0, r_af, #8 + ldr r2, =sbc8 + bx r2 + +opa0: + mov r0, r_bc + lsrs r0, #8 + ldr r2, =ands8 + bx r2 + +.ltorg +opa1: + mov r0, r_bc + uxtb r0, r0 + ldr r2, =ands8 + bx r2 + +opa2: + mov r0, r_de + lsrs r0, #8 + ldr r2, =ands8 + bx r2 + +opa3: + mov r0, r_de + uxtb r0, r0 + ldr r2, =ands8 + bx r2 + +opa4: + mov r0, r_hl + lsrs r0, #8 + ldr r2, =ands8 + bx r2 + +opa5: + mov r0, r_hl + uxtb r0, r0 + ldr r2, =ands8 + bx r2 + +opa6: + mov r0, r_hl + read8_internal r0 + push {lr} + bl ands8 + adds r_t, #3 + pop {pc} + +opa7: + lsrs r0, r_af, #8 + ldr r2, =ands8 + bx r2 + +opa8: + mov r0, r_bc + lsrs r0, #8 + ldr r2, =xor8 + bx r2 + +opa9: + mov r0, r_bc + uxtb r0, r0 + ldr r2, =xor8 + bx r2 + +opaa: + mov r0, r_de + lsrs r0, #8 + ldr r2, =xor8 + bx r2 + +opab: + mov r0, r_de + uxtb r0, r0 + ldr r2, =xor8 + bx r2 + +opac: + mov r0, r_hl + lsrs r0, #8 + ldr r2, =xor8 + bx r2 + +opad: + mov r0, r_hl + uxtb r0, r0 + ldr r2, =xor8 + bx r2 + +opae: + mov r0, r_hl + read8_internal r0 + push {lr} + bl xor8 + adds r_t, #3 + pop {pc} + +opaf: + movs r_af, #68 + bx lr + +opb0: + mov r0, r_bc + lsrs r0, #8 + ldr r2, =or8 + bx r2 + +opb1: + mov r0, r_bc + uxtb r0, r0 + ldr r2, =or8 + bx r2 + +opb2: + mov r0, r_de + lsrs r0, #8 + ldr r2, =or8 + bx r2 + +opb3: + mov r0, r_de + uxtb r0, r0 + ldr r2, =or8 + bx r2 + +opb4: + mov r0, r_hl + lsrs r0, #8 + ldr r2, =or8 + bx r2 + +opb5: + mov r0, r_hl + uxtb r0, r0 + ldr r2, =or8 + bx r2 + +opb6: + mov r0, r_hl + read8_internal r0 + push {lr} + bl or8 + adds r_t, #3 + pop {pc} + +opb7: + lsrs r0, r_af, #8 + ldr r2, =or8 + bx r2 + +opb8: + mov r0, r_bc + lsrs r0, #8 + ldr r2, =cp8 + bx r2 + +opb9: + mov r0, r_bc + uxtb r0, r0 + ldr r2, =cp8 + bx r2 + +opba: + mov r0, r_de + lsrs r0, #8 + ldr r2, =cp8 + bx r2 + +opbb: + mov r0, r_de + uxtb r0, r0 + ldr r2, =cp8 + bx r2 + +opbc: + mov r0, r_hl + lsrs r0, #8 + ldr r2, =cp8 + bx r2 + +opbd: + mov r0, r_hl + uxtb r0, r0 + ldr r2, =cp8 + bx r2 + +opbe: + mov r0, r_hl + read8_internal r0 + push {lr} + bl cp8 + adds r_t, #3 + pop {pc} + +opbf: + lsrs r0, r_af, #8 + ldr r2, =cp8 + bx r2 + +opc0: + lsrs r0, r_af, #7 + bcs 3f + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #7 + pop {pc} + +3: + adds r_t, #1 + bx lr + +.ltorg +opc1: + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_bc, r0 + adds r_t, #6 + pop {pc} + +opc2: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read16 + mov r_memptr, r0 + lsrs r0, r_af, #7 + bcs 3f + mov r_pc, r_memptr + pop {pc} + +3: + adds r_pc, #2 + uxth r_pc, r_pc + pop {pc} + +opc3: + mov r0, r_pc + push {lr} + bl read16 + mov r_pc, r0 + mov r_memptr, r_pc + adds r_t, #6 + pop {pc} + +opc4: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_memptr, r0 + lsrs r0, r_af, #7 + bcs 3f + mov r0, r_pc + bl _push + mov r_pc, r_memptr + adds r_t, #13 + pop {pc} + +3: + adds r_t, #6 + pop {pc} + +opc5: + adds r_t, #7 + mov r0, r_bc + ldr r2, =_push + bx r2 + +opc6: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + bl add8 + adds r_t, #3 + pop {pc} + +opc7: + mov r0, r_pc + push {lr} + bl _push + movs r_pc, #0 + movs r0, #0 + mov r_memptr, r0 + adds r_t, #7 + pop {pc} + +opc8: + lsrs r0, r_af, #7 + bcc 2f + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #7 + pop {pc} + +2: + adds r_t, #1 + bx lr + +opc9: + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #6 + pop {pc} + +opca: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read16 + mov r_memptr, r0 + lsrs r0, r_af, #7 + bcc 2f + mov r_pc, r_memptr + pop {pc} + +2: + adds r_pc, #2 + uxth r_pc, r_pc + pop {pc} + +opcb: + ldr r_temp, =opl_table + fetch + lsls r0, #1 + ldrh r0, [r_temp, r0] + sxth r0, r0 + add r0, r_temp + bx r0 + bx lr + +opcc: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_memptr, r0 + lsrs r0, r_af, #7 + bcc 2f + mov r0, r_pc + bl _push + mov r_pc, r_memptr + adds r_t, #13 + pop {pc} + +2: + adds r_t, #6 + pop {pc} + +opcd: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + mov r0, r_pc + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #13 + pop {pc} + +opce: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + bl adc8 + adds r_t, #3 + pop {pc} + +opcf: + movs r_temp, #8 + mov r0, r_pc + push {lr} + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #7 + pop {pc} + +opd0: + lsrs r0, r_af, #1 + bcs 3f + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #7 + pop {pc} + +3: + adds r_t, #1 + bx lr + +opd1: + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_de, r0 + adds r_t, #6 + pop {pc} + +opd2: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read16 + mov r_memptr, r0 + lsrs r0, r_af, #1 + bcs 3f + mov r_pc, r_memptr + pop {pc} + +3: + adds r_pc, #2 + uxth r_pc, r_pc + pop {pc} + +opd3: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + mov r_temp, r0 + adds r_t, #7 + lsrs r1, r_af, #8 + lsls r1, #8 + adds r0, r_temp, #1 + uxtb r0, r0 + orrs r0, r1 + mov r_memptr, r0 + mov r0, r_af + lsrs r0, #8 + orrs r1, r_temp + bl iowrite8 + pop {pc} + +opd4: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_memptr, r0 + lsrs r0, r_af, #1 + bcs 3f + mov r0, r_pc + bl _push + mov r_pc, r_memptr + adds r_t, #13 + pop {pc} + +3: + adds r_t, #6 + pop {pc} + +opd5: + adds r_t, #7 + mov r0, r_de + ldr r2, =_push + bx r2 + +opd6: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + bl sub8 + adds r_t, #3 + pop {pc} + +opd7: + movs r_temp, #16 + mov r0, r_pc + push {lr} + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #7 + pop {pc} + +opd8: + lsrs r0, r_af, #1 + bcc 2f + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #7 + pop {pc} + +2: + adds r_t, #1 + bx lr + +opd9: + ldr r2, =z80a_resting_state + mov r0, r_bc + ldr r1, [r2, #64] // alt.bc + str r0, [r2, #64] // alt.bc + mov r_bc, r1 + mov r0, r_de + ldr r1, [r2, #68] // alt.de + str r0, [r2, #68] // alt.de + mov r_de, r1 + mov r0, r_hl + ldr r1, [r2, #72] // alt.hl + str r0, [r2, #72] // alt.hl + mov r_hl, r1 + bx lr + +opda: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read16 + mov r_memptr, r0 + lsrs r0, r_af, #1 + bcc 2f + mov r_pc, r_memptr + pop {pc} + +2: + adds r_pc, #2 + uxth r_pc, r_pc + pop {pc} + +opdb: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + mov r_temp, r0 + lsrs r1, r_af, #8 + lsls r1, #8 + orrs r_temp, r1 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + adds r_t, #7 + mov r0, r_temp + bl ioread8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opdc: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_memptr, r0 + lsrs r0, r_af, #1 + bcc 2f + mov r0, r_pc + bl _push + mov r_pc, r_memptr + adds r_t, #13 + pop {pc} + +2: + adds r_t, #6 + pop {pc} + +opdd: + movs r0, #0xdd + ldr r2, =opDDFD + bx r2 + +opde: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + bl sbc8 + adds r_t, #3 + pop {pc} + +opdf: + movs r_temp, #24 + mov r0, r_pc + push {lr} + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #7 + pop {pc} + +ope0: + lsrs r0, r_af, #3 + bcs 3f + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #7 + pop {pc} + +3: + adds r_t, #1 + bx lr + +.ltorg +ope1: + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_hl, r0 + adds r_t, #6 + pop {pc} + +ope2: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read16 + mov r_memptr, r0 + lsrs r0, r_af, #3 + bcs 3f + mov r_pc, r_memptr + pop {pc} + +3: + adds r_pc, #2 + uxth r_pc, r_pc + pop {pc} + +ope3: + mov r0, r_sp + push {lr} + bl read16 + mov r_temp, r0 + mov r0, r_hl + mov r1, r_sp + bl write16 + mov r_memptr, r_temp + mov r_hl, r_temp + adds r_t, #15 + pop {pc} + +ope4: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_memptr, r0 + lsrs r0, r_af, #3 + bcs 3f + mov r0, r_pc + bl _push + mov r_pc, r_memptr + adds r_t, #13 + pop {pc} + +3: + adds r_t, #6 + pop {pc} + +ope5: + adds r_t, #7 + mov r0, r_hl + ldr r2, =_push + bx r2 + +ope6: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + bl ands8 + adds r_t, #3 + pop {pc} + +ope7: + movs r_temp, #32 + mov r0, r_pc + push {lr} + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #7 + pop {pc} + +ope8: + lsrs r0, r_af, #3 + bcc 2f + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #7 + pop {pc} + +2: + adds r_t, #1 + bx lr + +ope9: + mov r_pc, r_hl + bx lr + +opea: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read16 + mov r_memptr, r0 + lsrs r0, r_af, #3 + bcc 2f + mov r_pc, r_memptr + pop {pc} + +2: + adds r_pc, #2 + uxth r_pc, r_pc + pop {pc} + +opeb: + mov r_temp, r_de + mov r_de, r_hl + mov r_hl, r_temp + bx lr + +opec: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_memptr, r0 + lsrs r0, r_af, #3 + bcc 2f + mov r0, r_pc + bl _push + mov r_pc, r_memptr + adds r_t, #13 + pop {pc} + +2: + adds r_t, #6 + pop {pc} + +oped: + push {lr} + ldr r_temp, =ope_table + step_op_table_in_r_temp_maybe_neg + pop {pc} + +opee: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + bl xor8 + adds r_t, #3 + pop {pc} + +opef: + movs r_temp, #40 + mov r0, r_pc + push {lr} + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #7 + pop {pc} + +opf0: + lsrs r0, r_af, #8 + bcs 3f + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #7 + pop {pc} + +3: + adds r_t, #1 + bx lr + +opf1: + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_af, r0 + adds r_t, #6 + pop {pc} + +opf2: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read16 + mov r_memptr, r0 + lsrs r0, r_af, #8 + bcs 3f + mov r_pc, r_memptr + pop {pc} + +3: + adds r_pc, #2 + uxth r_pc, r_pc + pop {pc} + +opf3: + movs r0, #0 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + movs r0, #0 + ldr r1, =z80a_resting_state + strb r0, [r1, #10] // iff2 + bx lr + +opf4: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_memptr, r0 + lsrs r0, r_af, #8 + bcs 3f + mov r0, r_pc + bl _push + mov r_pc, r_memptr + adds r_t, #13 + pop {pc} + +3: + adds r_t, #6 + pop {pc} + +opf5: + adds r_t, #7 + mov r0, r_af + ldr r2, =_push + bx r2 + +opf6: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + bl or8 + adds r_t, #3 + pop {pc} + +opf7: + movs r_temp, #48 + mov r0, r_pc + push {lr} + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #7 + pop {pc} + +opf8: + lsrs r0, r_af, #8 + bcc 2f + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_memptr, r0 + mov r_pc, r_memptr + adds r_t, #7 + pop {pc} + +2: + adds r_t, #1 + bx lr + +opf9: + mov r_sp, r_hl + adds r_t, #2 + bx lr + +opfa: + adds r_t, #6 + mov r0, r_pc + push {lr} + bl read16 + mov r_memptr, r0 + lsrs r0, r_af, #8 + bcc 2f + mov r_pc, r_memptr + pop {pc} + +2: + adds r_pc, #2 + uxth r_pc, r_pc + pop {pc} + +opfb: + movs r0, #1 + ldr r1, =z80a_resting_state + strb r0, [r1, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_t + ldr r1, =z80a_resting_state + str r0, [r1, #4] // eipos + bx lr + +opfc: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_memptr, r0 + lsrs r0, r_af, #8 + bcc 2f + mov r0, r_pc + bl _push + mov r_pc, r_memptr + adds r_t, #13 + pop {pc} + +2: + adds r_t, #6 + pop {pc} + +opfd: + movs r0, #0xfd + ldr r2, =opDDFD + bx r2 + +opfe: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + bl cp8 + adds r_t, #3 + pop {pc} + +opff: + movs r_temp, #56 + mov r0, r_pc + push {lr} + bl _push + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #7 + pop {pc} + +.ltorg +// === END top level opcodes + +// === BEGIN OPL inner logic functions which do core work on r_temp +opli_r35_table: +.short opli_r35_00 + 1 - opli_r35_table +.short opli_r35_08 + 1 - opli_r35_table +.short opli_r35_10 + 1 - opli_r35_table +.short opli_r35_18 + 1 - opli_r35_table +.short opli_r35_20 + 1 - opli_r35_table +.short opli_r35_28 + 1 - opli_r35_table +.short opli_r35_30 + 1 - opli_r35_table +.short opli_r35_38 + 1 - opli_r35_table +.short opli_r35_40 + 1 - opli_r35_table +.short opli_r35_48 + 1 - opli_r35_table +.short opli_r35_50 + 1 - opli_r35_table +.short opli_r35_58 + 1 - opli_r35_table +.short opli_r35_60 + 1 - opli_r35_table +.short opli_r35_68 + 1 - opli_r35_table +.short opli_r35_70 + 1 - opli_r35_table +.short opli_r35_78 + 1 - opli_r35_table +.short opli_r35_80 + 1 - opli_r35_table +.short opli_r35_88 + 1 - opli_r35_table +.short opli_r35_90 + 1 - opli_r35_table +.short opli_r35_98 + 1 - opli_r35_table +.short opli_r35_a0 + 1 - opli_r35_table +.short opli_r35_a8 + 1 - opli_r35_table +.short opli_r35_b0 + 1 - opli_r35_table +.short opli_r35_b8 + 1 - opli_r35_table +.short opli_r35_c0 + 1 - opli_r35_table +.short opli_r35_c8 + 1 - opli_r35_table +.short opli_r35_d0 + 1 - opli_r35_table +.short opli_r35_d8 + 1 - opli_r35_table +.short opli_r35_e0 + 1 - opli_r35_table +.short opli_r35_e8 + 1 - opli_r35_table +.short opli_r35_f0 + 1 - opli_r35_table +.short opli_r35_f8 + 1 - opli_r35_table +opddcb_bitX_table: +.short opddcb_bit + 1 - opddcb_bitX_table +.short opddcb_bit + 1 - opddcb_bitX_table +.short opddcb_bit + 1 - opddcb_bitX_table +.short opddcb_bit + 1 - opddcb_bitX_table +.short opddcb_bit + 1 - opddcb_bitX_table +.short opddcb_bit + 1 - opddcb_bitX_table +.short opddcb_bit + 1 - opddcb_bitX_table +.short opddcb_bit + 1 - opddcb_bitX_table + +opddcb_X_table: +.short opddcb_0 + 1 - opddcb_X_table +.short opddcb_1 + 1 - opddcb_X_table +.short opddcb_2 + 1 - opddcb_X_table +.short opddcb_3 + 1 - opddcb_X_table +.short opddcb_4 + 1 - opddcb_X_table +.short opddcb_5 + 1 - opddcb_X_table +.short opddcb_6 + 1 - opddcb_X_table +.short opddcb_7 + 1 - opddcb_X_table + +opddcb_0: + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + push {lr} + blx r2 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_memptr + bl write8 + adds r_t, #15 + pop {pc} + +opddcb_1: + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + push {lr} + blx r2 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + mov r0, r_temp // high half of word is ignored later + mov r1, r_memptr + bl write8 + adds r_t, #15 + pop {pc} + +opddcb_2: + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + push {lr} + blx r2 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_memptr + bl write8 + adds r_t, #15 + pop {pc} + +opddcb_3: + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + push {lr} + blx r2 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + mov r0, r_temp // high half of word is ignored later + mov r1, r_memptr + bl write8 + adds r_t, #15 + pop {pc} + +opddcb_4: + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + push {lr} + blx r2 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_memptr + bl write8 + adds r_t, #15 + pop {pc} + +opddcb_5: + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + push {lr} + blx r2 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + mov r0, r_temp // high half of word is ignored later + mov r1, r_memptr + bl write8 + adds r_t, #15 + pop {pc} + +opddcb_6: + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + push {lr} + blx r2 + mov r0, r_temp // high half of word is ignored later + mov r1, r_memptr + bl write8 + adds r_t, #15 + pop {pc} + +opddcb_7: + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + push {lr} + blx r2 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_memptr + bl write8 + adds r_t, #15 + pop {pc} + +opddcb_bit: + adds r_t, #12 + mov r0, r_memptr + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + bx r2 + +opli_r35_00: + uxtb r0, r_temp + push {lr} + bl rlc8 + mov r_temp, r0 // fine to overwrite hi in r_temp + pop {pc} + +opli_r35_08: + uxtb r0, r_temp + push {lr} + bl rrc8 + mov r_temp, r0 // fine to overwrite hi in r_temp + pop {pc} + +opli_r35_10: + uxtb r0, r_temp + push {lr} + bl rl8 + mov r_temp, r0 // fine to overwrite hi in r_temp + pop {pc} + +opli_r35_18: + uxtb r0, r_temp + push {lr} + bl rr8 + mov r_temp, r0 // fine to overwrite hi in r_temp + pop {pc} + +opli_r35_20: + uxtb r0, r_temp + push {lr} + bl sla8 + mov r_temp, r0 // fine to overwrite hi in r_temp + pop {pc} + +opli_r35_28: + uxtb r0, r_temp + push {lr} + bl sra8 + mov r_temp, r0 // fine to overwrite hi in r_temp + pop {pc} + +opli_r35_30: + uxtb r0, r_temp + push {lr} + bl sli8 + mov r_temp, r0 // fine to overwrite hi in r_temp + pop {pc} + +opli_r35_38: + uxtb r0, r_temp + push {lr} + bl srl8 + mov r_temp, r0 // fine to overwrite hi in r_temp + pop {pc} + +opli_r35_40: + // this is the regular version of the inner bit code - it uses r_temp for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x01 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r0, #(F3|F5) + ands r0, r_temp + orrs r_af, r0 // note this OR is safe because 35 from log_f[r_temp&mask] are <= + bx lr + +opli_r35_48: + // this is the regular version of the inner bit code - it uses r_temp for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x02 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r0, #(F3|F5) + ands r0, r_temp + orrs r_af, r0 // note this OR is safe because 35 from log_f[r_temp&mask] are <= + bx lr + +opli_r35_50: + // this is the regular version of the inner bit code - it uses r_temp for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x04 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r0, #(F3|F5) + ands r0, r_temp + orrs r_af, r0 // note this OR is safe because 35 from log_f[r_temp&mask] are <= + bx lr + +opli_r35_58: + // this is the regular version of the inner bit code - it uses r_temp for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x08 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r0, #(F3|F5) + ands r0, r_temp + orrs r_af, r0 // note this OR is safe because 35 from log_f[r_temp&mask] are <= + bx lr + +opli_r35_60: + // this is the regular version of the inner bit code - it uses r_temp for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x10 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r0, #(F3|F5) + ands r0, r_temp + orrs r_af, r0 // note this OR is safe because 35 from log_f[r_temp&mask] are <= + bx lr + +opli_r35_68: + // this is the regular version of the inner bit code - it uses r_temp for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x20 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r0, #(F3|F5) + ands r0, r_temp + orrs r_af, r0 // note this OR is safe because 35 from log_f[r_temp&mask] are <= + bx lr + +opli_r35_70: + // this is the regular version of the inner bit code - it uses r_temp for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x40 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r0, #(F3|F5) + ands r0, r_temp + orrs r_af, r0 // note this OR is safe because 35 from log_f[r_temp&mask] are <= + bx lr + +opli_r35_78: + // this is the regular version of the inner bit code - it uses r_temp for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x80 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r0, #(F3|F5) + ands r0, r_temp + orrs r_af, r0 // note this OR is safe because 35 from log_f[r_temp&mask] are <= + bx lr + +opli_r35_80: + movs r0, #0x01 + bics r_temp, r0 + bx lr + +opli_r35_88: + movs r0, #0x02 + bics r_temp, r0 + bx lr + +opli_r35_90: + movs r0, #0x04 + bics r_temp, r0 + bx lr + +opli_r35_98: + movs r0, #0x08 + bics r_temp, r0 + bx lr + +opli_r35_a0: + movs r0, #0x10 + bics r_temp, r0 + bx lr + +opli_r35_a8: + movs r0, #0x20 + bics r_temp, r0 + bx lr + +opli_r35_b0: + movs r0, #0x40 + bics r_temp, r0 + bx lr + +opli_r35_b8: + movs r0, #0x80 + bics r_temp, r0 + bx lr + +opli_r35_c0: + movs r0, #0x01 + orrs r_temp, r0 + bx lr + +opli_r35_c8: + movs r0, #0x02 + orrs r_temp, r0 + bx lr + +opli_r35_d0: + movs r0, #0x04 + orrs r_temp, r0 + bx lr + +opli_r35_d8: + movs r0, #0x08 + orrs r_temp, r0 + bx lr + +opli_r35_e0: + movs r0, #0x10 + orrs r_temp, r0 + bx lr + +opli_r35_e8: + movs r0, #0x20 + orrs r_temp, r0 + bx lr + +opli_r35_f0: + movs r0, #0x40 + orrs r_temp, r0 + bx lr + +opli_r35_f8: + movs r0, #0x80 + orrs r_temp, r0 + bx lr + +.ltorg +// === END OPL inner logic functions which do core work on r_temp + +// === BEGIN OPL inner logic functions which do core work on r_temp, but set F3&F5 based on mem_h +opli_m35_table: +.short opli_r35_00 + 1 - opli_m35_table +.short opli_r35_08 + 1 - opli_m35_table +.short opli_r35_10 + 1 - opli_m35_table +.short opli_r35_18 + 1 - opli_m35_table +.short opli_r35_20 + 1 - opli_m35_table +.short opli_r35_28 + 1 - opli_m35_table +.short opli_r35_30 + 1 - opli_m35_table +.short opli_r35_38 + 1 - opli_m35_table +.short opli_m35_40 + 1 - opli_m35_table +.short opli_m35_48 + 1 - opli_m35_table +.short opli_m35_50 + 1 - opli_m35_table +.short opli_m35_58 + 1 - opli_m35_table +.short opli_m35_60 + 1 - opli_m35_table +.short opli_m35_68 + 1 - opli_m35_table +.short opli_m35_70 + 1 - opli_m35_table +.short opli_m35_78 + 1 - opli_m35_table +.short opli_r35_80 + 1 - opli_m35_table +.short opli_r35_88 + 1 - opli_m35_table +.short opli_r35_90 + 1 - opli_m35_table +.short opli_r35_98 + 1 - opli_m35_table +.short opli_r35_a0 + 1 - opli_m35_table +.short opli_r35_a8 + 1 - opli_m35_table +.short opli_r35_b0 + 1 - opli_m35_table +.short opli_r35_b8 + 1 - opli_m35_table +.short opli_r35_c0 + 1 - opli_m35_table +.short opli_r35_c8 + 1 - opli_m35_table +.short opli_r35_d0 + 1 - opli_m35_table +.short opli_r35_d8 + 1 - opli_m35_table +.short opli_r35_e0 + 1 - opli_m35_table +.short opli_r35_e8 + 1 - opli_m35_table +.short opli_r35_f0 + 1 - opli_m35_table +.short opli_r35_f8 + 1 - opli_m35_table +opli_m35_40: + // Beware confusion; t may already be updated for the instruction + // this is the bitmem version of the inner bit code - it uses mem_h for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x01 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r1, #(F3|F5) + bics r_af, r1 + mov r0, r_memptr + lsrs r0, #8 + ands r0, r1 + orrs r_af, r0 + bx lr + +opli_m35_48: + // Beware confusion; t may already be updated for the instruction + // this is the bitmem version of the inner bit code - it uses mem_h for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x02 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r1, #(F3|F5) + bics r_af, r1 + mov r0, r_memptr + lsrs r0, #8 + ands r0, r1 + orrs r_af, r0 + bx lr + +opli_m35_50: + // Beware confusion; t may already be updated for the instruction + // this is the bitmem version of the inner bit code - it uses mem_h for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x04 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r1, #(F3|F5) + bics r_af, r1 + mov r0, r_memptr + lsrs r0, #8 + ands r0, r1 + orrs r_af, r0 + bx lr + +opli_m35_58: + // Beware confusion; t may already be updated for the instruction + // this is the bitmem version of the inner bit code - it uses mem_h for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x08 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r1, #(F3|F5) + bics r_af, r1 + mov r0, r_memptr + lsrs r0, #8 + ands r0, r1 + orrs r_af, r0 + bx lr + +opli_m35_60: + // Beware confusion; t may already be updated for the instruction + // this is the bitmem version of the inner bit code - it uses mem_h for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x10 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r1, #(F3|F5) + bics r_af, r1 + mov r0, r_memptr + lsrs r0, #8 + ands r0, r1 + orrs r_af, r0 + bx lr + +opli_m35_68: + // Beware confusion; t may already be updated for the instruction + // this is the bitmem version of the inner bit code - it uses mem_h for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x20 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r1, #(F3|F5) + bics r_af, r1 + mov r0, r_memptr + lsrs r0, #8 + ands r0, r1 + orrs r_af, r0 + bx lr + +opli_m35_70: + // Beware confusion; t may already be updated for the instruction + // this is the bitmem version of the inner bit code - it uses mem_h for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x40 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r1, #(F3|F5) + bics r_af, r1 + mov r0, r_memptr + lsrs r0, #8 + ands r0, r1 + orrs r_af, r0 + bx lr + +opli_m35_78: + // Beware confusion; t may already be updated for the instruction + // this is the bitmem version of the inner bit code - it uses mem_h for F3 & F5 + preserve_only_flags r0, CF + movs r0, #0x80 + ands r0, r_temp + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_af, #HF + movs r1, #(F3|F5) + bics r_af, r1 + mov r0, r_memptr + lsrs r0, #8 + ands r0, r1 + orrs r_af, r0 + bx lr + +.ltorg +// === END OPL inner logic functions which do core work on r_temp, but set F3&F5 based on mem_h + +// === BEGIN cb logic opcodes +opl_table: +.short opl00 + 1 - opl_table +.short opl01 + 1 - opl_table +.short opl02 + 1 - opl_table +.short opl03 + 1 - opl_table +.short opl04 + 1 - opl_table +.short opl05 + 1 - opl_table +.short opl06 + 1 - opl_table +.short opl07 + 1 - opl_table +.short opl08 + 1 - opl_table +.short opl09 + 1 - opl_table +.short opl0a + 1 - opl_table +.short opl0b + 1 - opl_table +.short opl0c + 1 - opl_table +.short opl0d + 1 - opl_table +.short opl0e + 1 - opl_table +.short opl0f + 1 - opl_table +.short opl10 + 1 - opl_table +.short opl11 + 1 - opl_table +.short opl12 + 1 - opl_table +.short opl13 + 1 - opl_table +.short opl14 + 1 - opl_table +.short opl15 + 1 - opl_table +.short opl16 + 1 - opl_table +.short opl17 + 1 - opl_table +.short opl18 + 1 - opl_table +.short opl19 + 1 - opl_table +.short opl1a + 1 - opl_table +.short opl1b + 1 - opl_table +.short opl1c + 1 - opl_table +.short opl1d + 1 - opl_table +.short opl1e + 1 - opl_table +.short opl1f + 1 - opl_table +.short opl20 + 1 - opl_table +.short opl21 + 1 - opl_table +.short opl22 + 1 - opl_table +.short opl23 + 1 - opl_table +.short opl24 + 1 - opl_table +.short opl25 + 1 - opl_table +.short opl26 + 1 - opl_table +.short opl27 + 1 - opl_table +.short opl28 + 1 - opl_table +.short opl29 + 1 - opl_table +.short opl2a + 1 - opl_table +.short opl2b + 1 - opl_table +.short opl2c + 1 - opl_table +.short opl2d + 1 - opl_table +.short opl2e + 1 - opl_table +.short opl2f + 1 - opl_table +.short opl30 + 1 - opl_table +.short opl31 + 1 - opl_table +.short opl32 + 1 - opl_table +.short opl33 + 1 - opl_table +.short opl34 + 1 - opl_table +.short opl35 + 1 - opl_table +.short opl36 + 1 - opl_table +.short opl37 + 1 - opl_table +.short opl38 + 1 - opl_table +.short opl39 + 1 - opl_table +.short opl3a + 1 - opl_table +.short opl3b + 1 - opl_table +.short opl3c + 1 - opl_table +.short opl3d + 1 - opl_table +.short opl3e + 1 - opl_table +.short opl3f + 1 - opl_table +.short opl40 + 1 - opl_table +.short opl41 + 1 - opl_table +.short opl42 + 1 - opl_table +.short opl43 + 1 - opl_table +.short opl44 + 1 - opl_table +.short opl45 + 1 - opl_table +.short opl46 + 1 - opl_table +.short opl47 + 1 - opl_table +.short opl48 + 1 - opl_table +.short opl49 + 1 - opl_table +.short opl4a + 1 - opl_table +.short opl4b + 1 - opl_table +.short opl4c + 1 - opl_table +.short opl4d + 1 - opl_table +.short opl4e + 1 - opl_table +.short opl4f + 1 - opl_table +.short opl50 + 1 - opl_table +.short opl51 + 1 - opl_table +.short opl52 + 1 - opl_table +.short opl53 + 1 - opl_table +.short opl54 + 1 - opl_table +.short opl55 + 1 - opl_table +.short opl56 + 1 - opl_table +.short opl57 + 1 - opl_table +.short opl58 + 1 - opl_table +.short opl59 + 1 - opl_table +.short opl5a + 1 - opl_table +.short opl5b + 1 - opl_table +.short opl5c + 1 - opl_table +.short opl5d + 1 - opl_table +.short opl5e + 1 - opl_table +.short opl5f + 1 - opl_table +.short opl60 + 1 - opl_table +.short opl61 + 1 - opl_table +.short opl62 + 1 - opl_table +.short opl63 + 1 - opl_table +.short opl64 + 1 - opl_table +.short opl65 + 1 - opl_table +.short opl66 + 1 - opl_table +.short opl67 + 1 - opl_table +.short opl68 + 1 - opl_table +.short opl69 + 1 - opl_table +.short opl6a + 1 - opl_table +.short opl6b + 1 - opl_table +.short opl6c + 1 - opl_table +.short opl6d + 1 - opl_table +.short opl6e + 1 - opl_table +.short opl6f + 1 - opl_table +.short opl70 + 1 - opl_table +.short opl71 + 1 - opl_table +.short opl72 + 1 - opl_table +.short opl73 + 1 - opl_table +.short opl74 + 1 - opl_table +.short opl75 + 1 - opl_table +.short opl76 + 1 - opl_table +.short opl77 + 1 - opl_table +.short opl78 + 1 - opl_table +.short opl79 + 1 - opl_table +.short opl7a + 1 - opl_table +.short opl7b + 1 - opl_table +.short opl7c + 1 - opl_table +.short opl7d + 1 - opl_table +.short opl7e + 1 - opl_table +.short opl7f + 1 - opl_table +.short opl80 + 1 - opl_table +.short opl81 + 1 - opl_table +.short opl82 + 1 - opl_table +.short opl83 + 1 - opl_table +.short opl84 + 1 - opl_table +.short opl85 + 1 - opl_table +.short opl86 + 1 - opl_table +.short opl87 + 1 - opl_table +.short opl88 + 1 - opl_table +.short opl89 + 1 - opl_table +.short opl8a + 1 - opl_table +.short opl8b + 1 - opl_table +.short opl8c + 1 - opl_table +.short opl8d + 1 - opl_table +.short opl8e + 1 - opl_table +.short opl8f + 1 - opl_table +.short opl90 + 1 - opl_table +.short opl91 + 1 - opl_table +.short opl92 + 1 - opl_table +.short opl93 + 1 - opl_table +.short opl94 + 1 - opl_table +.short opl95 + 1 - opl_table +.short opl96 + 1 - opl_table +.short opl97 + 1 - opl_table +.short opl98 + 1 - opl_table +.short opl99 + 1 - opl_table +.short opl9a + 1 - opl_table +.short opl9b + 1 - opl_table +.short opl9c + 1 - opl_table +.short opl9d + 1 - opl_table +.short opl9e + 1 - opl_table +.short opl9f + 1 - opl_table +.short opla0 + 1 - opl_table +.short opla1 + 1 - opl_table +.short opla2 + 1 - opl_table +.short opla3 + 1 - opl_table +.short opla4 + 1 - opl_table +.short opla5 + 1 - opl_table +.short opla6 + 1 - opl_table +.short opla7 + 1 - opl_table +.short opla8 + 1 - opl_table +.short opla9 + 1 - opl_table +.short oplaa + 1 - opl_table +.short oplab + 1 - opl_table +.short oplac + 1 - opl_table +.short oplad + 1 - opl_table +.short oplae + 1 - opl_table +.short oplaf + 1 - opl_table +.short oplb0 + 1 - opl_table +.short oplb1 + 1 - opl_table +.short oplb2 + 1 - opl_table +.short oplb3 + 1 - opl_table +.short oplb4 + 1 - opl_table +.short oplb5 + 1 - opl_table +.short oplb6 + 1 - opl_table +.short oplb7 + 1 - opl_table +.short oplb8 + 1 - opl_table +.short oplb9 + 1 - opl_table +.short oplba + 1 - opl_table +.short oplbb + 1 - opl_table +.short oplbc + 1 - opl_table +.short oplbd + 1 - opl_table +.short oplbe + 1 - opl_table +.short oplbf + 1 - opl_table +.short oplc0 + 1 - opl_table +.short oplc1 + 1 - opl_table +.short oplc2 + 1 - opl_table +.short oplc3 + 1 - opl_table +.short oplc4 + 1 - opl_table +.short oplc5 + 1 - opl_table +.short oplc6 + 1 - opl_table +.short oplc7 + 1 - opl_table +.short oplc8 + 1 - opl_table +.short oplc9 + 1 - opl_table +.short oplca + 1 - opl_table +.short oplcb + 1 - opl_table +.short oplcc + 1 - opl_table +.short oplcd + 1 - opl_table +.short oplce + 1 - opl_table +.short oplcf + 1 - opl_table +.short opld0 + 1 - opl_table +.short opld1 + 1 - opl_table +.short opld2 + 1 - opl_table +.short opld3 + 1 - opl_table +.short opld4 + 1 - opl_table +.short opld5 + 1 - opl_table +.short opld6 + 1 - opl_table +.short opld7 + 1 - opl_table +.short opld8 + 1 - opl_table +.short opld9 + 1 - opl_table +.short oplda + 1 - opl_table +.short opldb + 1 - opl_table +.short opldc + 1 - opl_table +.short opldd + 1 - opl_table +.short oplde + 1 - opl_table +.short opldf + 1 - opl_table +.short ople0 + 1 - opl_table +.short ople1 + 1 - opl_table +.short ople2 + 1 - opl_table +.short ople3 + 1 - opl_table +.short ople4 + 1 - opl_table +.short ople5 + 1 - opl_table +.short ople6 + 1 - opl_table +.short ople7 + 1 - opl_table +.short ople8 + 1 - opl_table +.short ople9 + 1 - opl_table +.short oplea + 1 - opl_table +.short opleb + 1 - opl_table +.short oplec + 1 - opl_table +.short opled + 1 - opl_table +.short oplee + 1 - opl_table +.short oplef + 1 - opl_table +.short oplf0 + 1 - opl_table +.short oplf1 + 1 - opl_table +.short oplf2 + 1 - opl_table +.short oplf3 + 1 - opl_table +.short oplf4 + 1 - opl_table +.short oplf5 + 1 - opl_table +.short oplf6 + 1 - opl_table +.short oplf7 + 1 - opl_table +.short oplf8 + 1 - opl_table +.short oplf9 + 1 - opl_table +.short oplfa + 1 - opl_table +.short oplfb + 1 - opl_table +.short oplfc + 1 - opl_table +.short oplfd + 1 - opl_table +.short oplfe + 1 - opl_table +.short oplff + 1 - opl_table +opl00: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl rlc8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl01: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl rlc8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +opl02: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl rlc8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl03: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl rlc8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +opl04: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl rlc8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl05: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl rlc8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +opl06: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl rlc8 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + adds r_t, #7 + pop {pc} + +opl07: + lsrs r0, r_af, #8 + push {lr} + bl rlc8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl08: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl rrc8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl09: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl rrc8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +opl0a: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl rrc8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl0b: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl rrc8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +opl0c: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl rrc8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl0d: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl rrc8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +opl0e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl rrc8 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + adds r_t, #7 + pop {pc} + +opl0f: + lsrs r0, r_af, #8 + push {lr} + bl rrc8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl10: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl rl8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl11: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl rl8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +opl12: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl rl8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl13: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl rl8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +opl14: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl rl8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl15: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl rl8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +opl16: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl rl8 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + adds r_t, #7 + pop {pc} + +opl17: + lsrs r0, r_af, #8 + push {lr} + bl rl8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl18: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl rr8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl19: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl rr8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +opl1a: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl rr8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl1b: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl rr8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +opl1c: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl rr8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl1d: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl rr8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +opl1e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl rr8 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + adds r_t, #7 + pop {pc} + +opl1f: + lsrs r0, r_af, #8 + push {lr} + bl rr8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl20: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl sla8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +.ltorg +opl21: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl sla8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +opl22: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl sla8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl23: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl sla8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +opl24: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl sla8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl25: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl sla8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +opl26: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl sla8 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + adds r_t, #7 + pop {pc} + +opl27: + lsrs r0, r_af, #8 + push {lr} + bl sla8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl28: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl sra8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl29: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl sra8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +opl2a: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl sra8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl2b: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl sra8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +opl2c: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl sra8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl2d: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl sra8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +opl2e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl sra8 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + adds r_t, #7 + pop {pc} + +opl2f: + lsrs r0, r_af, #8 + push {lr} + bl sra8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl30: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl sli8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl31: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl sli8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +opl32: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl sli8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl33: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl sli8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +opl34: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl sli8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl35: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl sli8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +opl36: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl sli8 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + adds r_t, #7 + pop {pc} + +opl37: + lsrs r0, r_af, #8 + push {lr} + bl sli8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl38: + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl srl8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl39: + mov r0, r_bc + uxtb r0, r0 + push {lr} + bl srl8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + pop {pc} + +opl3a: + mov r0, r_de + lsrs r0, #8 + push {lr} + bl srl8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl3b: + mov r0, r_de + uxtb r0, r0 + push {lr} + bl srl8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + pop {pc} + +opl3c: + mov r0, r_hl + lsrs r0, #8 + push {lr} + bl srl8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl3d: + mov r0, r_hl + uxtb r0, r0 + push {lr} + bl srl8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + pop {pc} + +opl3e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + uxtb r0, r_temp + push {lr} + bl srl8 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + adds r_t, #7 + pop {pc} + +opl3f: + lsrs r0, r_af, #8 + push {lr} + bl srl8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl40: + mov r_temp, r_bc + lsrs r_temp, #8 + push {lr} + bl opli_r35_40 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +.ltorg +opl41: + mov r_temp, r_bc + uxtb r3, r3 + push {lr} + bl opli_r35_40 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + pop {pc} + +opl42: + mov r_temp, r_de + lsrs r_temp, #8 + push {lr} + bl opli_r35_40 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl43: + mov r_temp, r_de + uxtb r3, r3 + push {lr} + bl opli_r35_40 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + pop {pc} + +opl44: + mov r_temp, r_hl + lsrs r_temp, #8 + push {lr} + bl opli_r35_40 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl45: + mov r_temp, r_hl + uxtb r3, r3 + push {lr} + bl opli_r35_40 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + pop {pc} + +opl46: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + push {lr} + bl opli_m35_40 + adds r_t, #4 + pop {pc} + +opl47: + lsrs r_temp, r_af, #8 + push {lr} + bl opli_r35_40 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl48: + mov r_temp, r_bc + lsrs r_temp, #8 + push {lr} + bl opli_r35_48 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl49: + mov r_temp, r_bc + uxtb r3, r3 + push {lr} + bl opli_r35_48 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + pop {pc} + +opl4a: + mov r_temp, r_de + lsrs r_temp, #8 + push {lr} + bl opli_r35_48 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl4b: + mov r_temp, r_de + uxtb r3, r3 + push {lr} + bl opli_r35_48 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + pop {pc} + +opl4c: + mov r_temp, r_hl + lsrs r_temp, #8 + push {lr} + bl opli_r35_48 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl4d: + mov r_temp, r_hl + uxtb r3, r3 + push {lr} + bl opli_r35_48 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + pop {pc} + +opl4e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + push {lr} + bl opli_m35_48 + adds r_t, #4 + pop {pc} + +opl4f: + lsrs r_temp, r_af, #8 + push {lr} + bl opli_r35_48 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl50: + mov r_temp, r_bc + lsrs r_temp, #8 + push {lr} + bl opli_r35_50 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl51: + mov r_temp, r_bc + uxtb r3, r3 + push {lr} + bl opli_r35_50 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + pop {pc} + +opl52: + mov r_temp, r_de + lsrs r_temp, #8 + push {lr} + bl opli_r35_50 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl53: + mov r_temp, r_de + uxtb r3, r3 + push {lr} + bl opli_r35_50 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + pop {pc} + +opl54: + mov r_temp, r_hl + lsrs r_temp, #8 + push {lr} + bl opli_r35_50 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl55: + mov r_temp, r_hl + uxtb r3, r3 + push {lr} + bl opli_r35_50 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + pop {pc} + +opl56: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + push {lr} + bl opli_m35_50 + adds r_t, #4 + pop {pc} + +opl57: + lsrs r_temp, r_af, #8 + push {lr} + bl opli_r35_50 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl58: + mov r_temp, r_bc + lsrs r_temp, #8 + push {lr} + bl opli_r35_58 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl59: + mov r_temp, r_bc + uxtb r3, r3 + push {lr} + bl opli_r35_58 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + pop {pc} + +opl5a: + mov r_temp, r_de + lsrs r_temp, #8 + push {lr} + bl opli_r35_58 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl5b: + mov r_temp, r_de + uxtb r3, r3 + push {lr} + bl opli_r35_58 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + pop {pc} + +opl5c: + mov r_temp, r_hl + lsrs r_temp, #8 + push {lr} + bl opli_r35_58 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl5d: + mov r_temp, r_hl + uxtb r3, r3 + push {lr} + bl opli_r35_58 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + pop {pc} + +opl5e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + push {lr} + bl opli_m35_58 + adds r_t, #4 + pop {pc} + +opl5f: + lsrs r_temp, r_af, #8 + push {lr} + bl opli_r35_58 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl60: + mov r_temp, r_bc + lsrs r_temp, #8 + push {lr} + bl opli_r35_60 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +.ltorg +opl61: + mov r_temp, r_bc + uxtb r3, r3 + push {lr} + bl opli_r35_60 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + pop {pc} + +opl62: + mov r_temp, r_de + lsrs r_temp, #8 + push {lr} + bl opli_r35_60 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl63: + mov r_temp, r_de + uxtb r3, r3 + push {lr} + bl opli_r35_60 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + pop {pc} + +opl64: + mov r_temp, r_hl + lsrs r_temp, #8 + push {lr} + bl opli_r35_60 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl65: + mov r_temp, r_hl + uxtb r3, r3 + push {lr} + bl opli_r35_60 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + pop {pc} + +opl66: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + push {lr} + bl opli_m35_60 + adds r_t, #4 + pop {pc} + +opl67: + lsrs r_temp, r_af, #8 + push {lr} + bl opli_r35_60 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl68: + mov r_temp, r_bc + lsrs r_temp, #8 + push {lr} + bl opli_r35_68 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl69: + mov r_temp, r_bc + uxtb r3, r3 + push {lr} + bl opli_r35_68 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + pop {pc} + +opl6a: + mov r_temp, r_de + lsrs r_temp, #8 + push {lr} + bl opli_r35_68 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl6b: + mov r_temp, r_de + uxtb r3, r3 + push {lr} + bl opli_r35_68 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + pop {pc} + +opl6c: + mov r_temp, r_hl + lsrs r_temp, #8 + push {lr} + bl opli_r35_68 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl6d: + mov r_temp, r_hl + uxtb r3, r3 + push {lr} + bl opli_r35_68 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + pop {pc} + +opl6e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + push {lr} + bl opli_m35_68 + adds r_t, #4 + pop {pc} + +opl6f: + lsrs r_temp, r_af, #8 + push {lr} + bl opli_r35_68 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl70: + mov r_temp, r_bc + lsrs r_temp, #8 + push {lr} + bl opli_r35_70 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl71: + mov r_temp, r_bc + uxtb r3, r3 + push {lr} + bl opli_r35_70 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + pop {pc} + +opl72: + mov r_temp, r_de + lsrs r_temp, #8 + push {lr} + bl opli_r35_70 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl73: + mov r_temp, r_de + uxtb r3, r3 + push {lr} + bl opli_r35_70 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + pop {pc} + +opl74: + mov r_temp, r_hl + lsrs r_temp, #8 + push {lr} + bl opli_r35_70 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl75: + mov r_temp, r_hl + uxtb r3, r3 + push {lr} + bl opli_r35_70 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + pop {pc} + +opl76: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + push {lr} + bl opli_m35_70 + adds r_t, #4 + pop {pc} + +opl77: + lsrs r_temp, r_af, #8 + push {lr} + bl opli_r35_70 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl78: + mov r_temp, r_bc + lsrs r_temp, #8 + push {lr} + bl opli_r35_78 + lsls r0, r_temp, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + pop {pc} + +opl79: + mov r_temp, r_bc + uxtb r3, r3 + push {lr} + bl opli_r35_78 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_bc, r1 + pop {pc} + +opl7a: + mov r_temp, r_de + lsrs r_temp, #8 + push {lr} + bl opli_r35_78 + lsls r0, r_temp, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + pop {pc} + +opl7b: + mov r_temp, r_de + uxtb r3, r3 + push {lr} + bl opli_r35_78 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_de, r1 + pop {pc} + +opl7c: + mov r_temp, r_hl + lsrs r_temp, #8 + push {lr} + bl opli_r35_78 + lsls r0, r_temp, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + pop {pc} + +opl7d: + mov r_temp, r_hl + uxtb r3, r3 + push {lr} + bl opli_r35_78 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r_temp + mov r_hl, r1 + pop {pc} + +opl7e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + push {lr} + bl opli_m35_78 + adds r_t, #4 + pop {pc} + +opl7f: + lsrs r_temp, r_af, #8 + push {lr} + bl opli_r35_78 + lsls r0, r_temp, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + pop {pc} + +opl80: + movs r0, #1 + lsls r0, #8 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +.ltorg +opl81: + movs r0, #0x01 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opl82: + movs r0, #1 + lsls r0, #8 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opl83: + movs r0, #0x01 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opl84: + movs r0, #1 + lsls r0, #8 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opl85: + movs r0, #0x01 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opl86: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x01 + bics r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +opl87: + movs r0, #1 + lsls r0, #8 + bics r_af, r0 + bx lr + +opl88: + movs r0, #1 + lsls r0, #9 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opl89: + movs r0, #0x02 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opl8a: + movs r0, #1 + lsls r0, #9 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opl8b: + movs r0, #0x02 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opl8c: + movs r0, #1 + lsls r0, #9 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opl8d: + movs r0, #0x02 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opl8e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x02 + bics r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +opl8f: + movs r0, #1 + lsls r0, #9 + bics r_af, r0 + bx lr + +opl90: + movs r0, #1 + lsls r0, #10 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opl91: + movs r0, #0x04 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opl92: + movs r0, #1 + lsls r0, #10 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opl93: + movs r0, #0x04 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opl94: + movs r0, #1 + lsls r0, #10 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opl95: + movs r0, #0x04 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opl96: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x04 + bics r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +opl97: + movs r0, #1 + lsls r0, #10 + bics r_af, r0 + bx lr + +opl98: + movs r0, #1 + lsls r0, #11 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opl99: + movs r0, #0x08 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opl9a: + movs r0, #1 + lsls r0, #11 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opl9b: + movs r0, #0x08 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opl9c: + movs r0, #1 + lsls r0, #11 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opl9d: + movs r0, #0x08 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opl9e: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x08 + bics r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +opl9f: + movs r0, #1 + lsls r0, #11 + bics r_af, r0 + bx lr + +opla0: + movs r0, #1 + lsls r0, #12 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +.ltorg +opla1: + movs r0, #0x10 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opla2: + movs r0, #1 + lsls r0, #12 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opla3: + movs r0, #0x10 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +opla4: + movs r0, #1 + lsls r0, #12 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opla5: + movs r0, #0x10 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +opla6: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x10 + bics r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +opla7: + movs r0, #1 + lsls r0, #12 + bics r_af, r0 + bx lr + +opla8: + movs r0, #1 + lsls r0, #13 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +opla9: + movs r0, #0x20 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +oplaa: + movs r0, #1 + lsls r0, #13 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +oplab: + movs r0, #0x20 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +oplac: + movs r0, #1 + lsls r0, #13 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +oplad: + movs r0, #0x20 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +oplae: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x20 + bics r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +oplaf: + movs r0, #1 + lsls r0, #13 + bics r_af, r0 + bx lr + +oplb0: + movs r0, #1 + lsls r0, #14 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +oplb1: + movs r0, #0x40 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +oplb2: + movs r0, #1 + lsls r0, #14 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +oplb3: + movs r0, #0x40 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +oplb4: + movs r0, #1 + lsls r0, #14 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +oplb5: + movs r0, #0x40 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +oplb6: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x40 + bics r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +oplb7: + movs r0, #1 + lsls r0, #14 + bics r_af, r0 + bx lr + +oplb8: + movs r0, #1 + lsls r0, #15 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +oplb9: + movs r0, #0x80 + mov r1, r_bc + bics r1, r0 + mov r_bc, r1 + bx lr + +oplba: + movs r0, #1 + lsls r0, #15 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +oplbb: + movs r0, #0x80 + mov r1, r_de + bics r1, r0 + mov r_de, r1 + bx lr + +oplbc: + movs r0, #1 + lsls r0, #15 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +oplbd: + movs r0, #0x80 + mov r1, r_hl + bics r1, r0 + mov r_hl, r1 + bx lr + +oplbe: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x80 + bics r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +oplbf: + movs r0, #1 + lsls r0, #15 + bics r_af, r0 + bx lr + +oplc0: + movs r0, #1 + lsls r0, #8 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +.ltorg +oplc1: + movs r0, #0x01 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplc2: + movs r0, #1 + lsls r0, #8 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplc3: + movs r0, #0x01 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplc4: + movs r0, #1 + lsls r0, #8 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplc5: + movs r0, #0x01 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplc6: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x01 + orrs r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +oplc7: + movs r0, #1 + lsls r0, #8 + orrs r_af, r0 + bx lr + +oplc8: + movs r0, #1 + lsls r0, #9 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplc9: + movs r0, #0x02 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplca: + movs r0, #1 + lsls r0, #9 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplcb: + movs r0, #0x02 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplcc: + movs r0, #1 + lsls r0, #9 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplcd: + movs r0, #0x02 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplce: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x02 + orrs r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +oplcf: + movs r0, #1 + lsls r0, #9 + orrs r_af, r0 + bx lr + +opld0: + movs r0, #1 + lsls r0, #10 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +opld1: + movs r0, #0x04 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +opld2: + movs r0, #1 + lsls r0, #10 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +opld3: + movs r0, #0x04 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +opld4: + movs r0, #1 + lsls r0, #10 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +opld5: + movs r0, #0x04 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +opld6: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x04 + orrs r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +opld7: + movs r0, #1 + lsls r0, #10 + orrs r_af, r0 + bx lr + +opld8: + movs r0, #1 + lsls r0, #11 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +opld9: + movs r0, #0x08 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplda: + movs r0, #1 + lsls r0, #11 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +opldb: + movs r0, #0x08 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +opldc: + movs r0, #1 + lsls r0, #11 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +opldd: + movs r0, #0x08 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplde: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x08 + orrs r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +opldf: + movs r0, #1 + lsls r0, #11 + orrs r_af, r0 + bx lr + +ople0: + movs r0, #1 + lsls r0, #12 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +.ltorg +ople1: + movs r0, #0x10 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +ople2: + movs r0, #1 + lsls r0, #12 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +ople3: + movs r0, #0x10 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +ople4: + movs r0, #1 + lsls r0, #12 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +ople5: + movs r0, #0x10 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +ople6: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x10 + orrs r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +ople7: + movs r0, #1 + lsls r0, #12 + orrs r_af, r0 + bx lr + +ople8: + movs r0, #1 + lsls r0, #13 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +ople9: + movs r0, #0x20 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplea: + movs r0, #1 + lsls r0, #13 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +opleb: + movs r0, #0x20 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplec: + movs r0, #1 + lsls r0, #13 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +opled: + movs r0, #0x20 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplee: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x20 + orrs r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +oplef: + movs r0, #1 + lsls r0, #13 + orrs r_af, r0 + bx lr + +oplf0: + movs r0, #1 + lsls r0, #14 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplf1: + movs r0, #0x40 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplf2: + movs r0, #1 + lsls r0, #14 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplf3: + movs r0, #0x40 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplf4: + movs r0, #1 + lsls r0, #14 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplf5: + movs r0, #0x40 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplf6: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x40 + orrs r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +oplf7: + movs r0, #1 + lsls r0, #14 + orrs r_af, r0 + bx lr + +oplf8: + movs r0, #1 + lsls r0, #15 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplf9: + movs r0, #0x80 + mov r1, r_bc + orrs r1, r0 + mov r_bc, r1 + bx lr + +oplfa: + movs r0, #1 + lsls r0, #15 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplfb: + movs r0, #0x80 + mov r1, r_de + orrs r1, r0 + mov r_de, r1 + bx lr + +oplfc: + movs r0, #1 + lsls r0, #15 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplfd: + movs r0, #0x80 + mov r1, r_hl + orrs r1, r0 + mov r_hl, r1 + bx lr + +oplfe: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r0, #0x80 + orrs r_temp, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + push {lr} + bl write8 + adds r_t, #7 + pop {pc} + +oplff: + movs r0, #1 + lsls r0, #15 + orrs r_af, r0 + bx lr + +.ltorg +// === END cb logic opcodes + +// === BEGIN dd/fd prefix xy opcodes r_temp holds ix or iy +opxy_table: +.short op00 + 1 - opxy_table +.short op01 + 1 - opxy_table +.short op02 + 1 - opxy_table +.short op03 + 1 - opxy_table +.short op04 + 1 - opxy_table +.short op05 + 1 - opxy_table +.short op06 + 1 - opxy_table +.short op07 + 1 - opxy_table +.short op08 + 1 - opxy_table +.short opxy09 + 1 - opxy_table +.short op0a + 1 - opxy_table +.short op0b + 1 - opxy_table +.short op0c + 1 - opxy_table +.short op0d + 1 - opxy_table +.short op0e + 1 - opxy_table +.short op0f + 1 - opxy_table +.short op10 + 1 - opxy_table +.short op11 + 1 - opxy_table +.short op12 + 1 - opxy_table +.short op13 + 1 - opxy_table +.short op14 + 1 - opxy_table +.short op15 + 1 - opxy_table +.short op16 + 1 - opxy_table +.short op17 + 1 - opxy_table +.short op18 + 1 - opxy_table +.short opxy19 + 1 - opxy_table +.short op1a + 1 - opxy_table +.short op1b + 1 - opxy_table +.short op1c + 1 - opxy_table +.short op1d + 1 - opxy_table +.short op1e + 1 - opxy_table +.short op1f + 1 - opxy_table +.short op20 + 1 - opxy_table +.short opxy21 + 1 - opxy_table +.short opxy22 + 1 - opxy_table +.short opxy23 + 1 - opxy_table +.short opxy24 + 1 - opxy_table +.short opxy25 + 1 - opxy_table +.short opxy26 + 1 - opxy_table +.short op27 + 1 - opxy_table +.short op28 + 1 - opxy_table +.short opxy29 + 1 - opxy_table +.short opxy2a + 1 - opxy_table +.short opxy2b + 1 - opxy_table +.short opxy2c + 1 - opxy_table +.short opxy2d + 1 - opxy_table +.short opxy2e + 1 - opxy_table +.short op2f + 1 - opxy_table +.short op30 + 1 - opxy_table +.short op31 + 1 - opxy_table +.short op32 + 1 - opxy_table +.short op33 + 1 - opxy_table +.short opxy34 + 1 - opxy_table +.short opxy35 + 1 - opxy_table +.short opxy36 + 1 - opxy_table +.short op37 + 1 - opxy_table +.short op38 + 1 - opxy_table +.short opxy39 + 1 - opxy_table +.short op3a + 1 - opxy_table +.short op3b + 1 - opxy_table +.short op3c + 1 - opxy_table +.short op3d + 1 - opxy_table +.short op3e + 1 - opxy_table +.short op3f + 1 - opxy_table +.short op40 + 1 - opxy_table +.short op41 + 1 - opxy_table +.short op42 + 1 - opxy_table +.short op43 + 1 - opxy_table +.short opxy44 + 1 - opxy_table +.short opxy45 + 1 - opxy_table +.short opxy46 + 1 - opxy_table +.short op47 + 1 - opxy_table +.short op48 + 1 - opxy_table +.short op49 + 1 - opxy_table +.short op4a + 1 - opxy_table +.short op4b + 1 - opxy_table +.short opxy4c + 1 - opxy_table +.short opxy4d + 1 - opxy_table +.short opxy4e + 1 - opxy_table +.short op4f + 1 - opxy_table +.short op50 + 1 - opxy_table +.short op51 + 1 - opxy_table +.short op52 + 1 - opxy_table +.short op53 + 1 - opxy_table +.short opxy54 + 1 - opxy_table +.short opxy55 + 1 - opxy_table +.short opxy56 + 1 - opxy_table +.short op57 + 1 - opxy_table +.short op58 + 1 - opxy_table +.short op59 + 1 - opxy_table +.short op5a + 1 - opxy_table +.short op5b + 1 - opxy_table +.short opxy5c + 1 - opxy_table +.short opxy5d + 1 - opxy_table +.short opxy5e + 1 - opxy_table +.short op5f + 1 - opxy_table +.short opxy60 + 1 - opxy_table +.short opxy61 + 1 - opxy_table +.short opxy62 + 1 - opxy_table +.short opxy63 + 1 - opxy_table +.short op64 + 1 - opxy_table +.short opxy65 + 1 - opxy_table +.short opxy66 + 1 - opxy_table +.short opxy67 + 1 - opxy_table +.short opxy68 + 1 - opxy_table +.short opxy69 + 1 - opxy_table +.short opxy6a + 1 - opxy_table +.short opxy6b + 1 - opxy_table +.short opxy6c + 1 - opxy_table +.short op6d + 1 - opxy_table +.short opxy6e + 1 - opxy_table +.short opxy6f + 1 - opxy_table +.short opxy70 + 1 - opxy_table +.short opxy71 + 1 - opxy_table +.short opxy72 + 1 - opxy_table +.short opxy73 + 1 - opxy_table +.short opxy74 + 1 - opxy_table +.short opxy75 + 1 - opxy_table +.short op76 + 1 - opxy_table +.short opxy77 + 1 - opxy_table +.short op78 + 1 - opxy_table +.short op79 + 1 - opxy_table +.short op7a + 1 - opxy_table +.short op7b + 1 - opxy_table +.short opxy7c + 1 - opxy_table +.short opxy7d + 1 - opxy_table +.short opxy7e + 1 - opxy_table +.short op7f + 1 - opxy_table +.short op80 + 1 - opxy_table +.short op81 + 1 - opxy_table +.short op82 + 1 - opxy_table +.short op83 + 1 - opxy_table +.short opxy84 + 1 - opxy_table +.short opxy85 + 1 - opxy_table +.short opxy86 + 1 - opxy_table +.short op87 + 1 - opxy_table +.short op88 + 1 - opxy_table +.short op89 + 1 - opxy_table +.short op8a + 1 - opxy_table +.short op8b + 1 - opxy_table +.short opxy8c + 1 - opxy_table +.short opxy8d + 1 - opxy_table +.short opxy8e + 1 - opxy_table +.short op8f + 1 - opxy_table +.short op90 + 1 - opxy_table +.short op91 + 1 - opxy_table +.short op92 + 1 - opxy_table +.short op93 + 1 - opxy_table +.short opxy94 + 1 - opxy_table +.short opxy95 + 1 - opxy_table +.short opxy96 + 1 - opxy_table +.short op97 + 1 - opxy_table +.short op98 + 1 - opxy_table +.short op99 + 1 - opxy_table +.short op9a + 1 - opxy_table +.short op9b + 1 - opxy_table +.short opxy9c + 1 - opxy_table +.short opxy9d + 1 - opxy_table +.short opxy9e + 1 - opxy_table +.short op9f + 1 - opxy_table +.short opa0 + 1 - opxy_table +.short opa1 + 1 - opxy_table +.short opa2 + 1 - opxy_table +.short opa3 + 1 - opxy_table +.short opxya4 + 1 - opxy_table +.short opxya5 + 1 - opxy_table +.short opxya6 + 1 - opxy_table +.short opa7 + 1 - opxy_table +.short opa8 + 1 - opxy_table +.short opa9 + 1 - opxy_table +.short opaa + 1 - opxy_table +.short opab + 1 - opxy_table +.short opxyac + 1 - opxy_table +.short opxyad + 1 - opxy_table +.short opxyae + 1 - opxy_table +.short opaf + 1 - opxy_table +.short opb0 + 1 - opxy_table +.short opb1 + 1 - opxy_table +.short opb2 + 1 - opxy_table +.short opb3 + 1 - opxy_table +.short opxyb4 + 1 - opxy_table +.short opxyb5 + 1 - opxy_table +.short opxyb6 + 1 - opxy_table +.short opb7 + 1 - opxy_table +.short opb8 + 1 - opxy_table +.short opb9 + 1 - opxy_table +.short opba + 1 - opxy_table +.short opbb + 1 - opxy_table +.short opxybc + 1 - opxy_table +.short opxybd + 1 - opxy_table +.short opxybe + 1 - opxy_table +.short opbf + 1 - opxy_table +.short opc0 + 1 - opxy_table +.short opc1 + 1 - opxy_table +.short opc2 + 1 - opxy_table +.short opc3 + 1 - opxy_table +.short opc4 + 1 - opxy_table +.short opc5 + 1 - opxy_table +.short opc6 + 1 - opxy_table +.short opc7 + 1 - opxy_table +.short opc8 + 1 - opxy_table +.short opc9 + 1 - opxy_table +.short opca + 1 - opxy_table +.short opcb + 1 - opxy_table +.short opcc + 1 - opxy_table +.short opcd + 1 - opxy_table +.short opce + 1 - opxy_table +.short opcf + 1 - opxy_table +.short opd0 + 1 - opxy_table +.short opd1 + 1 - opxy_table +.short opd2 + 1 - opxy_table +.short opd3 + 1 - opxy_table +.short opd4 + 1 - opxy_table +.short opd5 + 1 - opxy_table +.short opd6 + 1 - opxy_table +.short opd7 + 1 - opxy_table +.short opd8 + 1 - opxy_table +.short opd9 + 1 - opxy_table +.short opda + 1 - opxy_table +.short opdb + 1 - opxy_table +.short opdc + 1 - opxy_table +.short opdd + 1 - opxy_table +.short opde + 1 - opxy_table +.short opdf + 1 - opxy_table +.short ope0 + 1 - opxy_table +.short opxye1 + 1 - opxy_table +.short ope2 + 1 - opxy_table +.short opxye3 + 1 - opxy_table +.short ope4 + 1 - opxy_table +.short opxye5 + 1 - opxy_table +.short ope6 + 1 - opxy_table +.short ope7 + 1 - opxy_table +.short ope8 + 1 - opxy_table +.short opxye9 + 1 - opxy_table +.short opea + 1 - opxy_table +.short opeb + 1 - opxy_table +.short opec + 1 - opxy_table +.short oped + 1 - opxy_table +.short opee + 1 - opxy_table +.short opef + 1 - opxy_table +.short opf0 + 1 - opxy_table +.short opf1 + 1 - opxy_table +.short opf2 + 1 - opxy_table +.short opf3 + 1 - opxy_table +.short opf4 + 1 - opxy_table +.short opf5 + 1 - opxy_table +.short opf6 + 1 - opxy_table +.short opf7 + 1 - opxy_table +.short opf8 + 1 - opxy_table +.short opxyf9 + 1 - opxy_table +.short opfa + 1 - opxy_table +.short opfb + 1 - opxy_table +.short opfc + 1 - opxy_table +.short opfd + 1 - opxy_table +.short opfe + 1 - opxy_table +.short opff + 1 - opxy_table +opxy09: + mov r0, r_ixy + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_ixy + mov r1, r_bc + push {lr} + bl add16 + mov r_ixy, r0 + adds r_t, #7 + pop {pc} + +opxy19: + mov r0, r_ixy + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_ixy + mov r1, r_de + push {lr} + bl add16 + mov r_ixy, r0 + adds r_t, #7 + pop {pc} + +.ltorg +opxy21: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_ixy, r0 + adds r_t, #6 + pop {pc} + +opxy22: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp_restricted, r0 + adds r0, r_temp_restricted, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_ixy + mov r1, r_temp_restricted + bl write16 + adds r_t, #12 + pop {pc} + +opxy23: + mov r0, r_ixy + adds r0, #1 + uxth r0, r0 + mov r_ixy, r0 + adds r_t, #2 + bx lr + +opxy24: + mov r0, r_ixy + lsrs r0, #8 + push {lr} + bl inc8 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + pop {pc} + +opxy25: + mov r0, r_ixy + lsrs r0, #8 + push {lr} + bl dec8 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + pop {pc} + +opxy26: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + adds r_t, #3 + pop {pc} + +opxy29: + mov r0, r_ixy + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_ixy + mov r1, r_ixy + push {lr} + bl add16 + mov r_ixy, r0 + adds r_t, #7 + pop {pc} + +opxy2a: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp_restricted, r0 + mov r0, r_temp_restricted + bl read16 + mov r_ixy, r0 + adds r0, r_temp_restricted, #1 + uxth r0, r0 + mov r_memptr, r0 + adds r_t, #12 + pop {pc} + +opxy2b: + mov r0, r_ixy + subs r0, #1 + uxth r0, r0 + mov r_ixy, r0 + adds r_t, #2 + bx lr + +opxy2c: + mov r0, r_ixy + uxtb r0, r0 + push {lr} + bl inc8 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + pop {pc} + +opxy2d: + mov r0, r_ixy + uxtb r0, r0 + push {lr} + bl dec8 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + pop {pc} + +opxy2e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + adds r_t, #3 + pop {pc} + +opxy34: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + ldr r1, =z80a_resting_state + str r0, [r1, #76] // scratch + ldr r0, =z80a_resting_state + ldr r0, [r0, #76] // scratch + read8_internal r0 + mov r_temp_restricted, r0 // fine to overwrite hi in r_temp_restricted + uxtb r0, r_temp_restricted + bl inc8 + mov r_temp_restricted, r0 // fine to overwrite hi in r_temp_restricted + ldr r0, =z80a_resting_state + ldr r0, [r0, #76] // scratch + mov r1, r0 + uxtb r0, r_temp_restricted + bl write8 + adds r_t, #15 + pop {pc} + +opxy35: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + ldr r1, =z80a_resting_state + str r0, [r1, #76] // scratch + ldr r0, =z80a_resting_state + ldr r0, [r0, #76] // scratch + read8_internal r0 + mov r_temp_restricted, r0 // fine to overwrite hi in r_temp_restricted + uxtb r0, r_temp_restricted + bl dec8 + mov r_temp_restricted, r0 // fine to overwrite hi in r_temp_restricted + ldr r0, =z80a_resting_state + ldr r0, [r0, #76] // scratch + mov r1, r0 + uxtb r0, r_temp_restricted + bl write8 + adds r_t, #15 + pop {pc} + +opxy36: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + ldr r1, =z80a_resting_state + str r0, [r1, #76] // scratch + mov r0, r_pc + bl read8inc + mov r_pc, r1 + mov r_temp_restricted, r0 // fine to overwrite hi in r_temp_restricted + ldr r0, =z80a_resting_state + ldr r0, [r0, #76] // scratch + mov r1, r0 + uxtb r0, r_temp_restricted + bl write8 + adds r_t, #11 + pop {pc} + +opxy39: + mov r0, r_ixy + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_ixy + mov r1, r_sp + push {lr} + bl add16 + mov r_ixy, r0 + adds r_t, #7 + pop {pc} + +.ltorg +opxy44: + mov r0, r_ixy + lsrs r0, #8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + bx lr + +opxy45: + mov r0, r_ixy + uxtb r0, r0 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + bx lr + +opxy46: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + adds r_t, #11 + pop {pc} + +opxy4c: + mov r0, r_ixy + lsrs r0, #8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + bx lr + +opxy4d: + mov r0, r_ixy + uxtb r0, r0 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + bx lr + +opxy4e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + adds r_t, #11 + pop {pc} + +opxy54: + mov r0, r_ixy + lsrs r0, #8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + bx lr + +opxy55: + mov r0, r_ixy + uxtb r0, r0 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + bx lr + +opxy56: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + adds r_t, #11 + pop {pc} + +opxy5c: + mov r0, r_ixy + lsrs r0, #8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + bx lr + +opxy5d: + mov r0, r_ixy + uxtb r0, r0 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + bx lr + +opxy5e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + adds r_t, #11 + pop {pc} + +opxy60: + mov r0, r_bc + lsrs r0, #8 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + bx lr + +.ltorg +opxy61: + mov r0, r_bc + uxtb r0, r0 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + bx lr + +opxy62: + mov r0, r_de + lsrs r0, #8 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + bx lr + +opxy63: + mov r0, r_de + uxtb r0, r0 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + bx lr + +opxy65: + mov r0, r_ixy + uxtb r0, r0 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + bx lr + +opxy66: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + adds r_t, #11 + pop {pc} + +opxy67: + lsrs r0, r_af, #8 + lsls r0, #8 + mov r1, r_ixy + uxtb r1, r1 + orrs r0, r1 + mov r_ixy, r0 + bx lr + +opxy68: + mov r0, r_bc + lsrs r0, #8 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + bx lr + +opxy69: + mov r0, r_bc + uxtb r0, r0 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + bx lr + +opxy6a: + mov r0, r_de + lsrs r0, #8 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + bx lr + +opxy6b: + mov r0, r_de + uxtb r0, r0 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + bx lr + +opxy6c: + mov r0, r_ixy + lsrs r0, #8 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + bx lr + +opxy6e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + adds r_t, #11 + pop {pc} + +opxy6f: + lsrs r0, r_af, #8 + mov r1, r_ixy + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_ixy, r1 + bx lr + +opxy70: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + mov r1, r0 + mov r0, r_bc + lsrs r0, #8 + bl write8 + adds r_t, #11 + pop {pc} + +opxy71: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + mov r1, r0 + mov r0, r_bc + uxtb r0, r0 + bl write8 + adds r_t, #11 + pop {pc} + +opxy72: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + mov r1, r0 + mov r0, r_de + lsrs r0, #8 + bl write8 + adds r_t, #11 + pop {pc} + +opxy73: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + mov r1, r0 + mov r0, r_de + uxtb r0, r0 + bl write8 + adds r_t, #11 + pop {pc} + +opxy74: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + mov r1, r0 + mov r0, r_hl + lsrs r0, #8 + bl write8 + adds r_t, #11 + pop {pc} + +opxy75: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + mov r1, r0 + mov r0, r_hl + uxtb r0, r0 + bl write8 + adds r_t, #11 + pop {pc} + +opxy77: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + mov r1, r0 + lsrs r0, r_af, #8 + bl write8 + adds r_t, #11 + pop {pc} + +opxy7c: + mov r0, r_ixy + lsrs r0, #8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + bx lr + +opxy7d: + mov r0, r_ixy + uxtb r0, r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + bx lr + +opxy7e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + adds r_t, #11 + pop {pc} + +.ltorg +opxy84: + mov r0, r_ixy + lsrs r0, #8 + ldr r2, =add8 + bx r2 + +opxy85: + mov r0, r_ixy + uxtb r0, r0 + ldr r2, =add8 + bx r2 + +opxy86: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + bl add8 + adds r_t, #11 + pop {pc} + +opxy8c: + mov r0, r_ixy + lsrs r0, #8 + ldr r2, =adc8 + bx r2 + +opxy8d: + mov r0, r_ixy + uxtb r0, r0 + ldr r2, =adc8 + bx r2 + +opxy8e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + bl adc8 + adds r_t, #11 + pop {pc} + +opxy94: + mov r0, r_ixy + lsrs r0, #8 + ldr r2, =sub8 + bx r2 + +opxy95: + mov r0, r_ixy + uxtb r0, r0 + ldr r2, =sub8 + bx r2 + +opxy96: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + bl sub8 + adds r_t, #11 + pop {pc} + +opxy9c: + mov r0, r_ixy + lsrs r0, #8 + ldr r2, =sbc8 + bx r2 + +opxy9d: + mov r0, r_ixy + uxtb r0, r0 + ldr r2, =sbc8 + bx r2 + +opxy9e: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + bl sbc8 + adds r_t, #11 + pop {pc} + +.ltorg +opxya4: + mov r0, r_ixy + lsrs r0, #8 + ldr r2, =ands8 + bx r2 + +opxya5: + mov r0, r_ixy + uxtb r0, r0 + ldr r2, =ands8 + bx r2 + +opxya6: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + bl ands8 + adds r_t, #11 + pop {pc} + +opxyac: + mov r0, r_ixy + lsrs r0, #8 + ldr r2, =xor8 + bx r2 + +opxyad: + mov r0, r_ixy + uxtb r0, r0 + ldr r2, =xor8 + bx r2 + +opxyae: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + bl xor8 + adds r_t, #11 + pop {pc} + +opxyb4: + mov r0, r_ixy + lsrs r0, #8 + ldr r2, =or8 + bx r2 + +opxyb5: + mov r0, r_ixy + uxtb r0, r0 + ldr r2, =or8 + bx r2 + +opxyb6: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + bl or8 + adds r_t, #11 + pop {pc} + +opxybc: + mov r0, r_ixy + lsrs r0, #8 + ldr r2, =cp8 + bx r2 + +opxybd: + mov r0, r_ixy + uxtb r0, r0 + ldr r2, =cp8 + bx r2 + +opxybe: + mov r0, r_pc + push {lr} + bl read8inc + mov r_pc, r1 + sxtb r0, r0 + mov r1, r_ixy + add r0, r1 + uxth r0, r0 + read8_internal r0 + bl cp8 + adds r_t, #11 + pop {pc} + +.ltorg +.ltorg +opxye1: + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_ixy, r0 + adds r_t, #6 + pop {pc} + +opxye3: + mov r0, r_sp + push {lr} + bl read16 + mov r_memptr, r0 + mov r0, r_ixy + mov r1, r_sp + bl write16 + mov r_ixy, r_memptr + adds r_t, #15 + pop {pc} + +opxye5: + mov r0, r_ixy + push {lr} + bl _push + adds r_t, #7 + pop {pc} + +opxye9: + mov r_pc, r_ixy + bx lr + +opxyf9: + mov r_sp, r_ixy + adds r_t, #2 + bx lr + +.ltorg +// === END dd/fd prefix xy opcodes r_temp holds ix or iy + +// === BEGIN ed prefix +ope_table: +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short ope40 + 1 - ope_table +.short ope41 + 1 - ope_table +.short ope42 + 1 - ope_table +.short ope43 + 1 - ope_table +.short ope44 + 1 - ope_table +.short ope45 + 1 - ope_table +.short ope46 + 1 - ope_table +.short ope47 + 1 - ope_table +.short ope48 + 1 - ope_table +.short ope49 + 1 - ope_table +.short ope4a + 1 - ope_table +.short ope4b + 1 - ope_table +.short ope4c + 1 - ope_table +.short ope4d + 1 - ope_table +.short ope4e + 1 - ope_table +.short ope4f + 1 - ope_table +.short ope50 + 1 - ope_table +.short ope51 + 1 - ope_table +.short ope52 + 1 - ope_table +.short ope53 + 1 - ope_table +.short ope54 + 1 - ope_table +.short ope55 + 1 - ope_table +.short ope56 + 1 - ope_table +.short ope57 + 1 - ope_table +.short ope58 + 1 - ope_table +.short ope59 + 1 - ope_table +.short ope5a + 1 - ope_table +.short ope5b + 1 - ope_table +.short ope5c + 1 - ope_table +.short ope5d + 1 - ope_table +.short ope5e + 1 - ope_table +.short ope5f + 1 - ope_table +.short ope60 + 1 - ope_table +.short ope61 + 1 - ope_table +.short ope62 + 1 - ope_table +.short ope63 + 1 - ope_table +.short ope64 + 1 - ope_table +.short ope65 + 1 - ope_table +.short ope66 + 1 - ope_table +.short ope67 + 1 - ope_table +.short ope68 + 1 - ope_table +.short ope69 + 1 - ope_table +.short ope6a + 1 - ope_table +.short ope6b + 1 - ope_table +.short ope6c + 1 - ope_table +.short ope6d + 1 - ope_table +.short ope6e + 1 - ope_table +.short ope6f + 1 - ope_table +.short ope70 + 1 - ope_table +.short ope71 + 1 - ope_table +.short ope72 + 1 - ope_table +.short ope73 + 1 - ope_table +.short ope74 + 1 - ope_table +.short ope75 + 1 - ope_table +.short ope76 + 1 - ope_table +.short ope77 + 1 - ope_table +.short ope78 + 1 - ope_table +.short ope79 + 1 - ope_table +.short ope7a + 1 - ope_table +.short ope7b + 1 - ope_table +.short ope7c + 1 - ope_table +.short ope7d + 1 - ope_table +.short ope7e + 1 - ope_table +.short ope7f + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short opea0 + 1 - ope_table +.short opea1 + 1 - ope_table +.short opea2 + 1 - ope_table +.short opea3 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short opea8 + 1 - ope_table +.short opea9 + 1 - ope_table +.short opeaa + 1 - ope_table +.short opeab + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short opeb0 + 1 - ope_table +.short opeb1 + 1 - ope_table +.short opeb2 + 1 - ope_table +.short opeb3 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short opeb8 + 1 - ope_table +.short opeb9 + 1 - ope_table +.short opeba + 1 - ope_table +.short opebb + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.short op00 + 1 - ope_table +.ltorg +ope40: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + mov r0, r_bc + lsrs r0, #8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + pop {pc} + +.ltorg +ope41: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + lsrs r0, #8 + mov r1, r_bc + ldr r2, =iowrite8 + bx r2 + +ope42: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl sbc16 + adds r_t, #7 + pop {pc} + +ope43: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + mov r1, r_temp + bl write16 + adds r_t, #12 + pop {pc} + +ope44: + ldr r2, =neg8 + bx r2 + +ope45: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_temp, r0 + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #6 + pop {pc} + +ope46: + movs r0, #0 + ldr r1, =z80a_resting_state + str r0, [r1, #0] // im + bx lr + +ope47: + lsrs r0, r_af, #8 + ldr r1, =z80a_resting_state + strb r0, [r1, #13] // i + adds r_t, #1 + bx lr + +ope48: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + mov r1, r_bc + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_bc, r1 + mov r0, r_bc + uxtb r0, r0 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + pop {pc} + +ope49: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc // high half of word is ignored later + mov r1, r_bc + ldr r2, =iowrite8 + bx r2 + +ope4a: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl adc16 + adds r_t, #7 + pop {pc} + +ope4b: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_temp + bl read16 + mov r_bc, r0 + adds r_t, #12 + pop {pc} + +ope4c: + ldr r2, =neg8 + bx r2 + +ope4d: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_temp, r0 + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #6 + pop {pc} + +ope4e: + movs r0, #1 + ldr r1, =z80a_resting_state + str r0, [r1, #0] // im + bx lr + +ope4f: + lsrs r0, r_af, #8 + ldr r1, =z80a_resting_state + strb r0, [r1, #12] // r_low + lsrs r0, r_af, #8 + movs r1, #0x80 + ands r0, r1 + ldr r1, =z80a_resting_state + strb r0, [r1, #8] // r_hi + adds r_t, #1 + bx lr + +ope50: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + lsls r0, #8 + mov r1, r_de + uxtb r1, r1 + orrs r0, r1 + mov r_de, r0 + mov r0, r_de + lsrs r0, #8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + pop {pc} + +ope51: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_de + lsrs r0, #8 + mov r1, r_bc + ldr r2, =iowrite8 + bx r2 + +ope52: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_de + push {lr} + bl sbc16 + adds r_t, #7 + pop {pc} + +ope53: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_de + mov r1, r_temp + bl write16 + adds r_t, #12 + pop {pc} + +ope54: + ldr r2, =neg8 + bx r2 + +ope55: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_temp, r0 + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #6 + pop {pc} + +ope56: + movs r0, #1 + ldr r1, =z80a_resting_state + str r0, [r1, #0] // im + bx lr + +ope57: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #13] // i + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + lsrs r0, r_af, #8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + movs r1, #PV + bics r_af, r1 + adds r_t, #1 + ldr r0, =z80a_resting_state + ldrb r0, [r0, #9] // iff1 + cmp r0, #0 + beq 1f +#ifndef USE_Z80_ARM_OFFSET_T + ldr r0, frame_tacts +#else + movs r0, #0 +#endif + subs r0, #10 + cmp r_t, r0 + bge 1f + adds r_af, #PV +1: + bx lr + +ope58: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + mov r1, r_de + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_de, r1 + mov r0, r_de + uxtb r0, r0 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + pop {pc} + +ope59: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_de // high half of word is ignored later + mov r1, r_bc + ldr r2, =iowrite8 + bx r2 + +ope5a: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_de + push {lr} + bl adc16 + adds r_t, #7 + pop {pc} + +ope5b: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + mov r0, r_temp + bl read16 + mov r_de, r0 + adds r_t, #12 + pop {pc} + +ope5c: + ldr r2, =neg8 + bx r2 + +ope5d: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_temp, r0 + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #6 + pop {pc} + +ope5e: + movs r0, #2 + ldr r1, =z80a_resting_state + str r0, [r1, #0] // im + bx lr + +ope5f: +#ifdef NO_UPDATE_RLOW_IN_FETCH + lsrs r0, r_t, #2 +#else + ldr r0, =z80a_resting_state + ldrb r0, [r0, #12] // r_low +#endif + movs r1, #0x7f + ands r0, r1 + ldr r1, =z80a_resting_state + ldrb r1, [r1, #8] // r_hi + orrs r0, r1 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + lsrs r0, r_af, #8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + movs r1, #PV + bics r_af, r1 + adds r_t, #1 + ldr r0, =z80a_resting_state + ldrb r0, [r0, #9] // iff1 + cmp r0, #0 + beq 1f +#ifndef USE_Z80_ARM_OFFSET_T + ldr r0, frame_tacts +#else + movs r0, #0 +#endif + subs r0, #10 + cmp r_t, r0 + blt 2f + ldr r0, =z80a_resting_state + ldr r0, [r0, #4] // eipos + adds r0, #8 + cmp r0, r_t + bne 1f +2: + adds r_af, #PV +1: + bx lr + +ope60: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + lsls r0, #8 + mov r1, r_hl + uxtb r1, r1 + orrs r0, r1 + mov r_hl, r0 + mov r0, r_hl + lsrs r0, #8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + pop {pc} + +.ltorg +ope61: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + lsrs r0, #8 + mov r1, r_bc + ldr r2, =iowrite8 + bx r2 + +ope62: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + push {lr} + bl sbc16 + adds r_t, #7 + pop {pc} + +ope63: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + mov r1, r_temp + bl write16 + adds r_t, #12 + pop {pc} + +ope64: + ldr r2, =neg8 + bx r2 + +ope65: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_temp, r0 + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #6 + pop {pc} + +ope66: + movs r0, #0 + ldr r1, =z80a_resting_state + str r0, [r1, #0] // im + bx lr + +ope67: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + lsrs r0, r_af, #8 + lsls r0, #4 + uxtb r1, r_temp + lsrs r1, #4 + orrs r0, r1 + mov r1, r_hl + push {lr} + bl write8 + lsrs r0, r_af, #8 + movs r2, #0xf0 + ands r0, r2 + uxtb r1, r_temp + bics r1, r2 + orrs r0, r1 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + lsrs r0, r_af, #8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_t, #10 + pop {pc} + +ope68: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + mov r1, r_hl + lsrs r1, #8 + lsls r1, #8 + orrs r1, r0 + mov r_hl, r1 + mov r0, r_hl + uxtb r0, r0 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + pop {pc} + +ope69: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl // high half of word is ignored later + mov r1, r_bc + ldr r2, =iowrite8 + bx r2 + +ope6a: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_hl + push {lr} + bl adc16 + adds r_t, #7 + pop {pc} + +ope6b: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_temp + bl read16 + mov r_hl, r0 + adds r_t, #12 + pop {pc} + +ope6c: + ldr r2, =neg8 + bx r2 + +ope6d: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_temp, r0 + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #6 + pop {pc} + +ope6e: + movs r0, #1 + ldr r1, =z80a_resting_state + str r0, [r1, #0] // im + bx lr + +ope6f: + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + lsrs r0, r_af, #8 + movs r2, #0xf + ands r0, r2 + uxtb r1, r_temp + lsls r1, #4 + orrs r0, r1 + mov r1, r_hl + push {lr} + bl write8 + lsrs r0, r_af, #8 + movs r2, #0xf0 + ands r0, r2 + uxtb r1, r_temp + lsrs r1, #4 + orrs r0, r1 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + lsrs r0, r_af, #8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + adds r_t, #10 + pop {pc} + +ope70: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + pop {pc} + +ope71: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + movs r0, #0 + mov r1, r_bc + ldr r2, =iowrite8 + bx r2 + +ope72: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_sp + push {lr} + bl sbc16 + adds r_t, #7 + pop {pc} + +ope73: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_sp + mov r1, r_temp + bl write16 + adds r_t, #12 + pop {pc} + +ope74: + ldr r2, =neg8 + bx r2 + +ope75: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_temp, r0 + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #6 + pop {pc} + +ope76: + movs r0, #1 + ldr r1, =z80a_resting_state + str r0, [r1, #0] // im + bx lr + +ope77: + bx lr + +ope78: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + lsls r0, #8 + uxtb r1, r_af + orrs r0, r1 + mov r_af, r0 + lsrs r0, r_af, #8 + preserve_only_flags r1, CF + ldr r1, =_log_f + ldrb r0, [r1, r0] + orrs r_af, r0 + pop {pc} + +ope79: + adds r_t, #4 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_af + lsrs r0, #8 + mov r1, r_bc + ldr r2, =iowrite8 + bx r2 + +ope7a: + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_sp + push {lr} + bl adc16 + adds r_t, #7 + pop {pc} + +ope7b: + mov r0, r_pc + push {lr} + bl read16inc + mov r_pc, r1 + mov r_temp, r0 + adds r0, r_temp, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_temp + bl read16 + mov r_sp, r0 + adds r_t, #12 + pop {pc} + +ope7c: + ldr r2, =neg8 + bx r2 + +ope7d: + ldr r0, =z80a_resting_state + ldrb r0, [r0, #10] // iff2 + ldr r1, =z80a_resting_state + strb r0, [r1, #9] // iff1 + mov r0, r_sp + push {lr} + bl read16inc + mov r_sp, r1 + mov r_temp, r0 + mov r_pc, r_temp + mov r_memptr, r_temp + adds r_t, #6 + pop {pc} + +ope7e: + movs r0, #2 + ldr r1, =z80a_resting_state + str r0, [r1, #0] // im + bx lr + +ope7f: + bx lr + +.ltorg +opea0: + push {lr} + bl ldX_common + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_de + adds r0, #1 + uxth r0, r0 + mov r_de, r0 + pop {pc} + +.ltorg +opea1: + push {lr} + bl cpX_common + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_hl, r0 + pop {pc} + +opea2: + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + adds r_t, #8 + mov r0, r_bc + push {lr} + bl ioread8 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r1, #1 + lsls r1, #8 + mov r0, r_bc + subs r0, r1 + uxth r0, r0 + mov r_bc, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_bc // high half of word is ignored later + add r0, r_temp + adds r0, #1 + // a2a3aaabflags r_temp, r0 + lsrs r_af, #8 + lsls r_af, #8 + mov r2, r_bc + lsrs r2, #8 + ldr r1, =_log_f + ldrb r2, [r1, r2] + movs r1, #PV + bics r2, r1 + orrs r_af, r2 + uxtb r0, r0 + cmp r0, r_temp + bge 1f + adds r_af, #HF|CF +1: + lsrs r1, r_temp, #8 + bcc 1f + adds r_af, #NF +1: + lsls r1, r0, #29 + lsrs r1, #21 + mov r2, r_bc + eors r1, r2 + lsrs r1, #8 + ldr r0, =_log_f + ldrb r0, [r0, r1] + movs r1, #PV + ands r0, r1 + orrs r_af, r0 + pop {pc} + +opea3: + adds r_t, #8 + mov r0, r_hl + push {lr} + bl read8inc + mov r_hl, r1 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r1, #1 + lsls r1, #8 + mov r0, r_bc + subs r0, r1 + uxth r0, r0 + mov r_bc, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_bc + bl iowrite8 + mov r0, r_hl // high half of word is ignored later + add r0, r_temp + // a2a3aaabflags r_temp, r0 + lsrs r_af, #8 + lsls r_af, #8 + mov r2, r_bc + lsrs r2, #8 + ldr r1, =_log_f + ldrb r2, [r1, r2] + movs r1, #PV + bics r2, r1 + orrs r_af, r2 + uxtb r0, r0 + cmp r0, r_temp + bge 1f + adds r_af, #HF|CF +1: + lsrs r1, r_temp, #8 + bcc 1f + adds r_af, #NF +1: + lsls r1, r0, #29 + lsrs r1, #21 + mov r2, r_bc + eors r1, r2 + lsrs r1, #8 + ldr r0, =_log_f + ldrb r0, [r0, r1] + movs r1, #PV + ands r0, r1 + orrs r_af, r0 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + pop {pc} + +opea8: + push {lr} + bl ldX_common + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_de + subs r0, #1 + uxth r0, r0 + mov r_de, r0 + pop {pc} + +opea9: + push {lr} + bl cpX_common + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + pop {pc} + +opeaa: + mov r0, r_bc + subs r0, #1 + uxth r0, r0 + mov r_memptr, r0 + adds r_t, #8 + mov r0, r_bc + push {lr} + bl ioread8 + mov r_temp, r0 // fine to overwrite hi in r_temp + movs r1, #1 + lsls r1, #8 + mov r0, r_bc + subs r0, r1 + uxth r0, r0 + mov r_bc, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_hl + bl write8 + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_bc // high half of word is ignored later + add r0, r_temp + subs r0, #1 + // a2a3aaabflags r_temp, r0 + lsrs r_af, #8 + lsls r_af, #8 + mov r2, r_bc + lsrs r2, #8 + ldr r1, =_log_f + ldrb r2, [r1, r2] + movs r1, #PV + bics r2, r1 + orrs r_af, r2 + uxtb r0, r0 + cmp r0, r_temp + bge 1f + adds r_af, #HF|CF +1: + lsrs r1, r_temp, #8 + bcc 1f + adds r_af, #NF +1: + lsls r1, r0, #29 + lsrs r1, #21 + mov r2, r_bc + eors r1, r2 + lsrs r1, #8 + ldr r0, =_log_f + ldrb r0, [r0, r1] + movs r1, #PV + ands r0, r1 + orrs r_af, r0 + pop {pc} + +opeab: + adds r_t, #8 + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + movs r1, #1 + lsls r1, #8 + mov r0, r_bc + subs r0, r1 + uxth r0, r0 + mov r_bc, r0 + mov r0, r_temp // high half of word is ignored later + mov r1, r_bc + push {lr} + bl iowrite8 + mov r0, r_hl // high half of word is ignored later + add r0, r_temp + // a2a3aaabflags r_temp, r0 + lsrs r_af, #8 + lsls r_af, #8 + mov r2, r_bc + lsrs r2, #8 + ldr r1, =_log_f + ldrb r2, [r1, r2] + movs r1, #PV + bics r2, r1 + orrs r_af, r2 + uxtb r0, r0 + cmp r0, r_temp + bge 1f + adds r_af, #HF|CF +1: + lsrs r1, r_temp, #8 + bcc 1f + adds r_af, #NF +1: + lsls r1, r0, #29 + lsrs r1, #21 + mov r2, r_bc + eors r1, r2 + lsrs r1, #8 + ldr r0, =_log_f + ldrb r0, [r0, r1] + movs r1, #PV + ands r0, r1 + orrs r_af, r0 + mov r0, r_bc + subs r0, #1 + uxth r0, r0 + mov r_memptr, r0 + pop {pc} + +ldX_common: + adds r_t, #8 + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 // fine to overwrite hi in r_temp + mov r0, r_temp // high half of word is ignored later + mov r1, r_de + push {lr} + bl write8 + preserve_only_flags r1, (CF|SF|ZF) + lsrs r0, r_af, #8 + add r_temp, r0 + bl set_af35_special_r_temp + mov r0, r_bc + subs r0, #1 + uxth r0, r0 + mov r_bc, r0 + beq 6f + adds r_af, #PV +6: + pop {pc} + +cpX_common: + push {lr} + adds r_t, #8 + push {r_af} + mov r0, r_hl + read8_internal r0 + mov r_temp, r0 + bl cp8 + lsrs r0, r_af, #8 + subs r0, r_temp + lsrs r1, r_af, #HF_INDEX+1 +bcc 6f + subs r0, #1 +6: + mov r_temp, r0 + preserve_only_flags r1, (SF|HF|NF|ZF) + bl set_af35_special_r_temp + pop {r0} + movs r1, #CF + ands r0, r1 + orrs r_af, r0 + mov r0, r_bc + subs r0, #1 + uxth r0, r0 + mov r_bc, r0 + beq 6f + adds r_af, #PV +6: + mov r0, r_memptr + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + pop {pc} + +opeb0: + push {lr} + bl ldX_common + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_de + adds r0, #1 + uxth r0, r0 + mov r_de, r0 + lsrs r0, r_af, #3 + bcc 2f + subs r_pc, #2 + uxth r_pc, r_pc + adds r_t, #5 + adds r0, r_pc, #1 + uxth r0, r0 + mov r_memptr, r0 +2: + pop {pc} + +.ltorg +opeb1: + push {lr} + bl cpX_common + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_hl, r0 + lsrs r0, r_af, #3 + bcc 2f + lsrs r0, r_af, #7 + bcs 3f + subs r_pc, #2 + uxth r_pc, r_pc + adds r_t, #5 + adds r0, r_pc, #1 + uxth r0, r0 + mov r_memptr, r0 +3: +2: + pop {pc} + +opeb2: + adds r_t, #8 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + mov r1, r_hl + bl write8 + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_bc + lsrs r0, #8 + bl dec8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + mov r0, r_bc + lsrs r0, #8 + cmp r0, #0 + beq 1f + movs r0, #4 + orrs r_af, r0 + subs r_pc, #2 + uxth r_pc, r_pc + adds r_t, #5 + pop {pc} + +1: + movs r0, #4 + bics r_af, r0 + pop {pc} + +opeb3: + adds r_t, #8 + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl dec8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + mov r0, r_hl + read8_internal r0 + mov r1, r_bc + bl iowrite8 + mov r0, r_hl + adds r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_bc + adds r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + lsrs r0, #8 + cmp r0, #0 + beq 1f + movs r0, #4 + orrs r_af, r0 + subs r_pc, #2 + uxth r_pc, r_pc + adds r_t, #5 + movs r0, #1 + bics r_af, r0 + mov r0, r_hl + uxtb r0, r0 + cmp r0, #0 + bne 1f + movs r0, #1 + orrs r_af, r0 +1: + pop {pc} + +1: + movs r0, #4 + bics r_af, r0 + mov r0, r_hl + uxtb r0, r0 + cmp r0, #0 + bne 1f + movs r0, #1 + orrs r_af, r0 +1: + pop {pc} + +opeb8: + push {lr} + bl ldX_common + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_de + subs r0, #1 + uxth r0, r0 + mov r_de, r0 + lsrs r0, r_af, #3 + bcc 2f + subs r_pc, #2 + uxth r_pc, r_pc + adds r_t, #5 + adds r0, r_pc, #1 + uxth r0, r0 + mov r_memptr, r0 +2: + pop {pc} + +opeb9: + push {lr} + bl cpX_common + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + lsrs r0, r_af, #3 + bcc 2f + lsrs r0, r_af, #7 + bcs 3f + subs r_pc, #2 + uxth r_pc, r_pc + adds r_t, #5 + adds r0, r_pc, #1 + uxth r0, r0 + mov r_memptr, r0 +3: +2: + pop {pc} + +opeba: + adds r_t, #8 + mov r0, r_bc + subs r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + push {lr} + bl ioread8 + mov r1, r_hl + bl write8 + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_bc + lsrs r0, #8 + bl dec8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + mov r0, r_bc + lsrs r0, #8 + cmp r0, #0 + beq 1f + movs r0, #4 + orrs r_af, r0 + subs r_pc, #2 + uxth r_pc, r_pc + adds r_t, #5 + pop {pc} + +1: + movs r0, #4 + bics r_af, r0 + pop {pc} + +opebb: + adds r_t, #8 + mov r0, r_bc + lsrs r0, #8 + push {lr} + bl dec8 + lsls r0, #8 + mov r1, r_bc + uxtb r1, r1 + orrs r0, r1 + mov r_bc, r0 + mov r0, r_hl + read8_internal r0 + mov r1, r_bc + bl iowrite8 + mov r0, r_hl + subs r0, #1 + uxth r0, r0 + mov r_hl, r0 + mov r0, r_bc + subs r0, #1 + uxth r0, r0 + mov r_memptr, r0 + mov r0, r_bc + lsrs r0, #8 + cmp r0, #0 + beq 1f + movs r0, #4 + orrs r_af, r0 + subs r_pc, #2 + uxth r_pc, r_pc + adds r_t, #5 + movs r0, #1 + bics r_af, r0 + mov r0, r_hl + uxtb r0, r0 + cmp r0, #255 + bne 1f + movs r0, #1 + orrs r_af, r0 +1: + pop {pc} + +1: + movs r0, #4 + bics r_af, r0 + mov r0, r_hl + uxtb r0, r0 + cmp r0, #255 + bne 1f + movs r0, #1 + orrs r_af, r0 +1: + pop {pc} + +.ltorg +.ltorg +.ltorg +// === END ed prefix diff --git a/khan/z80t.cpp b/khan/z80t.cpp new file mode 100644 index 0000000..3dcec4c --- /dev/null +++ b/khan/z80t.cpp @@ -0,0 +1,576 @@ +/* +Portable ZX-Spectrum emulator. +Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "../std.h" +#include "../devices/memory.h" +#include "../devices/ula.h" +#include "../devices/device.h" + +#include "z80t.h" + +namespace Z80t +{ + const char *eZ80t::arm_regs[REG_COUNT]; + + static eZ80t::CALLFUNC seen_opcodes[2048]; + static const char *seen_prefix[2048]; + static int seen_num[2048]; + + int seen_opcode_count; + + bool eZ80t::lr_saved; + bool eZ80t::r_temp_used; + bool eZ80t::r_temp_restricted_used; + + rs_var_init(halted); + rs_var_init(iff1); + rs_var_init(iff2); + rs_var_init(r_hi); + rs_var_init(r_low); + rs_var_init(i); + rs_var_init(im); + rs_var_init(eipos); + + rs_var_struct_init(alt, bc); + rs_var_struct_init(alt, de); + rs_var_struct_init(alt, hl); + rs_var_struct_init(alt, af); + rs_var_init(scratch); + + const char *eZ80t::pending_call = NULL; + +#define IMPL_REG16(REGNAME, reg, low, high)\ + struct eZ80t::Reg16 eZ80t::reg; \ + struct eZ80t::RegLo eZ80t::low; \ + struct eZ80t::RegHi eZ80t::high; + + IMPL_REG16(PC, pc, pc_l, pc_h) + IMPL_REG16(SP, sp, sp_l, sp_h) + IMPL_REG16(MEMPTR, memptr, mem_l, mem_h) // undocumented register + IMPL_REG16(IX, ix, xl, xh) + IMPL_REG16(IY, iy, yl, yh) + + IMPL_REG16(BC, bc, c, b) + IMPL_REG16(DE, de, e, d) + IMPL_REG16(HL, hl, l, h) + IMPL_REG16(AF, af, f, a) + IMPL_REG16(TEMPORARY, temporary, temp_lo, temp_hi) + IMPL_REG16(TEMPORARY_RESTRICTED, restricted, restriced_lo, restricted_hi) + + //============================================================================= + // eZ80t::eZ80 + //----------------------------------------------------------------------------- + eZ80t::eZ80t() + { + + arm_regs[BC] = "r_bc"; + arm_regs[DE] = "r_de"; + arm_regs[AF] = "r_af"; + arm_regs[IX] = "r_ixy"; + arm_regs[IY] = "r_ixy"; + arm_regs[MEMPTR] = "r_memptr"; + arm_regs[PC] = "r_pc"; + arm_regs[HL] = "r_hl"; + arm_regs[SP] = "r_sp"; + arm_regs[TEMPORARY] = "r_temp"; + arm_regs[TEMPORARY_RESTRICTED] = "r_temp_restricted"; + } + //============================================================================= + eZ80t::ZeroExtendedByteInR0 eZ80t::IoRead(WordInR0 port) const { + // address already in r0 + emit_call_func("ioread8"); + return ZeroExtendedByteInR0(); + } + void eZ80t::IoWrite(WordInR1 port, LoByteAndGarbageInR0 v) { + emit_call_func("iowrite8"); + } + eZ80t::ZeroExtendedByteInR0 eZ80t::Read(WordInR0 addr) const { + // address already in r0 +#ifndef USE_SINGLE_64K_MEMORY + emit_call_func("read8"); +#else + emit("read8_internal", "r0"); +#endif + return ZeroExtendedByteInR0(); + } + eZ80t::ZeroExtendedByteInR0 eZ80t::ReadInc(Reg16Ref addr) const { + emit_r0_from_reg(addr); + emit_call_func("read8inc"); + emit_reg_from_r1(addr); + return ZeroExtendedByteInR0(); + } + eZ80t::WordInR0 eZ80t::Read2(WordInR0 addr) const { + // address already in r0 + emit_call_func("read16"); + return WordInR0(); + } // low then high + eZ80t::WordInR0 eZ80t::Read2Inc(Reg16Ref addr) const { + emit_r0_from_reg(addr); + emit_call_func("read16inc"); + emit_reg_from_r1(addr); + return WordInR0(); + } + void eZ80t::Write(Reg16Ref addr, LoByteAndGarbageInR0 v) { + WordInR1 __unused x = addr; + emit_call_func("write8"); + } + void eZ80t::WriteXY(WordInR0 addr, Reg8Ref v) { + emit("mov ", "r1, r0"); + if (v.lo) { + emit_r0_from_reg_lo(v); + } else { + emit_r0_from_reg_hi(v); + } + emit_call_func("write8"); + } + + void eZ80t::Write2(Reg16Ref addr, WordInR0 v) { + emit_r1_from_reg(addr); + emit_call_func("write16"); + } // low then high + + void eZ80t::generate_arm() { + emit("// ================================"); + emit("// == AUTOGENERATED: DO NOT EDIT =="); + emit("// ================================"); + emit(""); + emit("// ALWAYS DIFF THIS CODE AFTER GENERATION - SENSITVE TO COMPILER VERSION CHANGES!"); + emit(""); + + const CALLFUNC opcodes[] = + { + &eZ80t::Op00, &eZ80t::Op01, &eZ80t::Op02, &eZ80t::Op03, &eZ80t::Op04, &eZ80t::Op05, + &eZ80t::Op06, &eZ80t::Op07, + &eZ80t::Op08, &eZ80t::Op09, &eZ80t::Op0A, &eZ80t::Op0B, &eZ80t::Op0C, &eZ80t::Op0D, + &eZ80t::Op0E, &eZ80t::Op0F, + &eZ80t::Op10, &eZ80t::Op11, &eZ80t::Op12, &eZ80t::Op13, &eZ80t::Op14, &eZ80t::Op15, + &eZ80t::Op16, &eZ80t::Op17, + &eZ80t::Op18, &eZ80t::Op19, &eZ80t::Op1A, &eZ80t::Op1B, &eZ80t::Op1C, &eZ80t::Op1D, + &eZ80t::Op1E, &eZ80t::Op1F, + &eZ80t::Op20, &eZ80t::Op21, &eZ80t::Op22, &eZ80t::Op23, &eZ80t::Op24, &eZ80t::Op25, + &eZ80t::Op26, &eZ80t::Op27, + &eZ80t::Op28, &eZ80t::Op29, &eZ80t::Op2A, &eZ80t::Op2B, &eZ80t::Op2C, &eZ80t::Op2D, + &eZ80t::Op2E, &eZ80t::Op2F, + &eZ80t::Op30, &eZ80t::Op31, &eZ80t::Op32, &eZ80t::Op33, &eZ80t::Op34, &eZ80t::Op35, + &eZ80t::Op36, &eZ80t::Op37, + &eZ80t::Op38, &eZ80t::Op39, &eZ80t::Op3A, &eZ80t::Op3B, &eZ80t::Op3C, &eZ80t::Op3D, + &eZ80t::Op3E, &eZ80t::Op3F, + + &eZ80t::Op40, &eZ80t::Op41, &eZ80t::Op42, &eZ80t::Op43, &eZ80t::Op44, &eZ80t::Op45, + &eZ80t::Op46, &eZ80t::Op47, + &eZ80t::Op48, &eZ80t::Op49, &eZ80t::Op4A, &eZ80t::Op4B, &eZ80t::Op4C, &eZ80t::Op4D, + &eZ80t::Op4E, &eZ80t::Op4F, + &eZ80t::Op50, &eZ80t::Op51, &eZ80t::Op52, &eZ80t::Op53, &eZ80t::Op54, &eZ80t::Op55, + &eZ80t::Op56, &eZ80t::Op57, + &eZ80t::Op58, &eZ80t::Op59, &eZ80t::Op5A, &eZ80t::Op5B, &eZ80t::Op5C, &eZ80t::Op5D, + &eZ80t::Op5E, &eZ80t::Op5F, + &eZ80t::Op60, &eZ80t::Op61, &eZ80t::Op62, &eZ80t::Op63, &eZ80t::Op64, &eZ80t::Op65, + &eZ80t::Op66, &eZ80t::Op67, + &eZ80t::Op68, &eZ80t::Op69, &eZ80t::Op6A, &eZ80t::Op6B, &eZ80t::Op6C, &eZ80t::Op6D, + &eZ80t::Op6E, &eZ80t::Op6F, + &eZ80t::Op70, &eZ80t::Op71, &eZ80t::Op72, &eZ80t::Op73, &eZ80t::Op74, &eZ80t::Op75, + &eZ80t::Op76, &eZ80t::Op77, + &eZ80t::Op78, &eZ80t::Op79, &eZ80t::Op7A, &eZ80t::Op7B, &eZ80t::Op7C, &eZ80t::Op7D, + &eZ80t::Op7E, &eZ80t::Op7F, + + &eZ80t::Op80, &eZ80t::Op81, &eZ80t::Op82, &eZ80t::Op83, &eZ80t::Op84, &eZ80t::Op85, + &eZ80t::Op86, &eZ80t::Op87, + &eZ80t::Op88, &eZ80t::Op89, &eZ80t::Op8A, &eZ80t::Op8B, &eZ80t::Op8C, &eZ80t::Op8D, + &eZ80t::Op8E, &eZ80t::Op8F, + &eZ80t::Op90, &eZ80t::Op91, &eZ80t::Op92, &eZ80t::Op93, &eZ80t::Op94, &eZ80t::Op95, + &eZ80t::Op96, &eZ80t::Op97, + &eZ80t::Op98, &eZ80t::Op99, &eZ80t::Op9A, &eZ80t::Op9B, &eZ80t::Op9C, &eZ80t::Op9D, + &eZ80t::Op9E, &eZ80t::Op9F, + &eZ80t::OpA0, &eZ80t::OpA1, &eZ80t::OpA2, &eZ80t::OpA3, &eZ80t::OpA4, &eZ80t::OpA5, + &eZ80t::OpA6, &eZ80t::OpA7, + &eZ80t::OpA8, &eZ80t::OpA9, &eZ80t::OpAA, &eZ80t::OpAB, &eZ80t::OpAC, &eZ80t::OpAD, + &eZ80t::OpAE, &eZ80t::OpAF, + &eZ80t::OpB0, &eZ80t::OpB1, &eZ80t::OpB2, &eZ80t::OpB3, &eZ80t::OpB4, &eZ80t::OpB5, + &eZ80t::OpB6, &eZ80t::OpB7, + &eZ80t::OpB8, &eZ80t::OpB9, &eZ80t::OpBA, &eZ80t::OpBB, &eZ80t::OpBC, &eZ80t::OpBD, + &eZ80t::OpBE, &eZ80t::OpBF, + + &eZ80t::OpC0, &eZ80t::OpC1, &eZ80t::OpC2, &eZ80t::OpC3, &eZ80t::OpC4, &eZ80t::OpC5, + &eZ80t::OpC6, &eZ80t::OpC7, + &eZ80t::OpC8, &eZ80t::OpC9, &eZ80t::OpCA, &eZ80t::OpCB, &eZ80t::OpCC, &eZ80t::OpCD, + &eZ80t::OpCE, &eZ80t::OpCF, + &eZ80t::OpD0, &eZ80t::OpD1, &eZ80t::OpD2, &eZ80t::OpD3, &eZ80t::OpD4, &eZ80t::OpD5, + &eZ80t::OpD6, &eZ80t::OpD7, + &eZ80t::OpD8, &eZ80t::OpD9, &eZ80t::OpDA, &eZ80t::OpDB, &eZ80t::OpDC, &eZ80t::OpDD, + &eZ80t::OpDE, &eZ80t::OpDF, + &eZ80t::OpE0, &eZ80t::OpE1, &eZ80t::OpE2, &eZ80t::OpE3, &eZ80t::OpE4, &eZ80t::OpE5, + &eZ80t::OpE6, &eZ80t::OpE7, + &eZ80t::OpE8, &eZ80t::OpE9, &eZ80t::OpEA, &eZ80t::OpEB, &eZ80t::OpEC, &eZ80t::OpED, + &eZ80t::OpEE, &eZ80t::OpEF, + &eZ80t::OpF0, &eZ80t::OpF1, &eZ80t::OpF2, &eZ80t::OpF3, &eZ80t::OpF4, &eZ80t::OpF5, + &eZ80t::OpF6, &eZ80t::OpF7, + &eZ80t::OpF8, &eZ80t::OpF9, &eZ80t::OpFA, &eZ80t::OpFB, &eZ80t::OpFC, &eZ80t::OpFD, + &eZ80t::OpFE, &eZ80t::OpFF + }; + dump_instructions("op", opcodes, count_of(opcodes), "top level opcodes"); + + const CALLFUNC oplscodes[] = { + &eZ80t::Opl_rlc, + &eZ80t::Opl_rrc, + &eZ80t::Opl_rl, + &eZ80t::Opl_rr, + &eZ80t::Opl_sla, + &eZ80t::Opl_sra, + &eZ80t::Opl_sli, + &eZ80t::Opl_srl, + &eZ80t::Opl_bit0, + &eZ80t::Opl_bit1, + &eZ80t::Opl_bit2, + &eZ80t::Opl_bit3, + &eZ80t::Opl_bit4, + &eZ80t::Opl_bit5, + &eZ80t::Opl_bit6, + &eZ80t::Opl_bit7, + &eZ80t::Opl_res0, + &eZ80t::Opl_res1, + &eZ80t::Opl_res2, + &eZ80t::Opl_res3, + &eZ80t::Opl_res4, + &eZ80t::Opl_res5, + &eZ80t::Opl_res6, + &eZ80t::Opl_res7, + &eZ80t::Opl_set0, + &eZ80t::Opl_set1, + &eZ80t::Opl_set2, + &eZ80t::Opl_set3, + &eZ80t::Opl_set4, + &eZ80t::Opl_set5, + &eZ80t::Opl_set6, + &eZ80t::Opl_set7, + }; + + dump_instructions("opli_r35_", oplscodes, count_of(oplscodes), "OPL inner logic functions which do core work on r_temp", 8); + + const CALLFUNC oplsmcodes[] = { + &eZ80t::Opl_rlc, + &eZ80t::Opl_rrc, + &eZ80t::Opl_rl, + &eZ80t::Opl_rr, + &eZ80t::Opl_sla, + &eZ80t::Opl_sra, + &eZ80t::Opl_sli, + &eZ80t::Opl_srl, + &eZ80t::Opl_bit0m, + &eZ80t::Opl_bit1m, + &eZ80t::Opl_bit2m, + &eZ80t::Opl_bit3m, + &eZ80t::Opl_bit4m, + &eZ80t::Opl_bit5m, + &eZ80t::Opl_bit6m, + &eZ80t::Opl_bit7m, + &eZ80t::Opl_res0, + &eZ80t::Opl_res1, + &eZ80t::Opl_res2, + &eZ80t::Opl_res3, + &eZ80t::Opl_res4, + &eZ80t::Opl_res5, + &eZ80t::Opl_res6, + &eZ80t::Opl_res7, + &eZ80t::Opl_set0, + &eZ80t::Opl_set1, + &eZ80t::Opl_set2, + &eZ80t::Opl_set3, + &eZ80t::Opl_set4, + &eZ80t::Opl_set5, + &eZ80t::Opl_set6, + &eZ80t::Opl_set7, + }; + dump_instructions("opli_m35_", oplsmcodes, count_of(oplsmcodes), "OPL inner logic functions which do core work on r_temp, but set F3&F5 based on mem_h", 8); + +#ifdef USE_LARGER_FASTER_CB + const CALLFUNC oplcodes[] = + { + &eZ80t::Opl00, &eZ80t::Opl01, &eZ80t::Opl02, &eZ80t::Opl03, &eZ80t::Opl04, &eZ80t::Opl05, &eZ80t::Opl06, &eZ80t::Opl07, + &eZ80t::Opl08, &eZ80t::Opl09, &eZ80t::Opl0A, &eZ80t::Opl0B, &eZ80t::Opl0C, &eZ80t::Opl0D, &eZ80t::Opl0E, &eZ80t::Opl0F, + &eZ80t::Opl10, &eZ80t::Opl11, &eZ80t::Opl12, &eZ80t::Opl13, &eZ80t::Opl14, &eZ80t::Opl15, &eZ80t::Opl16, &eZ80t::Opl17, + &eZ80t::Opl18, &eZ80t::Opl19, &eZ80t::Opl1A, &eZ80t::Opl1B, &eZ80t::Opl1C, &eZ80t::Opl1D, &eZ80t::Opl1E, &eZ80t::Opl1F, + &eZ80t::Opl20, &eZ80t::Opl21, &eZ80t::Opl22, &eZ80t::Opl23, &eZ80t::Opl24, &eZ80t::Opl25, &eZ80t::Opl26, &eZ80t::Opl27, + &eZ80t::Opl28, &eZ80t::Opl29, &eZ80t::Opl2A, &eZ80t::Opl2B, &eZ80t::Opl2C, &eZ80t::Opl2D, &eZ80t::Opl2E, &eZ80t::Opl2F, + &eZ80t::Opl30, &eZ80t::Opl31, &eZ80t::Opl32, &eZ80t::Opl33, &eZ80t::Opl34, &eZ80t::Opl35, &eZ80t::Opl36, &eZ80t::Opl37, + &eZ80t::Opl38, &eZ80t::Opl39, &eZ80t::Opl3A, &eZ80t::Opl3B, &eZ80t::Opl3C, &eZ80t::Opl3D, &eZ80t::Opl3E, &eZ80t::Opl3F, + + &eZ80t::Opl40, &eZ80t::Opl41, &eZ80t::Opl42, &eZ80t::Opl43, &eZ80t::Opl44, &eZ80t::Opl45, &eZ80t::Opl46, &eZ80t::Opl47, + &eZ80t::Opl48, &eZ80t::Opl49, &eZ80t::Opl4A, &eZ80t::Opl4B, &eZ80t::Opl4C, &eZ80t::Opl4D, &eZ80t::Opl4E, &eZ80t::Opl4F, + &eZ80t::Opl50, &eZ80t::Opl51, &eZ80t::Opl52, &eZ80t::Opl53, &eZ80t::Opl54, &eZ80t::Opl55, &eZ80t::Opl56, &eZ80t::Opl57, + &eZ80t::Opl58, &eZ80t::Opl59, &eZ80t::Opl5A, &eZ80t::Opl5B, &eZ80t::Opl5C, &eZ80t::Opl5D, &eZ80t::Opl5E, &eZ80t::Opl5F, + &eZ80t::Opl60, &eZ80t::Opl61, &eZ80t::Opl62, &eZ80t::Opl63, &eZ80t::Opl64, &eZ80t::Opl65, &eZ80t::Opl66, &eZ80t::Opl67, + &eZ80t::Opl68, &eZ80t::Opl69, &eZ80t::Opl6A, &eZ80t::Opl6B, &eZ80t::Opl6C, &eZ80t::Opl6D, &eZ80t::Opl6E, &eZ80t::Opl6F, + &eZ80t::Opl70, &eZ80t::Opl71, &eZ80t::Opl72, &eZ80t::Opl73, &eZ80t::Opl74, &eZ80t::Opl75, &eZ80t::Opl76, &eZ80t::Opl77, + &eZ80t::Opl78, &eZ80t::Opl79, &eZ80t::Opl7A, &eZ80t::Opl7B, &eZ80t::Opl7C, &eZ80t::Opl7D, &eZ80t::Opl7E, &eZ80t::Opl7F, + + &eZ80t::Opl80, &eZ80t::Opl81, &eZ80t::Opl82, &eZ80t::Opl83, &eZ80t::Opl84, &eZ80t::Opl85, &eZ80t::Opl86, &eZ80t::Opl87, + &eZ80t::Opl88, &eZ80t::Opl89, &eZ80t::Opl8A, &eZ80t::Opl8B, &eZ80t::Opl8C, &eZ80t::Opl8D, &eZ80t::Opl8E, &eZ80t::Opl8F, + &eZ80t::Opl90, &eZ80t::Opl91, &eZ80t::Opl92, &eZ80t::Opl93, &eZ80t::Opl94, &eZ80t::Opl95, &eZ80t::Opl96, &eZ80t::Opl97, + &eZ80t::Opl98, &eZ80t::Opl99, &eZ80t::Opl9A, &eZ80t::Opl9B, &eZ80t::Opl9C, &eZ80t::Opl9D, &eZ80t::Opl9E, &eZ80t::Opl9F, + &eZ80t::OplA0, &eZ80t::OplA1, &eZ80t::OplA2, &eZ80t::OplA3, &eZ80t::OplA4, &eZ80t::OplA5, &eZ80t::OplA6, &eZ80t::OplA7, + &eZ80t::OplA8, &eZ80t::OplA9, &eZ80t::OplAA, &eZ80t::OplAB, &eZ80t::OplAC, &eZ80t::OplAD, &eZ80t::OplAE, &eZ80t::OplAF, + &eZ80t::OplB0, &eZ80t::OplB1, &eZ80t::OplB2, &eZ80t::OplB3, &eZ80t::OplB4, &eZ80t::OplB5, &eZ80t::OplB6, &eZ80t::OplB7, + &eZ80t::OplB8, &eZ80t::OplB9, &eZ80t::OplBA, &eZ80t::OplBB, &eZ80t::OplBC, &eZ80t::OplBD, &eZ80t::OplBE, &eZ80t::OplBF, + + &eZ80t::OplC0, &eZ80t::OplC1, &eZ80t::OplC2, &eZ80t::OplC3, &eZ80t::OplC4, &eZ80t::OplC5, &eZ80t::OplC6, &eZ80t::OplC7, + &eZ80t::OplC8, &eZ80t::OplC9, &eZ80t::OplCA, &eZ80t::OplCB, &eZ80t::OplCC, &eZ80t::OplCD, &eZ80t::OplCE, &eZ80t::OplCF, + &eZ80t::OplD0, &eZ80t::OplD1, &eZ80t::OplD2, &eZ80t::OplD3, &eZ80t::OplD4, &eZ80t::OplD5, &eZ80t::OplD6, &eZ80t::OplD7, + &eZ80t::OplD8, &eZ80t::OplD9, &eZ80t::OplDA, &eZ80t::OplDB, &eZ80t::OplDC, &eZ80t::OplDD, &eZ80t::OplDE, &eZ80t::OplDF, + &eZ80t::OplE0, &eZ80t::OplE1, &eZ80t::OplE2, &eZ80t::OplE3, &eZ80t::OplE4, &eZ80t::OplE5, &eZ80t::OplE6, &eZ80t::OplE7, + &eZ80t::OplE8, &eZ80t::OplE9, &eZ80t::OplEA, &eZ80t::OplEB, &eZ80t::OplEC, &eZ80t::OplED, &eZ80t::OplEE, &eZ80t::OplEF, + &eZ80t::OplF0, &eZ80t::OplF1, &eZ80t::OplF2, &eZ80t::OplF3, &eZ80t::OplF4, &eZ80t::OplF5, &eZ80t::OplF6, &eZ80t::OplF7, + &eZ80t::OplF8, &eZ80t::OplF9, &eZ80t::OplFA, &eZ80t::OplFB, &eZ80t::OplFC, &eZ80t::OplFD, &eZ80t::OplFE, &eZ80t::OplFF + }; + + dump_instructions("opl", oplcodes, count_of(oplcodes), "cb logic opcodes"); +#endif + + CALLFUNC const opddcodes[] = + { + &eZ80t::Op00, &eZ80t::Op01, &eZ80t::Op02, &eZ80t::Op03, &eZ80t::Op04, &eZ80t::Op05, + &eZ80t::Op06, &eZ80t::Op07, + &eZ80t::Op08, &eZ80t::Opx09, &eZ80t::Op0A, &eZ80t::Op0B, &eZ80t::Op0C, &eZ80t::Op0D, + &eZ80t::Op0E, &eZ80t::Op0F, + &eZ80t::Op10, &eZ80t::Op11, &eZ80t::Op12, &eZ80t::Op13, &eZ80t::Op14, &eZ80t::Op15, + &eZ80t::Op16, &eZ80t::Op17, + &eZ80t::Op18, &eZ80t::Opx19, &eZ80t::Op1A, &eZ80t::Op1B, &eZ80t::Op1C, &eZ80t::Op1D, + &eZ80t::Op1E, &eZ80t::Op1F, + &eZ80t::Op20, &eZ80t::Opx21, &eZ80t::Opx22, &eZ80t::Opx23, &eZ80t::Opx24, &eZ80t::Opx25, + &eZ80t::Opx26, &eZ80t::Op27, + &eZ80t::Op28, &eZ80t::Opx29, &eZ80t::Opx2A, &eZ80t::Opx2B, &eZ80t::Opx2C, &eZ80t::Opx2D, + &eZ80t::Opx2E, &eZ80t::Op2F, + &eZ80t::Op30, &eZ80t::Op31, &eZ80t::Op32, &eZ80t::Op33, &eZ80t::Opx34, &eZ80t::Opx35, + &eZ80t::Opx36, &eZ80t::Op37, + &eZ80t::Op38, &eZ80t::Opx39, &eZ80t::Op3A, &eZ80t::Op3B, &eZ80t::Op3C, &eZ80t::Op3D, + &eZ80t::Op3E, &eZ80t::Op3F, + + &eZ80t::Op40, &eZ80t::Op41, &eZ80t::Op42, &eZ80t::Op43, &eZ80t::Opx44, &eZ80t::Opx45, + &eZ80t::Opx46, &eZ80t::Op47, + &eZ80t::Op48, &eZ80t::Op49, &eZ80t::Op4A, &eZ80t::Op4B, &eZ80t::Opx4C, &eZ80t::Opx4D, + &eZ80t::Opx4E, &eZ80t::Op4F, + &eZ80t::Op50, &eZ80t::Op51, &eZ80t::Op52, &eZ80t::Op53, &eZ80t::Opx54, &eZ80t::Opx55, + &eZ80t::Opx56, &eZ80t::Op57, + &eZ80t::Op58, &eZ80t::Op59, &eZ80t::Op5A, &eZ80t::Op5B, &eZ80t::Opx5C, &eZ80t::Opx5D, + &eZ80t::Opx5E, &eZ80t::Op5F, + &eZ80t::Opx60, &eZ80t::Opx61, &eZ80t::Opx62, &eZ80t::Opx63, &eZ80t::Op64, &eZ80t::Opx65, + &eZ80t::Opx66, &eZ80t::Opx67, + &eZ80t::Opx68, &eZ80t::Opx69, &eZ80t::Opx6A, &eZ80t::Opx6B, &eZ80t::Opx6C, &eZ80t::Op6D, + &eZ80t::Opx6E, &eZ80t::Opx6F, + &eZ80t::Opx70, &eZ80t::Opx71, &eZ80t::Opx72, &eZ80t::Opx73, &eZ80t::Opx74, &eZ80t::Opx75, + &eZ80t::Op76, &eZ80t::Opx77, + &eZ80t::Op78, &eZ80t::Op79, &eZ80t::Op7A, &eZ80t::Op7B, &eZ80t::Opx7C, &eZ80t::Opx7D, + &eZ80t::Opx7E, &eZ80t::Op7F, + + &eZ80t::Op80, &eZ80t::Op81, &eZ80t::Op82, &eZ80t::Op83, &eZ80t::Opx84, &eZ80t::Opx85, + &eZ80t::Opx86, &eZ80t::Op87, + &eZ80t::Op88, &eZ80t::Op89, &eZ80t::Op8A, &eZ80t::Op8B, &eZ80t::Opx8C, &eZ80t::Opx8D, + &eZ80t::Opx8E, &eZ80t::Op8F, + &eZ80t::Op90, &eZ80t::Op91, &eZ80t::Op92, &eZ80t::Op93, &eZ80t::Opx94, &eZ80t::Opx95, + &eZ80t::Opx96, &eZ80t::Op97, + &eZ80t::Op98, &eZ80t::Op99, &eZ80t::Op9A, &eZ80t::Op9B, &eZ80t::Opx9C, &eZ80t::Opx9D, + &eZ80t::Opx9E, &eZ80t::Op9F, + &eZ80t::OpA0, &eZ80t::OpA1, &eZ80t::OpA2, &eZ80t::OpA3, &eZ80t::OpxA4, &eZ80t::OpxA5, + &eZ80t::OpxA6, &eZ80t::OpA7, + &eZ80t::OpA8, &eZ80t::OpA9, &eZ80t::OpAA, &eZ80t::OpAB, &eZ80t::OpxAC, &eZ80t::OpxAD, + &eZ80t::OpxAE, &eZ80t::OpAF, + &eZ80t::OpB0, &eZ80t::OpB1, &eZ80t::OpB2, &eZ80t::OpB3, &eZ80t::OpxB4, &eZ80t::OpxB5, + &eZ80t::OpxB6, &eZ80t::OpB7, + &eZ80t::OpB8, &eZ80t::OpB9, &eZ80t::OpBA, &eZ80t::OpBB, &eZ80t::OpxBC, &eZ80t::OpxBD, + &eZ80t::OpxBE, &eZ80t::OpBF, + + &eZ80t::OpC0, &eZ80t::OpC1, &eZ80t::OpC2, &eZ80t::OpC3, &eZ80t::OpC4, &eZ80t::OpC5, + &eZ80t::OpC6, &eZ80t::OpC7, + &eZ80t::OpC8, &eZ80t::OpC9, &eZ80t::OpCA, &eZ80t::OpCB, &eZ80t::OpCC, &eZ80t::OpCD, + &eZ80t::OpCE, &eZ80t::OpCF, + &eZ80t::OpD0, &eZ80t::OpD1, &eZ80t::OpD2, &eZ80t::OpD3, &eZ80t::OpD4, &eZ80t::OpD5, + &eZ80t::OpD6, &eZ80t::OpD7, + &eZ80t::OpD8, &eZ80t::OpD9, &eZ80t::OpDA, &eZ80t::OpDB, &eZ80t::OpDC, &eZ80t::OpDD, + &eZ80t::OpDE, &eZ80t::OpDF, + &eZ80t::OpE0, &eZ80t::OpxE1, &eZ80t::OpE2, &eZ80t::OpxE3, &eZ80t::OpE4, &eZ80t::OpxE5, + &eZ80t::OpE6, &eZ80t::OpE7, + &eZ80t::OpE8, &eZ80t::OpxE9, &eZ80t::OpEA, &eZ80t::OpEB, &eZ80t::OpEC, &eZ80t::OpED, + &eZ80t::OpEE, &eZ80t::OpEF, + &eZ80t::OpF0, &eZ80t::OpF1, &eZ80t::OpF2, &eZ80t::OpF3, &eZ80t::OpF4, &eZ80t::OpF5, + &eZ80t::OpF6, &eZ80t::OpF7, + &eZ80t::OpF8, &eZ80t::OpxF9, &eZ80t::OpFA, &eZ80t::OpFB, &eZ80t::OpFC, &eZ80t::OpFD, + &eZ80t::OpFE, &eZ80t::OpFF + }; + + dump_instructions("opxy", opddcodes, count_of(opddcodes), "dd/fd prefix xy opcodes r_temp holds ix or iy"); + + CALLFUNC const opedcodes[] = + { + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + + &eZ80t::Ope40, &eZ80t::Ope41, &eZ80t::Ope42, &eZ80t::Ope43, &eZ80t::Ope44, &eZ80t::Ope45, + &eZ80t::Ope46, &eZ80t::Ope47, + &eZ80t::Ope48, &eZ80t::Ope49, &eZ80t::Ope4A, &eZ80t::Ope4B, &eZ80t::Ope4C, &eZ80t::Ope4D, + &eZ80t::Ope4E, &eZ80t::Ope4F, + &eZ80t::Ope50, &eZ80t::Ope51, &eZ80t::Ope52, &eZ80t::Ope53, &eZ80t::Ope54, &eZ80t::Ope55, + &eZ80t::Ope56, &eZ80t::Ope57, + &eZ80t::Ope58, &eZ80t::Ope59, &eZ80t::Ope5A, &eZ80t::Ope5B, &eZ80t::Ope5C, &eZ80t::Ope5D, + &eZ80t::Ope5E, &eZ80t::Ope5F, + &eZ80t::Ope60, &eZ80t::Ope61, &eZ80t::Ope62, &eZ80t::Ope63, &eZ80t::Ope64, &eZ80t::Ope65, + &eZ80t::Ope66, &eZ80t::Ope67, + &eZ80t::Ope68, &eZ80t::Ope69, &eZ80t::Ope6A, &eZ80t::Ope6B, &eZ80t::Ope6C, &eZ80t::Ope6D, + &eZ80t::Ope6E, &eZ80t::Ope6F, + &eZ80t::Ope70, &eZ80t::Ope71, &eZ80t::Ope72, &eZ80t::Ope73, &eZ80t::Ope74, &eZ80t::Ope75, + &eZ80t::Ope76, &eZ80t::Ope77, + &eZ80t::Ope78, &eZ80t::Ope79, &eZ80t::Ope7A, &eZ80t::Ope7B, &eZ80t::Ope7C, &eZ80t::Ope7D, + &eZ80t::Ope7E, &eZ80t::Ope7F, + + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::OpeA0, &eZ80t::OpeA1, &eZ80t::OpeA2, &eZ80t::OpeA3, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::OpeA8, &eZ80t::OpeA9, &eZ80t::OpeAA, &eZ80t::OpeAB, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::OpeB0, &eZ80t::OpeB1, &eZ80t::OpeB2, &eZ80t::OpeB3, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::OpeB8, &eZ80t::OpeB9, &eZ80t::OpeBA, &eZ80t::OpeBB, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, &eZ80t::Op00, + &eZ80t::Op00, &eZ80t::Op00 + }; + + + dump_instructions("ope", opedcodes, count_of(opedcodes), "ed prefix"); + } + + void eZ80t::dump_instructions(const char *prefix, const CALLFUNC *instrs, int count, const char *description, int mult) { + char buf[256]; + if (description) { + snprintf(buf, sizeof(buf), "// === BEGIN %s", description); + emit(buf); + } + int seen[count]; + for(int i=0;i= 0) + { + snprintf(buf, sizeof(buf), ".short %s%02x + 1 - %s", seen_prefix[seen[i]], seen_num[seen[i]], table_name); + } else { + snprintf(buf, sizeof(buf), ".short %s%02x + 1 - %s", prefix, i * mult, table_name); + } + emit(buf); + } + emit_helper_functions(prefix, HELPER_START); + for(int i=0;i*instrs[i])(); + emit_function_return(); + if (!strcmp(prefix, "opxy")) { + // can't use r_temp and r_ixy since they are the same reg + assert(!r_temp_used); + } + } + if (i && !(i&31)) { + // code a bit too big to not have frequent references + emit(".ltorg"); + } + if (i == 0xb0 && !strcmp(prefix, "ope")) { + // extra one + emit(".ltorg"); + } + } + emit_helper_functions(prefix, HELPER_END); + emit(".ltorg"); + if (description) { + snprintf(buf, sizeof(buf), "// === END %s", description); + emit(buf); + } + printf("\n"); + } + +}//namespace xZ80 diff --git a/khan/z80t.h b/khan/z80t.h new file mode 100644 index 0000000..d36d7ec --- /dev/null +++ b/khan/z80t.h @@ -0,0 +1,2329 @@ +/* +Portable ZX-Spectrum emulator. +Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef __Z80X_H__ +#define __Z80X_H__ + +#include "pico.h" +#include "../std_types.h" +#include +#include +#include "z80khan.h" // needed for struct layout +#include + +#ifdef DECLARE_REG16 +#error z80 already included in same compilation unit +#endif + +#pragma once + +class eMemory; +class eRom; +class eUla; +class eDevices; + +extern bool always_true; +#define USE_Z80T +namespace Z80t +{ + +#define DECLARE_REG16(REGNAME, reg, low, high)\ + static struct Reg16 reg; \ + static struct RegLo low; \ + static struct RegHi high; + + enum eFlags + { + CF = 0x01, + NF = 0x02, + PV = 0x04, + F3 = 0x08, + HF = 0x10, + F5 = 0x20, + ZF = 0x40, + SF = 0x80 + }; + + enum Reg16Name { + BC, + DE, + HL, + AF, + IX, + IY, + MEMPTR, + PC, + SP, + TEMPORARY, + TEMPORARY_RESTRICTED, + REG_COUNT + }; + +#define R_TEMP_ARM 3 + +#define unimpl_assert(x) emit("bkpt", "#0", "// not implemented") + +//***************************************************************************** +// eZ80 +//----------------------------------------------------------------------------- +class eZ80t +{ +public: + + // is the register stored in an ARM low register (i.e. we can do direct arithmetic on it) + static bool is_arm_lo_reg(const enum Reg16Name x) { + return x == TEMPORARY || x == PC || x == AF || x == TEMPORARY_RESTRICTED; + } + + template struct RegLo; + template struct RegHi; + + // reference to an 8 bit or 16 bit reg + struct RegRef { + RegRef(const enum Reg16Name& reg) : reg(reg) { + if (reg == TEMPORARY) { + r_temp_used = true; + } + if (reg == TEMPORARY_RESTRICTED) { + r_temp_restricted_used = true; + } + } + enum Reg16Name reg; + }; + + struct Reg8Ref : public RegRef { + Reg8Ref(const enum Reg16Name& reg, bool lo) : RegRef(reg), lo(lo) {} + bool lo; + }; + + struct Reg16Ref : public RegRef + { + Reg16Ref(const enum Reg16Name ®) : RegRef(reg) {} + }; + + struct WordInR0HighWordUnknown; + struct WordInR1; + struct ZeroExtendedByteInR0; + + // this is a value in r0 that is explicitly constructed from either a value already in r0 or a Reg16Ref + struct WordInR0 { + WordInR0() { + // already in r0 + } + WordInR0(const WordInR0HighWordUnknown& x) { + emit("uxth", "r0, r0"); + } + WordInR0(const Reg16Ref& ref) { + char buf[128]; + // assign to r0 + snprintf(buf, sizeof(buf), "r0, %s", arm_regs[ref.reg]); + emit("mov ", buf); + } + WordInR0 operator|(const WordInR1& v) { + emit("orrs ", "r0, r1"); + return {}; + } + WordInR0HighWordUnknown operator+(int x) + { + char buf[32]; + snprintf(buf, sizeof(buf), "r0, #%d", x); + emit("adds ", buf); + return {}; + } + }; + + // this is a value in r1 that is explicitly constructed from either a value already in r1 or a Reg16Ref + struct WordInR1 { + WordInR1() { + // already in r1 + } + WordInR1(const Reg16Ref& ref) { + emit_r1_from_reg(ref); + } +// WordInR0 operator|(const ZeroExtendedByteInR0& v) { +// emit("orrs ", "r0, r1"); +// return {}; +// } + }; + + // this is a value in r0 that is explicitly constructed form a value in r0 which may need zeroing + struct WordInR0HighWordUnknown { + WordInR0 operator&(int v) { + assert(v == 255); + emit("uxtb", "r0, r0"); + return {}; + } + }; + + // this is a value in r0 that is explicitly constructed form either a value in r0, a Reg8Ref or a constant + struct ZeroExtendedByteInR0 { + ZeroExtendedByteInR0() { + // value already in r0 + } + ZeroExtendedByteInR0(int val) { + char buf[32]; + snprintf(buf, sizeof(buf), "r0, #%d", val); + emit("movs ", buf); + } + ZeroExtendedByteInR0(const Reg8Ref& ref) { + char buf[128]; + // assign to r0 + if (ref.lo) { + emit_zero_extend_z80_lo_to_arm(ref, 0); + } else { + if (is_arm_lo_reg(ref.reg)) { + snprintf(buf, sizeof(buf), "r0, %s, #8", arm_regs[ref.reg]); + emit("lsrs ", buf); + } else { + snprintf(buf, sizeof(buf), "r0, %s", arm_regs[ref.reg]); + emit("mov ", buf); + emit("lsrs ", "r0, #8"); + } + } + } + + operator WordInR0() { + return {}; + } + }; + + // similar to ZeroExtendedByteInR0 except the hi byte may not be zero. + struct LoByteAndGarbageInR0 { + // value already in r0 + LoByteAndGarbageInR0() {} + + LoByteAndGarbageInR0(const ZeroExtendedByteInR0& r) {} + + LoByteAndGarbageInR0(int val) { + char buf[32]; + snprintf(buf, sizeof(buf), "r0, #%d", val); + emit("movs ", buf); + } + LoByteAndGarbageInR0(const Reg8Ref& ref) { + char buf[128]; + // assign to r0 + if (ref.lo) { + snprintf(buf, sizeof(buf), "r0, %s", arm_regs[ref.reg]); + emit("mov ", buf, "// high half of word is ignored later"); + } else { + snprintf(buf, sizeof(buf), "r0, %s", arm_regs[ref.reg]); + emit("mov ", buf); + emit("lsrs ", "r0, #8"); + } + } + }; + + // note high word may not be corrsect, but we don't care + struct SignExtendedByteInR0HighWordUnknown { + SignExtendedByteInR0HighWordUnknown(ZeroExtendedByteInR0 v) { + emit("sxtb", "r0, r0"); + } + + WordInR0HighWordUnknown operator+(int val) { + assert(val == 1); + emit("adds ", "r0, #1"); + return {}; + } + + operator WordInR0HighWordUnknown() { + return {}; + } + }; + + // type of t variable + struct TState { + TState() {} + TState operator+=(int val) { + char buf[128]; + snprintf(buf, sizeof(buf), "r_t, #%d", val); + emit("adds ", buf); + return *this; + } + TState operator++(int) { + TState tmp = *this; + emit("adds ", "r_t, #1"); + return tmp; + } + }; + + // variable that doesn't have a dedicated register, so is load and stored to/from the resting state + struct RestingStateVariable { + private: + public: + RestingStateVariable(const char *name, int size, int offset) : name(name), offset(offset), size(size) {} + RestingStateVariable(const RestingStateVariable& v) = delete; + WordInR0 operator=(const RestingStateVariable&v) { + WordInR0 tmp = v; + return *this = tmp; + } + + void emit_read(int target, int state_ref) const { + char buf[32]; + snprintf(buf, sizeof(buf), "r%d, [r%d, #%d] // %s", target, state_ref, offset, name); + switch (size) { + case 1: + emit("ldrb", buf); + break; + case 2: + emit("ldrh", buf); + break; + case 4: + emit("ldr ", buf); + break; + default: + assert(false); + } + } + + void emit_read_to_r1() const { + emit("ldr ", "r1, =z80a_resting_state"); + emit_read(1, 1); + } + + void emit_read_to_r0() const { + emit("ldr ", "r0, =z80a_resting_state"); + emit_read(0, 0); + } + + void emit_write(int source, int state_ref) { + char buf[32]; + snprintf(buf, sizeof(buf), "r%d, [r%d, #%d] // %s", source, state_ref, offset, name); + switch (size) { + case 1: + emit("strb", buf); + break; + case 2: + emit("strh", buf); + break; + case 4: + emit("str ", buf); + break; + default: + assert(false); + } + } + + void emit_write_from_r0() { + emit("ldr ", "r1, =z80a_resting_state"); + emit_write(0, 1); + } + + WordInR0 operator=(int val) { + char buf[32]; + snprintf(buf, sizeof(buf), "r0, #%d", val); + emit("movs ", buf); + emit_write_from_r0(); + return {}; + } + + WordInR0 operator=(WordInR0 val) { + emit_write_from_r0(); + return {}; + } + + WordInR0 operator=(ZeroExtendedByteInR0 val) { + emit_write_from_r0(); + return {}; + } + + RestingStateVariable& operator=(const TState& t) { + emit("mov ", "r0, r_t"); + emit_write_from_r0(); + return *this; + } + + operator WordInR0() const { + emit_read_to_r0(); + return {}; + } + + const char *name; + int offset; + int size; + }; + + template struct Reg16 { + Reg16() : reg(R), thisref(R) { } + + // todo do we need this? or should we use Constorrseg16 + Reg16(const int& v) : reg(R), thisref(R) { + assert(R == TEMPORARY || R == TEMPORARY_RESTRICTED); + set(v); + } + Reg16(const WordInR0& v) : reg(R), thisref(R) { + emit_reg_from_r0(R); + } + Reg16(const ZeroExtendedByteInR0& v) : reg(R), thisref(R) { + assert(R == TEMPORARY || R == TEMPORARY_RESTRICTED); + char buf[32]; + snprintf(buf, sizeof(buf), "%s, r0", arm_regs[reg]); + emit("mov ", buf); + } + Reg16(const WordInR0HighWordUnknown& v) : reg(R), thisref(R) { + char buf[32]; + emit("uxth", "r0, r0"); + snprintf(buf, sizeof(buf), "%s, r0", arm_regs[reg]); + emit("mov ", buf); + } + + Reg16 operator|=(const WordInR0& v) { + assert(is_arm_lo_reg(R)); // no particular reason, just not used + char buf[32]; + snprintf(buf, sizeof(buf), "%s, r0", arm_regs[reg]); + emit("orrs ", buf); + return *this; + } + + Reg16 operator|=(const WordInR1& v) { + assert(is_arm_lo_reg(R)); // no particular reason, just not used + char buf[32]; + snprintf(buf, sizeof(buf), "%s, r1", arm_regs[reg]); + emit("orrs ", buf); + return *this; + } + + Reg16 operator ++() { + add(1); + return *this; + } + // return void since we don't return the old value (still allows a standalone foo++) + void operator ++(int) { + add(1); + } + Reg16 operator --() { + sub(1); + return *this; + } + void operator --(int) { + sub(1); + } + void add(int x) { + assert(x > 0 && x < 256); + char buf[128]; + if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "%s, #%d", arm_regs[reg], x); + emit("adds ", buf); + snprintf(buf, sizeof(buf), "%s, %s", arm_regs[reg], arm_regs[reg]); + emit("uxth", buf); + } else { + emit_r0_from_reg(thisref); + snprintf(buf, sizeof(buf), "r0, #%d", x); + emit("adds ", buf); + emit("uxth", "r0, r0"); + emit_reg_from_r0(thisref); + } + } + + void sub(int x) { + assert(x>=0 && x < 256); + char buf[128]; + if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "%s, #%d", arm_regs[reg], x); + emit("subs ", buf); + snprintf(buf, sizeof(buf), "%s, %s", arm_regs[reg], arm_regs[reg]); + emit("uxth", buf); + } else { + emit_r0_from_reg(thisref); + snprintf(buf, sizeof(buf), "r0, #%d", x); + emit("subs ", buf); + emit("uxth", "r0, r0"); + emit_reg_from_r0(thisref); + } + } + operator Reg16Ref() const { + return thisref; + } + void operator+=(int x) { + add(x); + } + void operator-=(int x) { + sub(x); + } + Reg16 operator+=(WordInR0HighWordUnknown x) { + char buf[128]; + if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "%s, r0", arm_regs[reg]); + emit("add ", buf); + snprintf(buf, sizeof(buf), "%s, %s", arm_regs[reg], arm_regs[reg]); + emit("uxth", buf); + } else { + emit_r1_from_reg(thisref); + snprintf(buf, sizeof(buf), "r0, r1"); + emit("add ", buf); + emit("uxth", "r0, r0"); + emit_reg_from_r0(thisref); + } + return *this; + } + + WordInR0 operator+(SignExtendedByteInR0HighWordUnknown x) const { + char buf[128]; + if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "r0, %s", arm_regs[reg]); + emit("add ", buf); + emit("uxth", "r0, r0"); + } else { + emit_r1_from_reg(thisref); + snprintf(buf, sizeof(buf), "r0, r1"); + emit("add ", buf); + emit("uxth", "r0, r0"); + } + return WordInR0(); + } + + WordInR0HighWordUnknown operator+(int x) const { + assert(x == 1); + char buf[128]; + if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "r0, %s, #%d", arm_regs[reg], x); + emit("adds ", buf); + } else { + emit_r0_from_reg(thisref); + snprintf(buf, sizeof(buf), "r0, #%d", x); + emit("adds ", buf); + } + return WordInR0HighWordUnknown(); + } + + WordInR0HighWordUnknown operator-(int x) const { + assert(x == 1); + char buf[128]; + if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "r0, %s, #%d", arm_regs[reg], x); + emit("subs ", buf); + } else { + emit_r0_from_reg(thisref); + snprintf(buf, sizeof(buf), "r0, #%d", x); + emit("subs ", buf); + } + return WordInR0HighWordUnknown(); + } + + Reg16& operator=(const Reg16Ref& s) { + if (s.reg != reg) + { + char buf[32]; + snprintf(buf, sizeof(buf), "%s, %s", arm_regs[reg], arm_regs[s.reg]); + emit("mov ", buf); + } + return *this; + } + + Reg16& operator=(const RestingStateVariable& s) { + *this = (WordInR0)s; + return *this; + } + + Reg16& operator=(int val) { + set(val); + return *this; + } + + WordInR1 operator|(const WordInR1& v) const { + char buf[32]; + if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "r1, %s", arm_regs[reg]); + emit("orrs ", buf); + } else { + assert(false); // for now + } + return WordInR1(); + } + + void set(int x) { + char buf[128]; + if (is_arm_lo_reg(reg)) { + snprintf(buf, sizeof(buf), "%s, #%d", arm_regs[reg], x); + emit("movs ", buf); + } else { + snprintf(buf, sizeof(buf), "r0, #%d", x); + emit("movs ", buf); + snprintf(buf, sizeof(buf), "%s, r0", arm_regs[reg]); + emit("mov ", buf); + } + } + operator WordInR0() const { + return WordInR0(thisref); + } + operator WordInR1() const { + return WordInR1(thisref); + } + enum Reg16Name reg; + Reg16Ref thisref; + word value; + }; + +#define rs_var(name) static RestingStateVariable name; +#define rs_var_init(name) eZ80t::RestingStateVariable eZ80t::name(#name, sizeof(z80a_resting_state.name), rs_offsetof_##name) +#define rs_var_struct_init(s, name) eZ80t::RestingStateVariable eZ80t::s::name(#s "." #name, sizeof(z80a_resting_state.name), rs_offsetof_##s##_##name) + + static void assign_hi_byte_from_lo_byte_clear_r0(Reg8Ref ref) { + assert(!ref.lo); + Reg16Name reg = ref.reg; + assert(reg != TEMPORARY && reg != TEMPORARY_RESTRICTED); // can't imagine anyone assigning to high byte of temporary + emit_zero_extend_z80_lo_to_arm(reg, 1); + emit("orrs ", "r0, r1"); + emit_reg_from_r0(ref); + } + + static void assign_lo_byte_from_hi_byte_clear_r(Reg8Ref ref, const char*arm_reg_name) { + assert(ref.lo); + char buf[128]; + Reg16Name reg = ref.reg; + if (reg == TEMPORARY) { + snprintf(buf, sizeof(buf), "r_temp, %s", arm_reg_name); + emit("mov ", buf, "// fine to overwrite hi in r_temp"); + } else if (reg == TEMPORARY_RESTRICTED) { + snprintf(buf, sizeof(buf), "r_temp_restricted, %s", arm_reg_name); + emit("mov ", buf, "// fine to overwrite hi in r_temp_restricted"); + } else if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "%s, #8", arm_regs[reg]); + emit("lsls ", buf); + emit("lsrs ", buf); + snprintf(buf, sizeof(buf), "%s, %s", arm_regs[reg], arm_reg_name); + emit("orrs ", buf); + } else { + emit_r1_from_reg(reg); + emit("lsrs ", "r1, #8"); + emit("lsls ", "r1, #8"); + snprintf(buf, sizeof(buf), "r1, %s", arm_reg_name); + emit("orrs ", buf); + emit_reg_from_r1(ref); + } + } + + friend RegLo& operator|=(RegLo& r, int val) { + char buf[32]; + assert(val>=0 && val<= 255); + snprintf(buf, sizeof(buf), "r0, #%d", val); + emit("movs ", buf); + emit("orrs ", "r_af, r0"); + return r; + } + + friend RegLo& operator&=(RegLo& r, int val) { + char buf[32]; + if (val < 0) { + val = ~val; + assert(val >= 0 && val <= 255); + snprintf(buf, sizeof(buf), "r0, #%d", val); + emit("movs ", buf); + emit("bics ", "r_af, r0"); + return r; + } else + { + assert(val >= 0 && val <= 255); + snprintf(buf, sizeof(buf), "r0, #%d", val); + emit("movs ", buf); + emit("ands ", "r_af, r0"); + return r; + } + } + + friend LoByteAndGarbageInR0 operator+(RegLo& r, LoByteAndGarbageInR0 v) { + emit("add ", "r0, r_temp"); + return v; + } + + friend LoByteAndGarbageInR0 operator+(LoByteAndGarbageInR0 l, int val) { + char buf[32]; + assert(val>=0 && val<= 255); + snprintf(buf, sizeof(buf), "r0, #%d", val); + emit("adds ", buf); + return l; + } + + friend LoByteAndGarbageInR0 operator-(LoByteAndGarbageInR0 l, int val) { + char buf[32]; + assert(val>=0 && val<= 255); + snprintf(buf, sizeof(buf), "r0, #%d", val); + emit("subs ", buf); + return l; + } + + template struct RegHi { + RegHi() : reg(R), thisref(R, false) { } + RegHi(ZeroExtendedByteInR0 v) : reg(R), thisref(R, false) { + emit("lsls", "r0, #8"); + assign_hi_byte_from_lo_byte_clear_r0(); + } + + operator Reg8Ref() { + return thisref; + } + + void assign_hi_byte_from_lo_byte_clear_r0() { + eZ80t::assign_hi_byte_from_lo_byte_clear_r0(thisref); + } + + template RegHi& operator=(const RegHi& s) { + emit_r0_from_reg_hi(s.thisref); + emit("lsls ", "r0, #8"); + assign_hi_byte_from_lo_byte_clear_r0(); + return *this; + } + + template RegHi& operator=(const RegLo& s) { + emit_zero_extend_z80_lo_to_arm(s.reg, 0); + emit("lsls", "r0, #8"); + assign_hi_byte_from_lo_byte_clear_r0(); + return *this; + } + + ZeroExtendedByteInR0 operator&(int v) { + ZeroExtendedByteInR0 rc = *this; + assert(v>=0 && v<=255); + char buf[32]; + snprintf(buf, sizeof(buf), "r1, #0x%02x", v); + emit("movs ", buf); + emit("ands ", "r0, r1"); + return rc; + } + + operator ZeroExtendedByteInR0() const { + return ZeroExtendedByteInR0(thisref); + } + + operator LoByteAndGarbageInR0() const { + return LoByteAndGarbageInR0(thisref); + } + + WordInR1 operator<<(int v) { + assert( v == 8); + if (is_arm_lo_reg(reg)) + { + char buf[32]; + snprintf(buf, sizeof(buf), "r1, %s, #8", arm_regs[reg]); + emit("lsrs ", buf); + } else { + emit_r1_from_reg_hi(thisref); + emit("lsrs ", "r1, #8"); + } + emit("lsls ", "r1, #8"); + return WordInR1(); + } + + RegHi operator --() { + sub(1); + return *this; + } + + RegHi operator^=(int v) { + char buf[32]; + assert(v >=0 && v <= 255); + assert(R == AF); + snprintf(buf, sizeof(buf), "r0, #%d", v); + emit("movs ", buf); + emit("lsls ", "r0, #8"); + emit("eors ", "r_af, r0"); + return *this; + } + + void sub(int x) { + assert(x>=0 && x < 256); + char buf[128]; + sprintf(buf, "r1, #%d", x); + emit("movs ", buf); + emit("lsls ", "r1, #8"); + if (is_arm_lo_reg(reg)) + { + snprintf(buf, sizeof(buf), "%s, r1", arm_regs[reg]); + emit("subs ", buf); + snprintf(buf, sizeof(buf), "%s, %s", arm_regs[reg], arm_regs[reg]); + emit("uxth", buf); + } else { + emit_r0_from_reg(thisref); + emit("subs ", "r0, r1"); + emit("uxth", "r0, r0"); + emit_reg_from_r0(thisref); + } + } + + enum Reg16Name reg; + Reg8Ref thisref; + }; + + template struct RegLo + { + RegLo() : reg(R), thisref(R, true) + { + } + + RegLo(ZeroExtendedByteInR0 v) : reg(R), thisref(R, true) + { + assign_lo_byte_from_hi_byte_clear_r0(); + } + + void assign_lo_byte_from_hi_byte_clear_r0() { + eZ80t::assign_lo_byte_from_hi_byte_clear_r(thisref, "r0"); + } + + operator Reg8Ref() { + return thisref; + } + + operator ZeroExtendedByteInR0() const { + return ZeroExtendedByteInR0(thisref); + } + + operator LoByteAndGarbageInR0() const { + return LoByteAndGarbageInR0(thisref); + } + + template RegLo& operator=(const RegLo& s) { + emit_zero_extend_z80_lo_to_arm(s.reg, 0); + assign_lo_byte_from_hi_byte_clear_r0(); + return *this; + } + + template RegLo& operator=(RegHi s) { + emit_r0_from_reg_hi(s); + assign_lo_byte_from_hi_byte_clear_r0(); + return *this; + } + + RegLo& operator=(const WordInR0HighWordUnknown& s) + { + if (reg == TEMPORARY) { + WordInR0 __unused x = s; + emit("mov ", "r_temp, r0","// fine to overwrite hi in r_temp"); + } else if (reg == TEMPORARY_RESTRICTED) { + __unused WordInR0 x = s; + emit("mov ", "r_temp_restricted, r0","// fine to overwrite hi in r_temp"); + } else + { + emit("uxtb", "r0, r0"); + assign_lo_byte_from_hi_byte_clear_r0(); + } + return *this; + } + + enum Reg16Name reg; + + Reg8Ref thisref; + }; + + typedef Reg16 temp16; + typedef RegLo temp8; + typedef WordInR1 temp16_2; + // the scratch must be global +#define scratch16 + typedef Reg16 temp16_xy; + typedef RegLo temp8_xy; + typedef SignExtendedByteInR0HighWordUnknown address_delta; + + TState t; + static const char *arm_regs[REG_COUNT]; + + eZ80t(); + + typedef void (eZ80t::*CALLFUNC)(); +// typedef temp8 (eZ80t::*CALLFUNCI)(temp8); + + void generate_arm(); + +protected: + ZeroExtendedByteInR0 IoRead(WordInR0 port) const; + void IoWrite(WordInR1 port, LoByteAndGarbageInR0 v); + ZeroExtendedByteInR0 Read(WordInR0 addr) const; + ZeroExtendedByteInR0 ReadInc(Reg16Ref addr) const; + WordInR0 Read2(WordInR0 addr) const; // low then high + WordInR0 Read2Inc(Reg16Ref addr) const; // low then high + void Write(Reg16Ref addr, LoByteAndGarbageInR0 v); + void WriteXY(WordInR0 addr, Reg8Ref v); + void Write2(Reg16Ref addr, WordInR0 v); // low then high + void dump_instructions(const char *prefix, const CALLFUNC *instrs, int count, const char *description=NULL, int mult = 1); + + static const char *pending_call; + static void emit_pending_call() { + if (pending_call) { + const char *funcname = pending_call; + pending_call = nullptr; + emit_save_lr_if_not_done_already(); + emit("bl ", funcname); + } + } + static void emit(const char *a) { + emit_pending_call(); + printf("%s\n", a); + } + static void emit(const char *a, const char *b, const char *c = "") { + char buf[128]; + snprintf(buf, sizeof(buf), "\t%s %s\t\t%s", a, b, c); + emit(buf); + } + static void emit_zero_extend_z80_lo_to_arm(RegRef lo, int arm_reg) { + char buf[32]; + if (is_arm_lo_reg(lo.reg)) + { + snprintf(buf, sizeof(buf), "r%d, %s", arm_reg, arm_regs[lo.reg]); + emit("uxtb", buf); + } else { + if (arm_reg == 0) { + emit_r0_from_reg(lo); + } else if (arm_reg == 1) { + emit_r1_from_reg(lo); + } else if (arm_reg == R_TEMP_ARM) { + emit_r_temp_from_reg(lo); + } else { + assert(false); + } + snprintf(buf, sizeof(buf), "r%d, r%d", arm_reg, arm_reg); + emit("uxtb", buf); + } + } + + static void emit_r0_from_reg(const RegRef &x) { + char buf[32]; + snprintf(buf, sizeof(buf), "r0, %s", arm_regs[x.reg]); + emit("mov ", buf); + } + + static void emit_r_temp_from_reg(const RegRef &x) { + char buf[32]; + snprintf(buf, sizeof(buf), "r_temp, %s", arm_regs[x.reg]); + emit("mov ", buf); + } + + static void emit_r_temp_from_reg8(const Reg8Ref &x) { + char buf[32]; + if (x.lo) { + emit_zero_extend_z80_lo_to_arm(x, R_TEMP_ARM); + } else { + if (is_arm_lo_reg(x.reg)) + { + snprintf(buf, sizeof(buf), "r_temp, %s, #8", arm_regs[x.reg]); + emit("lsrs ", buf); + } else { + emit_r_temp_from_reg((RegRef)x); + emit("lsrs ", "r_temp, #8"); + } + } + } + + static void emit_reg8_from_r_temp(const Reg8Ref &x) { + if (x.lo) { + assign_lo_byte_from_hi_byte_clear_r(x, "r_temp"); + } else { + emit("lsls ", "r0, r_temp, #8"); + assign_hi_byte_from_lo_byte_clear_r0(x); + } + } + + static void emit_r0_from_reg_lo(const Reg8Ref &x) { + assert(x.lo); + emit_zero_extend_z80_lo_to_arm(x, 0); + } + + static void emit_r1_from_reg_lo(const Reg8Ref &x) { + assert(x.lo); + emit_zero_extend_z80_lo_to_arm(x, 1); + } + + static void emit_r0_from_reg8(const Reg8Ref &x) { + if (x.lo) { + emit_r0_from_reg_lo(x); + } else { + emit_r0_from_reg_hi(x); + } + } + static void emit_reg8_from_r0(const Reg8Ref &x) { + if (x.lo) { + assign_lo_byte_from_hi_byte_clear_r(x, "r0"); + } else { + emit("lsls ", "r0, #8"); + assign_hi_byte_from_lo_byte_clear_r0(x); + } + } + + static void emit_r0_from_reg_hi(const Reg8Ref &x) { + char buf[32]; + assert(!x.lo); + if (is_arm_lo_reg(x.reg)) + { + snprintf(buf, sizeof(buf), "r0, %s, #8", arm_regs[x.reg]); + emit("lsrs ", buf); + } else { + emit_r0_from_reg(x); + emit("lsrs ", "r0, #8"); + } + } + static void emit_r1_from_reg_hi(const Reg8Ref &x) { + char buf[32]; + assert(!x.lo); + if (is_arm_lo_reg(x.reg)) + { + snprintf(buf, sizeof(buf), "r1, %s, #8", arm_regs[x.reg]); + emit("lsrs ", buf); + } else { + emit_r1_from_reg(x); + emit("lsrs ", "r1, #8"); + } + } + static void emit_reg_from_r0(const RegRef &x) { + char buf[32]; + snprintf(buf, sizeof(buf), "%s, r0", arm_regs[x.reg]); + emit("mov ", buf); + } + static void emit_reg_from_r1(const RegRef &x) { + char buf[32]; + snprintf(buf, sizeof(buf), "%s, r1", arm_regs[x.reg]); + emit("mov ", buf); + } + static void emit_r1_from_reg(const RegRef &x) { + char buf[32]; + snprintf(buf, sizeof(buf), "r1, %s", arm_regs[x.reg]); + emit("mov ", buf); + } + + template void if_nonzero(ZeroExtendedByteInR0 v, A&& a, B&& b) { + // can pass zero extended value + if_nonzero(WordInR0(), a, b); + } + + template void if_nonzero(WordInR0 v, A&& a, B&& b) { + emit("cmp ", "r0, #0"); + emit("beq ", "1f"); + int lr = lr_saved; + a(); + emit_function_return(); + lr_saved = lr; + emit("1:"); + b(); + } + + template void if_nonzero(WordInR0 v, A&& a) { + emit("cmp ", "r0, #0"); + emit("beq ", "1f"); + a(); + emit("1:"); + } + + template void if_zero(ZeroExtendedByteInR0 v, A&& a) { + if_zero((WordInR0)v, a); + } + + template void if_zero(WordInR0 v, A&& a) { + if_equal(v, 0, a); + } + + template void if_equal(ZeroExtendedByteInR0 v, int cmp, A&& a) { + if_equal((WordInR0)v, cmp, a); + } + + template void if_equal(WordInR0 v, int cmp, A&& a) { + char buf[128]; + assert(cmp >=0 && cmp <=255); + snprintf(buf, sizeof(buf), "r0, #%d", cmp); + emit("cmp ", buf); + emit("bne ", "1f"); + a(); + emit("1:"); + } + + template void if_flag_set(byte flag, A&& a, B&& b) { + assert(__builtin_popcount(flag) == 1); + char buf[32]; + snprintf(buf, sizeof(buf), "r0, r_af, #%d", 1 + __builtin_ctz(flag)); + emit("lsrs ", buf); + emit("bcc ", "2f"); + int lr = lr_saved; + a(); + emit_function_return(); + lr_saved = lr; + emit("2:"); + b(); + } + + template void if_flag_set(byte flag, A&& a) { + assert(__builtin_popcount(flag) == 1); + char buf[32]; + snprintf(buf, sizeof(buf), "r0, r_af, #%d", 1 + __builtin_ctz(flag)); + emit("lsrs ", buf); + emit("bcc ", "2f"); + a(); + emit("2:"); + } + + + template void if_flag_clear(byte flag, A&& a, B&& b) { + assert(__builtin_popcount(flag) == 1); + char buf[32]; + snprintf(buf, sizeof(buf), "r0, r_af, #%d", 1 + __builtin_ctz(flag)); + emit("lsrs ", buf); + emit("bcs ", "3f"); + int lr = lr_saved; + a(); + emit_function_return(); + lr_saved = lr; + emit("3:"); + b(); + } + + template void if_flag_clear(byte flag, A&& a) { + assert(__builtin_popcount(flag) == 1); + char buf[32]; + snprintf(buf, sizeof(buf), "r0, r_af, #%d", 1 + __builtin_ctz(flag)); + emit("lsrs ", buf); + emit("bcs ", "3f"); + a(); + emit("3:"); + } + +#define set_a35_flags_preserve_set(preserve, set) _set_a35_flags_preserve_set(#preserve, #set) + + void _set_a35_flags_preserve_set(const char *preserve, const char *set) { + char buf[128]; + snprintf(buf, sizeof(buf), "r0, %s", preserve); + emit("preserve_only_flags", buf); + snprintf(buf, sizeof(buf), "r_af, #%s", set); + emit("adds ", buf); + + ZeroExtendedByteInR0 __unused r0 = a; + emit("movs ", "r1, #(F3|F5)"); + emit("ands ", "r0, r1"); + emit("orrs ", "r_af, r0"); + } + +#define set_logic_flags_preserve(reg, preserve) _set_logic_flags_preserve_reset(reg, #preserve, NULL) +#define set_logic_flags_preserve_reset(reg, preserve, reset) _set_logic_flags_preserve_reset(reg, #preserve, #reset) + + void _set_logic_flags_preserve_reset(ZeroExtendedByteInR0 value, const char *preserve_flags, const char* reset_flags) { + char buf[128]; + snprintf(buf, sizeof(buf), "r1, %s", preserve_flags); + emit("preserve_only_flags", buf); + emit("ldr ", "r1, =_log_f"); + emit("ldrb", "r0, [r1, r0]"); + emit("orrs ", "r_af, r0"); + if (reset_flags) { + snprintf(buf, sizeof(buf), "r1, #%s", reset_flags); + emit("movs ", buf); + emit("bics ", "r_af, r1"); + } + } + + static bool r_temp_used; + static bool r_temp_restricted_used; + + void reset_function_state() { + r_temp_used = r_temp_restricted_used = false; + lr_saved = false; + } + void emit_function_return() { + if (pending_call && !lr_saved) { + // tail call (note we don't tail call when lr saved, since we can't pop lr + + // don't ever use r2 for arg (I hope) + char buf[32]; + // almost certainly too far for plain "b label" + snprintf(buf, sizeof(buf), "r2, =%s", pending_call); + pending_call = NULL; // reset so we don't recurse + emit("ldr ", buf); + emit("bx ", "r2"); + emit(""); + return; + } + emit_pending_call(); + if (lr_saved) { + emit("pop ", "{pc}"); + } else + { + emit("bx ", "lr"); + } + emit(""); + } + static void emit_save_lr_if_not_done_already() { + if (!lr_saved) + { + emit("push", "{lr}"); + lr_saved = true; + } + } + static bool lr_saved; + static void emit_call_func(const char *funcname) { + pending_call = funcname; + } + static void emit_call_func_with_reg(RegRef &x, const char *funcname) { + emit_r0_from_reg(x); + emit_call_func(funcname); + emit_reg_from_r0(x); + } + + void inc8(Reg8Ref x) + { + emit_r0_from_reg8(x); + emit_call_func("inc8"); + emit_reg8_from_r0(x); + } + void dec8(Reg8Ref x) + { + emit_r0_from_reg8(x); + emit_call_func("dec8"); + emit_reg8_from_r0(x); + } + void rlc8(Reg8Ref x) { + emit_r0_from_reg8(x); + emit_call_func("rlc8"); + emit_reg8_from_r0(x); + } + void rrc8(Reg8Ref x) { + emit_r0_from_reg8(x); + emit_call_func("rrc8"); + emit_reg8_from_r0(x); + } + void rl8(Reg8Ref x) { + emit_r0_from_reg8(x); + emit_call_func("rl8"); + emit_reg8_from_r0(x); + } + void rr8(Reg8Ref x) { + emit_r0_from_reg8(x); + emit_call_func("rr8"); + emit_reg8_from_r0(x); + } + void sla8(Reg8Ref x) { + emit_r0_from_reg8(x); + emit_call_func("sla8"); + emit_reg8_from_r0(x); + } + void sli8(Reg8Ref x) { + emit_r0_from_reg8(x); + emit_call_func("sli8"); + emit_reg8_from_r0(x); + } + void sra8(Reg8Ref x) { + emit_r0_from_reg8(x); + emit_call_func("sra8"); + emit_reg8_from_r0(x); + } + void srl8(Reg8Ref x) { + emit_r0_from_reg8(x); + emit_call_func("srl8"); + emit_reg8_from_r0(x); + } +#ifdef USE_LARGER_FASTER_CB + void bit(Reg8Ref x, byte bit) + { + // this function is large, so call the corrsesponding ls function + emit_r_temp_from_reg8(x); + char buf[32]; + assert(bit >= 0 && bit <= 7); + snprintf(buf, sizeof(buf), "opli_r35_%02x", 8*(8 + bit)); // note 8-15 are bit functions + emit_call_func(buf); + emit_reg8_from_r_temp(x); + } + void bitmem(ZeroExtendedByteInR0 src, byte bit) + { + // this function is large, so call the corrsespdnding ls function + emit("mov ", "r_temp, r0"); + char buf[32]; + assert(bit >= 0 && bit <= 7); + snprintf(buf, sizeof(buf), "opli_m35_%02x", 8*(8 + bit)); // note 8-15 are bit functions + emit_call_func(buf); + } + // These are also slightly faster than the 8x32 versions since they can work on values in place (e.g. high reg) + void res(Reg8Ref x, byte bit) const + { + if (!x.lo) + { + bit += 8; + } + char buf[32]; + if (bit < 8) + { + snprintf(buf, sizeof(buf), "r0, #0x%02x", 1 << bit); + emit("movs ", buf); + } else { + emit("movs ", "r0, #1"); + snprintf(buf, sizeof(buf), "r0, #%d", bit); + emit("lsls ", buf); + } + if (is_arm_lo_reg(x.reg)) { + snprintf(buf, sizeof(buf), "%s, r0", arm_regs[x.reg]); + emit("bics ", buf); + } else + { + emit_r1_from_reg(x); + emit("bics ", "r1, r0"); + emit_reg_from_r1(x); + } + } + void set(Reg8Ref x, byte bit) const + { + if (!x.lo) + { + bit += 8; + } + char buf[32]; + if (bit < 8) + { + snprintf(buf, sizeof(buf), "r0, #0x%02x", 1 << bit); + emit("movs ", buf); + } else { + emit("movs ", "r0, #1"); + snprintf(buf, sizeof(buf), "r0, #%d", bit); + emit("lsls ", buf); + } + if (is_arm_lo_reg(x.reg)) { + snprintf(buf, sizeof(buf), "%s, r0", arm_regs[x.reg]); + emit("orrs ", buf); + } else + { + emit_r1_from_reg(x); + emit("orrs ", "r1, r0"); + emit_reg_from_r1(x); + } + } +#endif + void add8(ZeroExtendedByteInR0 src) + { + emit_call_func("add8"); + } + void adc8(ZeroExtendedByteInR0 src) + { + emit_call_func("adc8"); + } + void sub8(ZeroExtendedByteInR0 src) + { + emit_call_func("sub8"); + } + void sbc8(ZeroExtendedByteInR0 src) + { + emit_call_func("sbc8"); + } + void and8(ZeroExtendedByteInR0 src) + { + emit_call_func("ands8"); + } + void or8(ZeroExtendedByteInR0 src) + { + emit_call_func("or8"); + } + void xor8(ZeroExtendedByteInR0 src) + { + emit_call_func("xor8"); + } + void cp8(ZeroExtendedByteInR0 src) + { + emit_call_func("cp8"); + } + + void OpeA2A3AAABFlags(const RegLo& val, LoByteAndGarbageInR0 tmp) + { + emit("// a2a3aaabflags r_temp, r0", ""); + + // f = 0; + emit("lsrs ", "r_af, #8"); + emit("lsls ", "r_af, #8"); + +// f = log_f[b] & ~PV; + emit("mov ", "r2, r_bc"); + emit("lsrs ", "r2, #8"); + emit("ldr ", "r1, =_log_f"); + emit("ldrb", "r2, [r1, r2]"); + emit("movs ", "r1, #PV"); + emit("bics ", "r2, r1"); + emit("orrs ", "r_af, r2"); + + // if(tmp < val) f |= (HF|CF); + emit("uxtb", "r0, r0"); + emit("cmp ", "r0, r_temp"); + emit("bge ", "1f"); + emit("adds ", "r_af, #HF|CF"); + emit("1:"); + + // if(val & 0x80) f |= NF; + emit("lsrs ", "r1, r_temp, #8"); + emit("bcc ", "1f"); + emit("adds ", "r_af, #NF"); + emit("1:"); + +// if(log_f[(tmp & 0x07) ^ b] & PV) f |= PV; + emit("lsls ", "r1, r0, #29"); + emit("lsrs ", "r1, #21"); + emit("mov ", "r2, r_bc"); + emit("eors ", "r1, r2"); + emit("lsrs ", "r1, #8"); + emit("ldr ", "r0, =_log_f"); + emit("ldrb", "r0, [r0, r1]"); + emit("movs ", "r1, #PV"); + emit("ands ", "r0, r1"); + emit("orrs ", "r_af, r0"); + } + + // --- logic (cb) ops -------------------------------------- + + // Note these are the versions that work on r_temp + + void Opl_rlc() { + rlc8(temp_lo); + } + + void Opl_rrc() { + rrc8(temp_lo); + } + + void Opl_rl() { + rl8(temp_lo); + } + + void Opl_rr() { + rr8(temp_lo); + } + + void Opl_sla() { + sla8(temp_lo); + } + + void Opl_sra() { + sra8(temp_lo); + } + + void Opl_sli() { + sli8(temp_lo); + } + + void Opl_srl() { + srl8(temp_lo); + } + + void Opl_bit0() { + bit_regular_35(0); + } + + void Opl_bit1() { + bit_regular_35(1); + } + + void Opl_bit2() { + bit_regular_35(2); + } + + void Opl_bit3() { + bit_regular_35(3); + } + + void Opl_bit4() { + bit_regular_35(4); + } + + void Opl_bit5() { + bit_regular_35(5); + } + + void Opl_bit6() { + bit_regular_35(6); + } + + void Opl_bit7() { + bit_regular_35(7); + } + + void Opl_bit0m() { + bit_mem_h_35(0); + } + + void Opl_bit1m() { + bit_mem_h_35(1); + } + + void Opl_bit2m() { + bit_mem_h_35(2); + } + + void Opl_bit3m() { + bit_mem_h_35(3); + } + + void Opl_bit4m() { + bit_mem_h_35(4); + } + + void Opl_bit5m() { + bit_mem_h_35(5); + } + + void Opl_bit6m() { + bit_mem_h_35(6); + } + + void Opl_bit7m() { + bit_mem_h_35(7); + } + + void Opl_res0() { + res_rtemp(0); + } + + void Opl_res1() { + res_rtemp(1); + } + + void Opl_res2() { + res_rtemp(2); + } + + void Opl_res3() { + res_rtemp(3); + } + + void Opl_res4() { + res_rtemp(4); + } + + void Opl_res5() { + res_rtemp(5); + } + + void Opl_res6() { + res_rtemp(6); + } + + void Opl_res7() { + res_rtemp(7); + } + + void Opl_set0() { + set_rtemp(0); + } + + void Opl_set1() { + set_rtemp(1); + } + + void Opl_set2() { + set_rtemp(2); + } + + void Opl_set3() { + set_rtemp(3); + } + + void Opl_set4() { + set_rtemp(4); + } + + void Opl_set5() { + set_rtemp(5); + } + + void Opl_set6() { + set_rtemp(6); + } + + void Opl_set7() { + set_rtemp(7); + } + + void res_rtemp(byte bit) const + { + char buf[32]; + snprintf(buf, sizeof(buf), "r0, #0x%02x", 1< void add16(Reg16 regM, Reg16Ref regN) { + memptr = regM+1; + emit_r0_from_reg(regM); + emit_r1_from_reg(regN); + emit_call_func("add16"); + emit_reg_from_r0(regM); + t += 7; + } + + void adc16(Reg16Ref reg) { + memptr = hl+1; + emit_r0_from_reg(reg); + emit_call_func("adc16"); + t += 7; + } + + // hl, reg + void sbc16(Reg16Ref reg) { + memptr = hl+1; + emit_r0_from_reg(reg); + emit_call_func("sbc16"); + t += 7; + } + + void push(Reg16Ref v) const { + emit_r0_from_reg(v); + emit_call_func("_push"); + } + +#include "../z80/z80_op_noprefix.h" + +#ifdef USE_LARGER_FASTER_CB + #include "../z80/z80_op_cb.h" +#endif + #include "../z80/z80_op_dd.h" + #include "../z80/z80_op_fd.h" + #include "../z80/z80_op_ed.h" + + // hand coded instructions + void Op3F() { // ccf + emit("movs ", "r1, #CF"); + emit("lsrs ", "r0, r_af, #1"); + emit("bcc ", "1f"); + emit("movs ", "r1, #HF"); + emit("1:"); + emit("preserve_only_flags", "r0, PV|ZF|SF"); + emit("orrs ", "r_af, r1"); + emit("lsrs ", "r0, r_af, #8"); + emit("movs ", "r1, #(F3|F5)"); + emit("ands ", "r0, r1"); + emit("orrs ", "r_af, r0"); + } + + void OpED() + { + emit_save_lr_if_not_done_already(); + emit("ldr ","r_temp, =ope_table"); + emit("step_op_table_in_r_temp_maybe_neg",""); + } + + void OpDD() + { + emit("movs ", "r0, #0xdd"); + emit_call_func("opDDFD"); + } + + void OpFD() + { + emit("movs ", "r0, #0xfd"); + emit_call_func("opDDFD"); + } + + void OpCB() + { +#ifndef USE_LARGER_FASTER_CB + emit("", "// note this is a slow implementation which is 4K smaller... define USE_LARGER_FASTER_CB for speed"); + emit("fetch", ""); + // r1 is inner instruction + emit("lsrs ", "r2, r0, #3"); + emit("lsls ", "r2, #1"); + + emit("movs ", "r1, #7"); + emit("ands ", "r1, r0", "// r1 is outer instruction"); + + emit("cmp ", "r1, #6", "// check for memory operand"); + emit("bne ", "2f"); + emit("ldr ", "r_temp, =opli_m35_table"); + emit("b ", "3f"); + emit("2:"); + emit("ldr ", "r_temp, =opli_r35_table"); + emit("3:"); + emit("ldrh", "r2, [r_temp, r2]"); + emit("sxth", "r2, r2"); + emit("add ", "r2, r_temp"); + + emit("ldr ", "r_temp, =opcbX_table"); + emit("lsrs ", "r0, #7"); + emit("bcc ","1f", "// arg no single test for this flag combo"); + emit("bne ","1f"); + emit("adds ", "r_temp, #(opcbbitX_table - opcbX_table)"); + emit("1:"); + emit("lsls ", "r0, r1, #1"); + emit("ldrh", "r1, [r_temp, r0]"); + emit("sxth", "r1, r1"); + emit("add ", "r1, r_temp"); + + emit("bx ", "r1"); +#else + emit("ldr ", "r_temp, =opl_table"); + emit("fetch", ""); + + emit("lsls ", "r0, #1"); + emit("ldrh", "r0, [r_temp, r0]"); + emit("sxth", "r0, r0"); + emit("add ", "r0, r_temp"); + emit("bx ", "r0"); +#endif + } + + void OpD9() { // exx + // todo this isn't strictly necessary to hard code, but is slightly better than generated code + emit("ldr ", "r2, =z80a_resting_state"); + emit("mov ", "r0, r_bc"); + alt.bc.emit_read(1, 2); + alt.bc.emit_write(0, 2); + emit("mov ", "r_bc, r1"); + emit("mov ", "r0, r_de"); + alt.de.emit_read(1, 2); + alt.de.emit_write(0, 2); + emit("mov ", "r_de, r1"); + emit("mov ", "r0, r_hl"); + alt.hl.emit_read(1, 2); + alt.hl.emit_write(0, 2); + emit("mov ", "r_hl, r1"); + } + + void Op07() { // rlca + emit("movs ", "r0, #(SF|ZF|PV)"); + emit("ands ", "r0, r_af", "// r0 = f & (SF|ZF|PV)"); + emit("movs ", "r1, #0"); + emit("lsrs ", "r_af, #8"); + emit("lsls ", "r_af, #25"); + emit("bcc ", "1f"); + emit("movs ", "r1, #CF"); + emit("orrs ", "r0, r1", "// r0 = (f & (SF|ZF|PV)) | (a7 ? CF: 0)"); + emit("1:"); + emit("lsrs ", "r_af, #24"); + emit("orrs ", "r_af, r1"); + emit("movs ", "r1, #(F3|F5)", "// r_af = (a << 1) | (a7 ? 1 : 0)"); + emit("ands ", "r1, r_af"); + emit("lsls ", "r_af, #8", "// r_af = a' : 0"); + emit("orrs ", "r_af, r0", "// r_af = a' | f'"); + emit("orrs ", "r_af, r1", "// r_af |= 35 from a'"); + } + + void Op0F() { // rrca + emit("movs ", "r0, #(SF|ZF|PV)"); + emit("ands ", "r0, r_af", "// r0 = f & (SF|ZF|PV)"); + emit("lsrs ", "r_af, r_af, #9"); + emit("bcc ", "1f"); + emit("adds ", "r0, #CF", "// r0 = (f & (SF|ZF|PV)) | (a0 ? CF: 0)"); + emit("adds ", "r_af, #128", "// r_af = (a0 ? 0x80 : 0) | (a >> 1)"); + emit("1:"); + emit("movs ", "r1, #(F3|F5)"); + emit("ands ", "r1, r_af"); + emit("lsls ", "r_af, #8", "// r_af = a' : 0"); + emit("orrs ", "r_af, r0", "// r_af = a' | f'"); + emit("orrs ", "r_af, r1", "// r_af |= 35 from a'"); + } + + void Op17() { // rla + emit("movs ", "r0, #(SF|ZF|PV)"); + emit("ands ", "r0, r_af", "// r0 = f & (SF|ZF|PV)"); + emit("movs ", "r1, #CF"); + emit("ands ", "r1, r_af"); + emit("lsrs ", "r_af, #8"); + emit("lsls ", "r_af, #25"); + emit("bcc ", "1f"); + emit("adds ", "r0, #CF", "// r0 = (f & (SF|ZF|PV)) | (a7 ? CF: 0)"); + emit("1:"); + emit("lsrs ", "r_af, #24"); + emit("orrs ", "r_af, r1"); + emit("movs ", "r1, #(F3|F5)", "// r_af = (a << 1) | (c ? 1 : 0)"); + emit("ands ", "r1, r_af"); + emit("lsls ", "r_af, #8", "// r_af = a' : 0"); + emit("orrs ", "r_af, r0", "// r_af = a' | f'"); + emit("orrs ", "r_af, r1", "// r_af |= 35 from a'"); + } + + void Op1F() { // rra + emit("movs ", "r0, #(SF|ZF|PV)"); + emit("ands ", "r0, r_af", "// r0 = f & (SF|ZF|PV)"); + emit("lsls ", "r2, r_af, #15", "// r2 = (garbage : oldCF) << 15"); + emit("lsrs ", "r1, r_af, #9"); + emit("bcc ", "1f"); + emit("adds ", "r0, #CF", "// r0 = (f & (SF|ZF|PV)) | (a0 ? CF: 0)"); + emit("1:"); + emit("lsls ", "r_af, r1, #8"); + emit("orrs ", "r_af, r2"); + emit("orrs ", "r_af, r0"); + emit("movs ", "r0, #(F3|F5)"); + emit("uxth", "r_af, r_af"); + emit("ands ", "r0, r1"); // note we are anding with only 7 bits of A, but they include those we care about + emit("orrs ", "r_af, r0"); + } + + void Op27() { // daa +// int delta = 0; +// int newflags = f&(CF|NF); + + emit("movs ", "r0, #0", "// delta"); + emit("movs ", "r_temp, #(CF|NF)"); + emit("ands ", "r_temp, r_af", "// new CHN flags - CH may be set later"); + +// if ((f & HF) || (a & 0xf) > 9) { +// delta = 0x06; + + emit("// lo nibble", ""); + emit("lsls ", "r1, r_af, #20"); + emit("lsrs ", "r1, #28"); + emit("lsrs ", "r2, r_af, #HF_INDEX+1"); + emit("bcs ", "1f"); + emit("cmp ", "r1, #10"); + emit("blt ", "3f"); + emit("1:"); + emit("adds ", "r0, #0x06"); + +// if ((f & HF) || (a & 0xf) > 9) { +// delta = 0x06; +// if (f&NF) { +// if (0x10 & ((a&0xf)-delta)) newflags |= HF; +// } else { +// if (0x10 & ((a&0xf)+delta)) newflags |= HF; +// } +// } + + emit("cmp ", "r_temp, #NF"); + emit("blt ", "1f"); + emit("subs ", "r1, r0"); + emit("b ", "2f"); + emit("1:"); + emit("adds ", "r1, r0"); + emit("2:"); + emit("lsls ", "r1, #28"); + emit("bcc ", "3f"); + emit("movs ", "r2, #HF"); + emit("orrs ", "r_temp, r2"); + emit("3:"); + +// if (a > 0x99) { +// newflags |= CF; +// delta |= 0x60; +// } else if (f&CF) { +// delta |= 0x60; +// } + + emit("// hi nibble", ""); + emit("movs ", "r2, #CF"); + emit("lsrs ", "r1, r_af, #8"); + emit("cmp ", "r1, #0x9a"); + emit("blt ", "1f"); + emit("orrs ", "r_temp, r2"); + emit("b ", "2f"); + emit("1:"); + emit("ands ", "r2, r_af"); + emit("beq ", "3f"); + emit("2:"); + emit("adds ", "r0, #0x60"); + emit("3:"); + +// if( f & NF) { +// a = (a-delta)&0xff; +// } else { +// a = (a+delta)&0xff; +// } +// f = log_f[a] | newflags; + + emit("lsrs ", "r2, r_af, #NF_INDEX+1"); + emit("bcc ", "1f"); + emit("subs ", "r1, r0"); + emit("b ", "2f"); + emit("1:"); + emit("adds ", "r1, r0"); + emit("2:"); + emit("uxtb", "r_af, r1"); + + emit("ldr ", "r2, =_log_f"); + emit("ldrb", "r0, [r2, r_af]"); + + emit("lsls ", "r_af, #8"); + emit("orrs ", "r_af, r0"); + emit("orrs ", "r_af, r_temp"); + } + + void Ope57() { // ld a,i + i.emit_read_to_r0(); + a = ZeroExtendedByteInR0(); + set_logic_flags_preserve_reset(a, CF, PV); + t++; +// if (iff1 && (t+10 < frame_tacts)) f |= PV; + iff1.emit_read_to_r0(); + emit("cmp ", "r0, #0"); + emit("beq ", "1f"); + emit("#ifndef USE_Z80_ARM_OFFSET_T"); + emit("ldr ", "r0, frame_tacts"); + emit("#else"); + emit("movs ", "r0, #0"); + emit("#endif"); + emit("subs ", "r0, #10"); + emit("cmp ", "r_t, r0"); + emit("bge ", "1f"); + emit("adds ", "r_af, #PV"); + emit("1:"); + } + + void Ope5F() { // ld a,r + emit("#ifdef NO_UPDATE_RLOW_IN_FETCH"); + // save on counting r_low + emit("lsrs ", "r0, r_t, #2"); + emit("#else"); + r_low.emit_read_to_r0(); + emit("#endif"); + emit("movs ", "r1, #0x7f"); + emit("ands ", "r0, r1"); + r_hi.emit_read_to_r1(); + emit("orrs ", "r0, r1"); + emit_reg8_from_r0(a); + +// f = (log_f[a] | (f & CF)) & ~PV; + set_logic_flags_preserve_reset(a, CF, PV); + t++; +// if (iff2 && ((t+10 < frame_tacts) || eipos+8==t)) f |= PV; + iff1.emit_read_to_r0(); + emit("cmp ", "r0, #0"); + emit("beq ", "1f"); + emit("#ifndef USE_Z80_ARM_OFFSET_T"); + emit("ldr ", "r0, frame_tacts"); + emit("#else"); + emit("movs ", "r0, #0"); + emit("#endif"); + emit("subs ", "r0, #10"); + emit("cmp ", "r_t, r0"); + emit("blt ", "2f"); + eipos.emit_read_to_r0(); + emit("adds ", "r0, #8"); + // don't need offset_t check for eipos, since it is also offset + emit("cmp ", "r0, r_t"); + emit("bne ", "1f"); + emit("2:"); + emit("adds ", "r_af, #PV"); + emit("1:"); + } + + void Ope6F() { // rld + temp8 tmp = Read(hl); + memptr = hl+1; +// Write(hl, (a & 0x0F) | (tmp << 4)); + emit_r0_from_reg8(a); + emit("movs ", "r2, #0xf"); + emit("ands ", "r0, r2"); + emit_r1_from_reg_lo(tmp); + emit("lsls ", "r1, #4"); + emit("orrs ", "r0, r1"); + Write(hl, LoByteAndGarbageInR0()); +// a = (a & 0xF0) | (tmp >> 4); + emit_r0_from_reg8(a); + emit("movs ", "r2, #0xf0"); + emit("ands ", "r0, r2"); + emit_r1_from_reg_lo(tmp); + emit("lsrs ", "r1, #4"); + emit("orrs ", "r0, r1"); + a = ZeroExtendedByteInR0(); + set_logic_flags_preserve(a, CF); + t += 10; + } + + void Ope67() { // rrd + temp8 tmp = Read(hl); + memptr = hl+1; +// Write(hl, (a << 4) | (tmp >> 4)); + emit_r0_from_reg8(a); + emit("lsls ", "r0, #4"); + emit_r1_from_reg_lo(tmp); + emit("lsrs ", "r1, #4"); + emit("orrs ", "r0, r1"); + Write(hl, LoByteAndGarbageInR0()); +// a = (a & 0xF0) | (tmp & 0x0F); + emit_r0_from_reg8(a); + emit("movs ", "r2, #0xf0"); + emit("ands ", "r0, r2"); + emit_r1_from_reg_lo(tmp); + emit("bics ", "r1, r2"); + emit("orrs ", "r0, r1"); + a = ZeroExtendedByteInR0(); + set_logic_flags_preserve(a, CF); + t += 10; + } + + void Op76() { // halt + halted = 1; +// unsigned int st = (frame_tacts - t - 1) / 4 + 1; +// t += 4 * st; + emit("#ifndef USE_Z80_ARM_OFFSET_T"); + emit("ldr ", "r0, frame_tacts"); + emit("#else"); + emit("movs ", "r0, #0"); + emit("#endif"); + emit("subs ", "r0, r_t"); + emit("movs ", "r1, #3"); + emit("adds ", "r0, r1"); + emit("bics ", "r0, r1"); + emit("add ", "r_t, r0"); +#ifndef NO_USE_REPLAY + if(handler.io) // replay is active + { + r_low += fetches; + fetches = 0; + } + else + r_low += st; +#else + emit("#ifndef NO_UPDATE_RLOW_IN_FETCH"); + r_low.emit_read_to_r1(); + emit("lsrs ", "r0, #2"); + emit("add ", "r0, r1"); + r_low.emit_write_from_r0(); + emit("#endif"); +#endif + } + + void Ope44() { // neg + // there are multiple neg op codes, so call (well bx) the function + emit_call_func("neg8"); + } + +#define HELPER_START -1 + #define HELPER_END -2 + void emit_helper_functions(const char *prefix, int num) { + char buf[128]; + reset_function_state(); + if (!strcmp(prefix, "ope")) { + if (num == 0xb0) { + emit("ldX_common:"); + t += 8; + temp8 tempbyte = Read(hl); + Write(de, tempbyte); +// tempbyte += a; tempbyte = (tempbyte & F3) + ((tempbyte << 4) & F5); +// f = (f & ~(NF|HF|PV|F3|F5)) + tempbyte; + emit("preserve_only_flags", "r1, (CF|SF|ZF)"); + emit("lsrs ", "r0, r_af, #8"); + emit("add ", "r_temp, r0"); + emit_call_func("set_af35_special_r_temp"); + + --bc; + // if (bc) { + emit("beq ", "6f"); + // f |= PV; + emit("adds ", "r_af, #PV"); + // } + emit("6:"); + emit_function_return(); + + reset_function_state(); + emit("cpX_common:"); + emit_save_lr_if_not_done_already(); // save this now so our stack doesn't get tangled + /** + t += 8; + byte cf = f & CF; + byte tempbyte = Read(hl++); + f = cpf8b[a*0x100 + tempbyte] + cf; + + NOTE + byte tempbyte = (i >> 8) - (i & 0xFF) - ((_sbcf[i] & HF) >> 4); + _cpf8b[i] = (_sbcf[i] & ~(F3|F5|PV|CF)) + (tempbyte & F3) + ((tempbyte << 4) & F5); + + if (--bc & 0xFFFF) f |= PV; //??? + memptr++; + */ + t += 8; + emit("push", "{r_af}"); + Read(hl); + emit("mov ", "r_temp, r0"); + emit_call_func("cp8"); + emit("lsrs ", "r0, r_af, #8"); + emit("subs ", "r0, r_temp"); + emit("lsrs ", "r1, r_af, #HF_INDEX+1"); + emit("bcc 6f"); + emit("subs ", "r0, #1"); + emit("6:"); + emit("mov ", "r_temp, r0"); + emit("preserve_only_flags", "r1, (SF|HF|NF|ZF)"); + emit_call_func("set_af35_special_r_temp"); + emit("pop ", "{r0}"); + emit("movs ", "r1, #CF"); + emit("ands ", "r0, r1"); + emit("orrs ", "r_af, r0"); + + --bc; + // if (bc) { + emit("beq ", "6f"); + // f |= PV; + emit("adds ", "r_af, #PV"); + // } + emit("6:"); + memptr++; + emit_function_return(); + } + } else if (!strcmp(prefix, "opli_r35_")) { + if (num == HELPER_START) { + // note that mem_l is a marker + Reg8Ref arg[] = { b, c, d, e, h, l, mem_l, a}; + +#ifndef USE_LARGER_FASTER_CB + // we start here as it is right at the end of opl_table and it has too be close + emit("opcbbitX_table:"); + for(int i=0;i*logic_ix_opcodes[opcode])(Read(ptr)); + // // select destination register for shift/res/set + // (this->*reg_offset[opcode & 7]) = v; // ??? + // Write(ptr, v); + // t += 11; + + emit_save_lr_if_not_done_already(); + emit("blx ", "r2"); + if (arg[i].reg != MEMPTR) { + emit_reg8_from_r_temp(arg[i]); + } + Write(memptr, v); + t += 11 + 4; // 4 from fetch earlier + emit_function_return(); + } + reset_function_state(); + emit("opddcb_bit:"); + // use a common bitm variant as it isn't dependent on which register + // e.g. + // t += 4; + // byte v = (this->*logic_ix_opcodes[opcode])(Read(ptr)); + // t += 8; + // select destination register for shift/res/set + // all the ddcb bit functions use memptr for r3,r5 and don't store the result + t += 8 + 4; // 4 from fetch earlier + temp8 __unused v = Read(memptr); // required because it is used in r_temp in the called function + emit("bx ", "r2"); + emit(""); + } + } else if (!strcmp(prefix, "op")) { + if (num == HELPER_START) { + // Define the interrupt function... simple enough to do here, so no need to hard code + emit(".thumb_func"); + reset_function_state(); + emit("z80a_interrupt:"); + temp16 __unused intad = 0x38; // this has side effects in generated code +// byte vector = 0xff; +// if(im >= 2) // im2 +// { +// word vec = vector + i*0x100; +// intad = Read(vec) + 0x100*Read(vec+1); +// t += 19; +// } else { +// t += 13; +// } + + // need lr push in both codepaths + emit_save_lr_if_not_done_already(); + im.emit_read_to_r0(); + emit("cmp ", "r0, #2"); + emit("blt ", "1f"); + i.emit_read_to_r0(); + emit("lsls ", "r0, #8"); + emit("movs ", "r1, #0xff"); + emit("orrs ", "r0, r1"); + intad = Read2(WordInR0()); + emit("adds ", "r_t, #6"); + emit("1:"); + emit("adds ", "r_t, #13"); + + push(pc); + pc = intad; + memptr = intad; + halted = 0; + iff1 = iff2 = 0; + r_low = ((WordInR0)r_low) + 1; + emit_function_return(); + } + } + } + + void OpeA0() { // ldi + emit_call_func("ldX_common"); + ++hl; + ++de; + } + + void OpeA1() { // cpi + emit_call_func("cpX_common"); + ++hl; + } + + void OpeA8() { // ldd + emit_call_func("ldX_common"); + --hl; + --de; + } + + void OpeA9() { // cpd + emit_call_func("cpX_common"); + --hl; + } + + DECLARE_REG16(PC, pc, pc_l, pc_h) + DECLARE_REG16(SP, sp, sp_l, sp_h) + rs_var(r_hi); + rs_var(iff1); + rs_var(iff2); + rs_var(halted); + rs_var(r_low); + rs_var(i); + rs_var(eipos); + rs_var(im); + /** + * r0 - + * r1 + * r2 - iy + * r3 - temp? + * r4 - pc + * r5 - sp + * r6 - t + * r7 - af + * r8 - bc + * r9 - de + * r10 - hl + * r11 - ix + * r12 - memptr + */ + DECLARE_REG16(MEMPTR, memptr, mem_l, mem_h) // undocumented register + DECLARE_REG16(IX, ix, xl, xh) + DECLARE_REG16(IY, iy, yl, yh) + + DECLARE_REG16(BC, bc, c, b) + DECLARE_REG16(DE, de, e, d) + DECLARE_REG16(HL, hl, l, h) + DECLARE_REG16(AF, af, f, a) + DECLARE_REG16(TEMPORARY, temporary, temp_lo, temp_hi) + DECLARE_REG16(TEMPORARY_RESTRICTED, restricted, restriced_lo, restricted_hi) + struct alt { + rs_var(bc); + rs_var(de); + rs_var(hl); + rs_var(af); + } alt; + rs_var(scratch); // another scratch register +}; + +}//namespace xZ80 + +#endif//__Z80_H__ diff --git a/options_common.cpp b/options_common.cpp index 6eaa9f8..baff71d 100755 --- a/options_common.cpp +++ b/options_common.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,13 +20,16 @@ along with this program. If not, see . #include "platform/platform.h" #include "platform/io.h" #include "tools/options.h" +#ifdef USE_UI #include "ui/ui.h" +#endif #include "options_common.h" #include "file_type.h" namespace xPlatform { +#ifndef NO_USE_SAVE static struct eOptionStoreSlot : public xOptions::eOptionInt { virtual const char* Name() const { return "save slot"; } @@ -113,7 +117,9 @@ static struct eOptionSaveFile : public eOptionSave } virtual int Order() const { return 8; } } op_save_file; +#endif +#ifndef NO_USE_SAVE static struct eOptionSaveState : public eOptionSave { virtual const char* Name() const { return "save state"; } @@ -141,33 +147,105 @@ static struct eOptionLoadState : public eOptionSave } virtual int Order() const { return 6; } } op_load_state; +#endif -static struct eOptionTape : public xOptions::eOptionInt +#ifndef NO_USE_TAPE +static struct eOptionTape : public xOptions::eOptionIntWithPending { eOptionTape() { storeable = false; } - virtual const char* Name() const { return "tape"; } - virtual const char** Values() const + virtual const char* Name() const override { +#ifndef USE_MU + return "tape"; +#else + return "Tape"; +#endif + } + enum { + V_NONE = 0, + V_STOPPED = 1, + V_PLAYING = 2, + V_STOP = 3, + V_PLAY = 4, + V_REWIND = 5 + }; + const char** Values() const override { - static const char* values[] = { "n/a", "stop", "start", NULL }; + static const char* values[] = { "none", "stopped", "playing", "stop", "play", "rewind", NULL }; return values; } - virtual void Change(bool next = true) - { - switch(Handler()->OnAction(A_TAPE_TOGGLE)) + static int translate(eActionResult result) { + switch(result) { - case AR_TAPE_NOT_INSERTED: Set(0); break; - case AR_TAPE_STOPPED: Set(1); break; - case AR_TAPE_STARTED: Set(2); break; - default: break; + case AR_TAPE_STOPPED: + return V_STOPPED; + case AR_TAPE_STARTED: + return V_PLAYING; + default: + return V_NONE; + } + } + + void Change(bool next = true) override { + int v = eOptionTape::translate(Handler()->OnAction(A_TAPE_QUERY)); + if (v == V_NONE) { + SetNow(V_NONE); + } else { + int cur; + if (*this == V_PLAY || *this == V_STOP) { + cur = 1; + } else if (*this == V_REWIND) { + cur = 2; + } else { + cur = 0; + } + cur += (next?1:2); + if (cur >= 3) cur -= 3; + if (cur == 0) { + Set(value); // set to the current value (i.e no longer pending) + } else if (cur == 1) { + Set(v == V_PLAYING ? V_STOP : V_PLAY); + } else { + Set(V_REWIND); + } + } + } + + void Complete(bool accept) override + { + if (accept && is_change_pending) { + SetNow(translate(Handler()->OnAction(A_TAPE_TOGGLE))); } + is_change_pending = false; + } + + int Order() const override { return 40; } + + bool Refresh() { + int v = eOptionTape::translate(Handler()->OnAction(A_TAPE_QUERY)); + if (v != value) { + // underlying state change compared to current (actual) value + SetNow(v); + return true; + } + return false; } - virtual int Order() const { return 40; } } op_tape; -static struct eOptionPause : public xOptions::eOptionBool +bool OpTapeRefresh() { + return op_tape.Refresh(); +} +#endif + +static struct eOptionPause : public xOptions::eOptionBoolYesNo { eOptionPause() { storeable = false; } - virtual const char* Name() const { return "pause"; } + virtual const char* Name() const { +#ifndef USE_MU + return "pause"; +#else + return "Paused"; +#endif + } virtual void Change(bool next = true) { eOptionBool::Change(); @@ -176,6 +254,28 @@ static struct eOptionPause : public xOptions::eOptionBool virtual int Order() const { return 70; } } op_pause; +#if 0 //ndef NO_USE_AY +static struct eOptionSound : public xOptions::eOptionInt +{ + eOptionSound() { + Set(S_AY); + } + virtual const char* Name() const { return "Sound"; } + virtual const char** Values() const + { + static const char* values[] = { "beeper", "ay", "tape", NULL }; + return values; + } + virtual void Change(bool next = true) + { + eOptionInt::Change(S_FIRST, S_LAST, next); + } + virtual int Order() const { return 20; } +} op_sound; +#endif + +#ifndef NO_USE_SOUND +#ifndef USE_MU static struct eOptionSound : public xOptions::eOptionInt { eOptionSound() { Set(S_AY); } @@ -191,11 +291,13 @@ static struct eOptionSound : public xOptions::eOptionInt } virtual int Order() const { return 20; } } op_sound; +eVolume OpVolume() { return (eVolume)(int)op_sound; } +void OpVolume(eVolume v) { op_sound.Set(v); } -static struct eOptionVolume : public xOptions::eOptionInt +#else +struct eOptionVolume : public xOptions::eOptionInt { - eOptionVolume() { Set(V_50); } - virtual const char* Name() const { return "volume"; } + eOptionVolume() { Set(V_70); } virtual const char** Values() const { static const char* values[] = { "mute", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%", NULL }; @@ -205,15 +307,45 @@ static struct eOptionVolume : public xOptions::eOptionInt { eOptionInt::Change(V_FIRST, V_LAST, next); } +}; + +static struct eOptionSoundVolume : public eOptionVolume +{ + eOptionSoundVolume() { +#ifndef NO_USE_AY + Set(V_50); +#else + Set(V_30); +#endif + } + virtual const char* Name() const { + return "Speaker volume"; + } virtual int Order() const { return 30; } -} op_volume; +} op_sound_volume; + +static struct eOptionTapeVolume : public eOptionVolume +{ + eOptionTapeVolume() { Set(V_30); } + virtual const char* Name() const { return "Tape volume"; } + virtual int Order() const { return 32; } +} op_tape_volume; +#endif -eVolume OpVolume() { return (eVolume)(int)op_volume; } -void OpVolume(eVolume v) { op_volume.Set(v); } +eVolume OpSoundVolume() { return (eVolume)(int)op_sound_volume; } +void OpSoundVolume(eVolume v) { op_sound_volume.Set(v); } +eVolume OpTapeVolume() { return (eVolume)(int)op_tape_volume; } +void OpTapeVolume(eVolume v) { op_tape_volume.Set(v); } +#endif + +#if 0 // ifndef NO_USE_AY eSound OpSound() { return (eSound)(int)op_sound; } void OpSound(eSound s) { op_sound.Set(s); } +#endif +#ifndef NO_USE_KEMPSTON +#ifndef USE_MU static struct eOptionJoy : public xOptions::eOptionInt { eOptionJoy() { Set(J_KEMPSTON); } @@ -229,7 +361,9 @@ static struct eOptionJoy : public xOptions::eOptionInt } virtual int Order() const { return 10; } } op_joy; - +#endif +#endif +#ifndef NO_USE_FDD static struct eOptionDrive : public xOptions::eOptionInt { eOptionDrive() { storeable = false; Set(D_A); } @@ -245,15 +379,43 @@ static struct eOptionDrive : public xOptions::eOptionInt } virtual int Order() const { return 60; } } op_drive; +#endif + +#ifdef USE_MU + static struct eOptionVSyncRate : public xOptions::eOptionInt + { + eOptionVSyncRate() { storeable = false; } + virtual const char* Name() const { return "VSync rate"; } + virtual const char** Values() const + { + static const char* values[] = { "50/60", "50", "60", "free", "wrong", NULL }; + return values; + } + virtual int Order() const { return 67; } + } op_vsync; + void OpVsyncRate(eVsyncRate vr) { op_vsync.Set(vr); } +#endif static struct eOptionReset : public xOptions::eOptionB { - eOptionReset() { storeable = false; } - virtual const char* Name() const { return "reset"; } + eOptionReset() { +#ifdef USE_MU + is_action = true; +#endif + storeable = false; + } + virtual const char* Name() const { +#ifndef USE_MU + return "reset"; +#else + return "Reset"; +#endif + } virtual void Change(bool next = true) { Handler()->OnAction(A_RESET); } virtual int Order() const { return 80; } } op_reset; +#ifndef USE_MU_SIMPLIFICATIONS static struct eOptionQuit : public xOptions::eOptionBool { eOptionQuit() { storeable = false; } @@ -261,7 +423,9 @@ static struct eOptionQuit : public xOptions::eOptionBool virtual int Order() const { return 100; } virtual const char** Values() const { return NULL; } } op_quit; +#endif +#ifndef USE_MU_SIMPLIFICATIONS static struct eOptionLastFile : public xOptions::eOptionString { eOptionLastFile() { customizable = false; } @@ -283,13 +447,20 @@ const char* OpLastFolder() return lf; } void OpLastFile(const char* name) { op_last_file.Set(name); } +#endif +#ifndef USE_MU_SIMPLIFICATIONS bool OpQuit() { return op_quit; } void OpQuit(bool v) { op_quit.Set(v); } +#endif +#ifndef NO_USE_FDD eDrive OpDrive() { return (eDrive)(int)op_drive; } void OpDrive(eDrive d) { op_drive.Set(d); } +#endif +#ifndef NO_USE_KEMPSTON +#ifndef USE_MU eJoystick OpJoystick() { return (eJoystick)(int)op_joy; } void OpJoystick(eJoystick v) { op_joy.Set(v); } dword OpJoyKeyFlags() @@ -303,6 +474,7 @@ dword OpJoyKeyFlags() } return KF_QAOP; } - +#endif +#endif } //namespace xPlatform diff --git a/options_common.h b/options_common.h index bcf487b..1fb4aa0 100755 --- a/options_common.h +++ b/options_common.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,27 +29,47 @@ enum eJoystick { J_FIRST, J_KEMPSTON = J_FIRST, J_CURSOR, J_QAOP, J_SINCLAIR2, J enum eSound { S_FIRST, S_BEEPER = S_FIRST, S_AY, S_TAPE, S_LAST }; enum eVolume { V_FIRST, V_MUTE = V_FIRST, V_10, V_20, V_30, V_40, V_50, V_60, V_70, V_80, V_90, V_100, V_LAST }; enum eDrive { D_FIRST, D_A = D_FIRST, D_B, D_C, D_D, D_LAST }; +enum eVsyncRate { VR_FIRST, VR_5060 = VR_FIRST , VR_50, VR_60, VR_FREE, VR_WRONG, VR_LAST }; const char* OpLastFolder(); const char* OpLastFile(); void OpLastFile(const char* path); +#ifndef USE_MU_SIMPLIFICATIONS bool OpQuit(); void OpQuit(bool v); +#endif +#ifndef NO_USE_FDD eDrive OpDrive(); void OpDrive(eDrive d); +#endif +#ifndef NO_USE_KEMPSTON +#ifndef USE_MU eJoystick OpJoystick(); void OpJoystick(eJoystick v); dword OpJoyKeyFlags(); +#endif +#endif +#ifndef USE_MU eVolume OpVolume(); void OpVolume(eVolume v); +#else +eVolume OpSoundVolume(); +void OpSoundVolume(eVolume v); +eVolume OpTapeVolume(); +void OpTapeVolume(eVolume v); +#ifndef NO_USE_TAPE +bool OpTapeRefresh(); +#endif +#endif eSound OpSound(); void OpSound(eSound s); +void OpVsyncRate(eVsyncRate vr); } //namespace xPlatform diff --git a/pico_extras_import.cmake b/pico_extras_import.cmake new file mode 100644 index 0000000..1f07720 --- /dev/null +++ b/pico_extras_import.cmake @@ -0,0 +1,57 @@ +if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) + set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) + message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) + set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PICO_EXTRAS_PATH) + if (PICO_EXTRAS_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + PICO_EXTRAS + GIT_REPOSITORY git@asic-git.pitowers.org:projectmu/pico_extras.git + GIT_TAG master + ) + if (NOT PICO_EXTRAS) + message("Downloading PICO EXTRAS") + FetchContent_Populate(PICO_EXTRAS) + set(PICO_EXTRAS_PATH ${PICO_EXTRAS_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") + set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) + message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") + else() + message(FATAL_ERROR + "PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git." + ) + endif() + endif () +endif () + +set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") +set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") +set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") + +get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_EXTRAS_PATH}) + message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") +endif () + +set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) + +add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) \ No newline at end of file diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..2548382 --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,64 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# todo document + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY git@asic-git.pitowers.org:projectmu/pico_sdk.git + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading PICO SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/platform/custom_ui/ui_file_open.cpp b/platform/custom_ui/ui_file_open.cpp index b4e2580..2d55cd0 100755 --- a/platform/custom_ui/ui_file_open.cpp +++ b/platform/custom_ui/ui_file_open.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,6 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#ifdef USE_UI #include "../../std.h" #include "ui_file_open.h" #include "../../ui/ui_list.h" @@ -27,7 +29,7 @@ along with this program. If not, see . #include "../../file_type.h" #include -#ifdef USE_UI +#ifdef USE_FILE_OPEN namespace xUi { @@ -188,4 +190,5 @@ void eFileOpenDialog::OnNotify(byte n, byte from) } //namespace xUi +#endif #endif//USE_UI diff --git a/platform/custom_ui/ui_keyboard.cpp b/platform/custom_ui/ui_keyboard.cpp index 9b718f7..e3bdae5 100755 --- a/platform/custom_ui/ui_keyboard.cpp +++ b/platform/custom_ui/ui_keyboard.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,12 +17,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#ifdef USE_UI #include "../platform.h" #include "ui_keyboard.h" #include "../../ui/ui_button.h" -#ifdef USE_UI - namespace xUi { diff --git a/platform/io.cpp b/platform/io.cpp index e431d72..dbf55c8 100644 --- a/platform/io.cpp +++ b/platform/io.cpp @@ -62,24 +62,24 @@ bool MkDir(const char* path); // platform-specific //============================================================================= // PathCreate //----------------------------------------------------------------------------- -bool PathCreate(const char* path) -{ - char buf[MAX_PATH_LEN]; - int p = 0; - int l = strlen(path); - for(int i = 0; i < l; ++i) - { - if(path[i] == '\\' || path[i] == '/') - { - strncpy(buf + p, path + p, i - p); - buf[i] = 0; - p = i; - if(i > 0 && !MkDir(buf)) - return false; - } - } - return MkDir(path); -} +bool PathCreate(const char* path) +{ + char buf[MAX_PATH_LEN]; + int p = 0; + int l = strlen(path); + for(int i = 0; i < l; ++i) + { + if(path[i] == '\\' || path[i] == '/') + { + strncpy(buf + p, path + p, i - p); + buf[i] = 0; + p = i; + if(i > 0 && !MkDir(buf)) + return false; + } + } + return MkDir(path); +} //============================================================================= // GetPathParent //----------------------------------------------------------------------------- diff --git a/platform/io.h b/platform/io.h index 398e448..eade702 100644 --- a/platform/io.h +++ b/platform/io.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +25,13 @@ along with this program. If not, see . namespace xIo { -enum { MAX_PATH_LEN = 1024 }; +enum { +#ifndef USE_MU + MAX_PATH_LEN = 1024 +#else + MAX_PATH_LEN = 64 +#endif +}; void SetResourcePath(const char* resource_path); const char* ResourcePath(const char* path); @@ -32,7 +39,7 @@ const char* ResourcePath(const char* path); void SetProfilePath(const char* profile_path); const char* ProfilePath(const char* path); -bool PathCreate(const char* path); +bool PathCreate(const char* path); void GetPathParent(char* parent, const char* path); } diff --git a/platform/platform.cpp b/platform/platform.cpp index 2ef483a..ae765d2 100644 --- a/platform/platform.cpp +++ b/platform/platform.cpp @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2010 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,12 +29,15 @@ eHandler::eHandler() assert(!handler); handler = this; } +#ifndef NO_USE_DESTRUCTORS eHandler::~eHandler() { handler = NULL; } +#endif eHandler* Handler() { return handler; } +#ifndef USE_MU void GetScaleWithAspectRatio43(float* sx, float* sy, int _w, int _h) { *sx = 1.0f; @@ -45,6 +49,6 @@ void GetScaleWithAspectRatio43(float* sx, float* sy, int _w, int _h) else *sy = a/a43; } - +#endif } //namespace xPlatform diff --git a/platform/platform.h b/platform/platform.h index 4d421fb..71b9713 100644 --- a/platform/platform.h +++ b/platform/platform.h @@ -1,6 +1,7 @@ /* Portable ZX-Spectrum emulator. Copyright (C) 2001-2015 SMT, Dexus, Alone Coder, deathsoft, djdron, scor +Copyright (C) 2023 Graham Sanderson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -73,7 +74,13 @@ enum eKeyFlags enum eMouseAction { MA_MOVE, MA_BUTTON, MA_WHEEL }; enum eAction { - A_RESET, A_TAPE_TOGGLE, A_TAPE_QUERY, A_DISK_QUERY + A_RESET, +#ifndef NO_USE_TAPE + A_TAPE_TOGGLE, A_TAPE_QUERY, A_TAPE_REWIND +#endif +#ifndef NO_USE_FDD + A_DISK_QUERY +#endif }; enum eActionResult { @@ -86,26 +93,46 @@ enum eActionResult struct eHandler { eHandler(); +#ifndef NO_USE_DESTRUCTORS ~eHandler(); +#endif virtual void OnInit() = 0; virtual void OnDone() = 0; virtual const char* OnLoop() = 0; // error desc if not NULL +#ifndef USE_MU_SIMPLIFICATIONS virtual const char* WindowCaption() const = 0; +#endif virtual void OnKey(char key, dword flags) = 0; +#ifndef NO_USE_KEMPSTON +#ifndef USE_MU virtual void OnMouse(eMouseAction action, byte a, byte b) = 0; - +#endif +#endif virtual bool OnOpenFile(const char* name, const void* data = NULL, size_t data_size = 0) = 0; +#ifdef USE_STREAM + virtual bool OnOpenFileCompressed(const char* name, const void* comressed_data, size_t compressed_data_size, size_t data_size) = 0; +#endif +#ifndef NO_USE_SAVE virtual bool OnSaveFile(const char* name) = 0; +#endif virtual bool FileTypeSupported(const char* name) const = 0; virtual eActionResult OnAction(eAction action) = 0; +#ifndef NO_USE_REPLAY virtual bool GetReplayProgress(dword* frame_current, dword* frames_total, dword* frames_cached) = 0; +#endif // data to draw +#ifndef NO_USE_SCREEN virtual void* VideoData() const = 0; +#endif +#ifndef NO_USE_UI virtual void* VideoDataUI() const = 0; +#endif // pause/resume function for sync video by audio virtual void VideoPaused(bool paused) = 0; + virtual bool IsVideoPaused() = 0; +#ifndef USE_MU_SIMPLIFICATIONS virtual int VideoFrame() const = 0; // audio virtual int AudioSources() const = 0; @@ -113,7 +140,7 @@ struct eHandler virtual dword AudioDataReady(int source) const = 0; virtual void AudioDataUse(int source, dword size) = 0; virtual void AudioSetSampleRate(dword sample_rate) = 0; - +#endif virtual bool FullSpeed() const = 0; virtual eSpeccy* Speccy() const = 0; diff --git a/res/3D_Pacman_1983_Freddy_Kristiansen.z80 b/res/3D_Pacman_1983_Freddy_Kristiansen.z80 new file mode 100644 index 0000000000000000000000000000000000000000..3d60eeac60f71b283be0dc29b37539412f21a2b3 GIT binary patch literal 14421 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 literal 0 HcmV?d00001 diff --git a/res/CHRON128.SCL b/res/CHRON128.SCL new file mode 100644 index 0000000000000000000000000000000000000000..8f65c469b67ca2c7deee12a383bb8b8e0cdb7f8d GIT binary patch literal 63003 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 Ol&1VXG}`%YG5CMvWv8zI literal 0 HcmV?d00001 diff --git a/res/CHRON128.sna b/res/CHRON128.sna new file mode 100644 index 0000000000000000000000000000000000000000..4babf2c06db79e6fb7b2889917da2115e65ca4f9 GIT binary patch literal 49179 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 literal 0 HcmV?d00001 diff --git a/res/DOOMDE_1.TAP b/res/DOOMDE_1.TAP new file mode 100644 index 0000000000000000000000000000000000000000..9e308bc6d7658caed3b20ea69b071510b2dca8f8 GIT binary patch literal 10475 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 YJ7F!$nO=mMENA*YuJnJV?Nasse{qXoKmY&$ literal 0 HcmV?d00001 diff --git a/res/Donkey_Kong_1986_Ocean.z80 b/res/Donkey_Kong_1986_Ocean.z80 new file mode 100644 index 0000000000000000000000000000000000000000..eb86d7b4b0d4002c6ce33a2478317bee1e03ac01 GIT binary patch literal 39025 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 Y<1lAyiZ%rp2E$A#)CWfnjg8U&0zx(3F#rGn literal 0 HcmV?d00001 diff --git a/res/Hobbit_The_v1.2_1982_Melbourne_House_a.z80 b/res/Hobbit_The_v1.2_1982_Melbourne_House_a.z80 new file mode 100644 index 0000000000000000000000000000000000000000..99549a520c9def782d12c8a7b86f858b69c6bc46 GIT binary patch literal 45550 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 literal 0 HcmV?d00001 diff --git a/res/JETSET.TAP b/res/JETSET.TAP new file mode 100644 index 0000000000000000000000000000000000000000..dbd99fb3b4c001ac0de26afcfc7931b5292a9069 GIT binary patch literal 33154 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 C(xo~8 literal 0 HcmV?d00001 diff --git a/res/Manic_Miner_1983_Software_Projects.sna b/res/Manic_Miner_1983_Software_Projects.sna new file mode 100644 index 0000000000000000000000000000000000000000..307d78d0517e7012f45dde8c1c59cbd937d139ed GIT binary patch literal 131103 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 literal 0 HcmV?d00001 diff --git a/res/ROBOCOP3.TAP b/res/ROBOCOP3.TAP new file mode 100644 index 0000000000000000000000000000000000000000..d9236be01b9afd67b3a620ad610ab5894f5b0b6f GIT binary patch literal 114022 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 CX4QZI literal 0 HcmV?d00001 diff --git a/res/The Sentinel.tzx b/res/The Sentinel.tzx new file mode 100644 index 0000000000000000000000000000000000000000..10454d9c628f62fad2e0e673f65d629cb6213dab GIT binary patch literal 48303 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®H|cw%s~!s zP$K3Iu8HIDDcDnQwV~dyCgmC-nlk|qCk%!`W2(x@Q#s+X4VptgKu(FY#P(xn>f#uj zGLu~?-o{^|g7rATgKUY|dvHw(9l;xG01kIBAYk0&h!qFJ9KnOB<0TORv#|VNN2-F} z;~u{&_&PHmewEnw3cf|`XO6EE`rIWBGv13DHubxr)?x%wXnCPhlImm+|PAd1w|6A2sp-PA!Z`BDLZy7!m~ z+To~O@tW&3=YIIr&R$w%$CnmW+Hs_dDRxp{e@d;?E?4^2cQs|T6vFM;QqR41<&vG0 zjnY#~fv>;aYUxhsg33neUdlQt1g@1b@lv~F2&8-~J@pI$t%abq2SzXWEF-=ajfSb$j0KmPnlWOJ_>Q7kWAU}7b>y??I>Y!{YH`NJrQn+H z_y$)M*C3=M<60bgO(6^}fhB&~9Y519P?ZCn2b*J|%?yHnrLM{FD$amOt_g#$d1>EJ z+XO3(d2Pu+v*5$?>V(oDz7(Bm1hWKXtYYBogy>WQxNyu^wHdGwc0T^}>%%iE2AgJ( zROR7=t(N&%im;I~4Q3G3Y1nYtFuhuK==^oMhm1s!VVN=w3K`3SGygUNg(b*nbb`!` z2xIjbK-Tq$(0eX6f_ST2iD8`xUYj z%CO53nJyz`?!qoYR8JXcWEV+$7dBnCp5!hg?b+oWKys3K&n6LN*8}_mL+iUv6{f|; zhLXyl@wVhgn3G;AgAn@P$?0V>OPnQc!+2gxpF`))tR2q{#1;^Mu+ubk-+|wS zPMpzyv+zbXa%rZ|tXrwu$%0HqSti*j(}zVCW?sXrgMa0Fm`64?B%Wm8Gs)gY3|uBz z!zkJN!mUNO`WHeQCMfe8Onl~s!`^L}mUN41ti^bcG4bO0OI~4mx7L7D7Zg>8dBm9W zd?NQe7wfLaXvsVrKyJ!Bb=Vl&70yPo;47m7#lxd;W&fIihCijHYmH(GuwVypO&7?+>A@ahq8cYUkgW$>rbMGmbX)t8rHnfdGOYRlps7X$RHV3QX) z=YqY1n0$zYk884z4dZ>3!xqv~nL)IuROVI?h_Lf+Dm#_+V;hx>_vy%Hpg zwX)FFtG3XbtRBYk;&|pu#-kx}orgcuK3}3L0`+Zx^KbhOWM|^&SL%Q}CjGjr zIsrnM3c}8iz-?&R7vf%$PQav7#`XMk0{VkqgxIO$_Cmx?^@h;=^rf!tghDtc zc|!!U4o1+vfx)cX!ED3|)i70{D;7Z` zIpYures-|`gg@kVFqg0$1R?Cq{Ak~qIPMgrE%W1qMBKs`rY&>x4+)2hz*`~QTS0h( zcrP}a&*!uGTs9xk*jz5MKqNH4J6AP=f_+)d!P?4@mn8@11tz72(E>0zw)BRgOc=f*=Y8HONBP_{g-<%R0b1 zip#j_@iGlA2VA_;#INt-WhN1wSRdE44IMtQY2Tf_4Fcqn1GTP^*5Ylhk*<;5EG}Nw zACZse_D7UhC!U=JE)(W6xSz`LBmA}(vQssc9eCXNaQ%+_`!Py_e4}3S<2m#5(sb-> zqU`L7Z5)I9ShX?djONpVnGtd(L(Ytq)AX47DmdRs-9qZ+l7QN-;AegEz0d=d8u?Ddks-fSgBChw z^+lD!iM0~VrPRphJ48?d<@3=a*S}IjmS`>vfRI`QQi~V^<;V3e4xt=_Peqk7X>ylR zrT~^d#VTcbA`3BGz>LgDL_#AOR%E`GGvCOsRHG>Ma`!&R6#3$7BtY2#f)RH9@i#V; zuLk3C9K<9kxpoe9 z?Vj2NDlKQ;>D|E5l$w&Gs&NPjXK;|Jj8N)O4lGIagqUjJD545;RNY)~;KP($1W)Es z`*UoAk*->D99nZ$IRa`_jsq!2o59B44)zMn;c}vQhzO(x=Dc;_k@V9Pap(gNBPmdGx5w4mqioYF zl@hRbpcM*Ra!sxiEB8W)d8HPkC6`>A%OK}Eg}oP)FetfBFA@0~u63bw&vh+fBn9{CvcqHMyIe-0Z0`$q#o=8E>wL$`8t;j3ns(FTqKu;fjSu$qQG@fE*VyN zo%@Sn*i4Twry!My!}SP@@v{h@`3nd`*y;H+I{_@pX#vB^r99iJmgifMO=D%d_-D5+ z9CV4z?xICxd%tw41Jy5Sl@B-W#YS4yhMSp!+r41R^Iovm z-`5zFSS^g883TLQYCHQtD@M5U(c6L)A{7C_U^o>TpC8EaLUL#i?sQ_Cy>Ja@FA>cIq4c+oMwMLPix zVdu)H*~#*3%lg74gjyBDG8F@1_^o&#JVmYX>qGfjqKq#;vLF=6_w|;_J@e)DVVq)t zK!6gMi_KHaMf+sQ=ZpRF<@-iURWzF;63L5FT*dw_XOhA_ zo8^AQEOw}Kx;(JRxn)=2*FF&!j8V?X@<4NXD9n>*|wu-UWILx5nHnk)()A6<@e@fh+JddFeZp ze>f>3Hqk%Ats8LjFLkO1z@OKr8T<1|VUhexFTY1ZNLM4ie=^wAy47-|RWaDCXs!3I zczG#kNn{Ag)y*U~?v>EE99;SXL`GZ+U2>9*e}B@KW^}TE7UjBMcxDTLxlMDq`4Gu{ z1+#~^nvh(*4Rd3$C*6H7ye@I8c)6f7IKnlE6mseQcQ?NW0we!m1e}kAy3bqP!sZO13Yzm+O{#GZ#&b+DY!1q}b?xOMOQ2KD1*Qp&R5GPUiXq=b7tX_2b>x^&8+L?37Mr zXY!1$ZvJTO<>6;d>igf>I33S_;x@in!#`##dB$H65!d&`lO7jwg7?G|eeC5EQfwqz zU~+<-QL^)RJo=V(lnn$O){BGa{Ya!<#1l8@+-u_+Nlwfk6(!SSt)o1XNq^crxI*rT z{G~SMd1RTHm5oho)bSW}yfr!|SZI!pK8`~lHIISe$KR0`fskBkX>HaLdh8SDC*l5o z+I%V8DO3vGg})1)3Kj`+1#binf?&a)QBFqrdMEzW2gd~@z8g6$|4D>v{4*w3sIO*q)!3Y8hS&=ns5^|6429XFmu2b2Wd>4A0fj(j5n0(dd zG3_x8n_6(!81*z0MNNw$rWRmt6%bRS9@F$=$wIftBR^tOquc~{SI))i6+B82K6=^* zLs+|nV42^B8EXcAz)xm#F70{VoB11 zcP~yXx1+elLEzH}4Sm7GNLUQoae0qjAGzQ^5<4J0hCXz_K@~{N3H+%>G9blor1@1Za-qB+n+kZS0}KO zgZsc;+g#!pRF9Rb!@#ClZc%$c6vECwKEaqgqobS4(1~T}Cxz(tLfgl`I3$#ztIDW1 z3T<+j@irNh7TfY2R&KySS$Ib04?M6n{MPo@_0VR!WSi~HdCp-l+&R*=e1Yw2b%(PM zo|rve;u}%4Z3D_y=L@T+a@(XI zF;P~!>JCZBV}L0x6{2~C)OOfBB^pN!DYW512tO1j(3X|Q7^g( z?Oxxt>jDru?cvNX9ZlyVLc`8=TclNA9KEEy5p@ z>xitb09-umzS<*RCkh+o5e;=6p^e&19f6JWOr0nj(hQwQo2D6mP=u}c$G5bA5Md|% zBX%Z^${xRF#zeC%E21q6@to-BaHk~89pSBV%iUv2OV5aOlK+3c2R>_f=K7!*&jB9C zj?of;&9Rx5Fr5=yTK~n*vL7t+ zEHCPR^259**XBJ5*p4imHH$n$idNgdD|OZ?^0YqwEHLGn^S5_I_I0H&uEinNQxoy3 zTtWP2u34U1Mc>-D6+O}2?tFO1$)hxThn;Zg5DPK#KOB8MJYN<)bMSmo^eouUa|fI~ ze}$9tuSIt@YrJXX2Sfiz^VnrVSKkxV$aIr5C!i7t)p#MEv-8-CAgTP1Dbh$i}4= zy6h@^T(Z7=X}(K-;ZuoUlrb$X?p))!wm0y zX~<&(lw%*xxzI5V*HDP-D5DJ(4$xQy;%wbN{K!uo+*06!wb>8i5O(BK*_nJb_~=Ol zz%-Pb9HB>Hlu(eO?-BHN{TT%CiB7H6jQ7>OjOd!EOsBqThOj28iP2mAlCdkFmC0zW z?v0Kir7*_I7au4CCU_@SwaQ_V@I1+hU(KKwuri(Os~L|FiK~8acsY!13QTA8RnI^J z818aF8<0})yPU?#{kP%!Ar@Z_fsC~BdwBSx?@dyME>eX62oJ03WJ`Y!gs_)1SciX>2)dGTxQ5&|L7+f;0|^fe9^HK_{)gPXWqds zpUJuebFps3kBkisjt=+q5xQY(1b^5-us9(u1ePZB!Peq_CvcIbkZ$OIchj%ZdQ*7x zM@po_Zm1b3m*#qe9C&?)U`M?E>N4GCFE;-b@gTh93bLa!b>EqMy)y(F?QvE(6lTB6 zFAg|>o-T8^1RUCceUig_fW84pH$l6Wem=bIa<~EX>uv&l1^p(_HBNTe4pNN|A0jzj za(Dp}6BEC&JS0jCJ!Bs`E(bynNe)fFPz^XGRygfL0?aHetI!_%M7&pGolvupVW0SO zJE^?FJCUuMl&CGdnvpo-q0yt8rZGpJX?&nZztK2(uW|C6YTc*W)oC9*qQo~|ffZI6 zkG(7^+?YIjjDtmm*L0cN-3-_=`V!cJhh5unK~iF3(us$dIS>11d!1fOf4$CL^m71`)RAQ54QIF+5ra~8MKT86wp=&QsfIwscOo;dbaI+j*4 zIhA*CI7i$g$6*w+8O7*ELDgeBbD<`~eYe!;E`9SoLZr<>T#}>B0msJ&nCFr8-k9Cc9Op0~;EbAGItciOc4eU9wZ(mYk(w<))Bpp1+Nwhu=I0?99_>eh?kF39R zG_E`hk`Q)oOl4>CorKj0z@)h5RXs5C;WcBXnbnKT-X#0`h<@Xk`I-3{(rT(W`sa1~ zN~#h?kx}<`zqETU1Ve2PDvZ*MwizulnrjsOwJZ8rwCIYN;eJD_AHfN{QI!u79B_5# z`}rNHbMdYVfzHkICmtNdF9dVklaJ^;5Y2IC*|~ckh<|zopYr1z?>Jb|YGgmm zbLD~6=>97Yu2_jwvk#nKFJAl_BXUisvT8-)W2;<4Ug1Mt9T3__ToXbalL*Zi(eFTn zu%npD&g3)e&KvOjB#IDReo&0#Ul+Rtmt#fHhD}BsN#G#-sUmA8)SvuqCB#=B48a}6 zCL*vHI3WgqXoyA(syNKkKX&il-Q~TaAX*Q4y+tEk(Md7fh!>3TBkgp#SL2Iz@AzuR zzwZC>$im=q3^E_TvjO~WeBc(l;Q!w|Ey;rI4G3t8peDkz%P>T0T2#Sid{R@2Y@Gn3Z zM4i6xHi)_t|J?gxwZn-6018I}{OtpTonJo9&Vd?({fAxx`;MArhl+uH4iv4x-nM2L zEgZeq`JJtEgYC1IfG-yNiN!$!_D0Y@4D1_hm%*TW$v@qjuVsG@+B(6#%i0ilPpS!L zf8jme?nn`Oub*B`T(XB^`*Q5M1MkM~O!3N!a75rh%%Ql8BR z5%c#Yq`b4nS=7QZ>uqtPU(R~A-u7)NK-E$JNyZWee+m5@ZWCyv_GP8Q zzlfIXS)C?H-kvHM-K7ooYiqAYYin;;q-gKTAJf*%s@2|`k*BTwCy*fQu%@yzdE80Q z+`D&QS}GhvvskX?vvX=w6$)-vN_T6ZVuujQH-U0csz|jPOUuh%FU!`5PSuE!Xw+-eHx}$z zU7l0-y(Bd=Yq)NmhGM^uLSi>2MKE<5F|76ZsfWr{aJ$toMY%?O!KQJnM*abz!gso) zJavmzUOCjD=-y%_d!?Z;^U>g^YH;oPQX%}89g0-#e2KOsUDCIAi&Z299RsNdJ5E#C zncR2o4x>7DYAHpwieI&cD>}5ahBde0wt4?nl%}mr+mxP`zU{8kVn$I~__(TtD!Lev z9+0-}ZueTPZBb;POv4nVW74(C$f?}l`=_A za?&!*3|VzocHwVzvtKR^1IusDNgO+T_^?Yx+DnnU7Um&3AY2s<@X*@0*DiEsPT z)7v!DM}A(awV_IVWYjC0Dt9huXrmU({~|h2MiTS^Cy{^*UJigU9vXfq9oe{Y{m=4Q%Djvl^dT zCe)1^a^MvS^;UY?qhlb_$j z%|TC?q3GYUZsttFhxG&wlL7zr;J{|=K|Fh~8HZE@%4zF=*4(u_X{hpLx=fnUh~pf- zGJS*eBbku_zoS8h^TcqU7#SI;@iBX1z`ux5jf{*#_6@~3l1%A;k{?QbkRMw4Am6^t z&CSfr$RHOKq^3%xFyW!gmv0;g83;R$Q}><8%j<$oQ!69+3^q-AD&kcX#l_(kitJ-a{1+wb1x<;i4H5-C4l zBFV@&eE7TOYM#{tbq}dY<^iDJ|Q6AP;mG2$P6yBZNKesrQcpDUImrnwQ&0O%9ls>A3Cd}bM{coetBw%EJM0E+`qF&dbVvyD%FJQd#Jig zrKvpszCP*M6F`cv^YL|q6W8xRB0lSJ5@aImJp1&%Bm2yUE|C2fcCtS6aSCK1>{w0R zcP7v1@L=;edrd>F$2(BxxwCf`Zqf9B&H z{JlxYj@i_6)+9RxpZPEX1^>nlJnl>!l?y-fVGIfpc0Ru5bmEL&^qG(Ipy*TVOzb!&pJ9Xcgd>8uaGar}0t531>;p(P}&wE@36$m?LKiYRDKK-iv%*Pc_`6+fL_8s`m z*k?V=LDj!KvnD^mXe->iow#vvURe||${&?)YONm4AKBIl?Y~r( z!G(My{E4({BQKg2YRgC7Db&^_kJP*y(Z;;29#*PJi<;}yej0gKr-rOAsb%DhXjY9> zZ@;;9!6Eh4!_9UZmEX@;*R`i`q~tCl(?)hIXjEU2r(U!!?C!jEPR*y)7uREk=Yx!& ztKn!K)vnaX$k$0$p}-2*z=G-@AWFGz#=5ix1KQJ@o7L*xHLI<<51OxnYJ{Et@Y~#t zsQT3vZH*|6hw4^PtPrR-1YwX12X?)hRlA^eR@H(kyV|bWv6`L!HjCbZ@R5?B+Zyk| zl7^F@zU?GfUAoc=im$2QYYa4=hQ`xiwFxwy220e(bU|M6PDN$u`_rJmMx9inPOnuT zQ6DM$ngvRg8cAS@qGC^Ne$7v}w3QmBU`b6)%|?@rHJeN})vSjV^J;h2u6fl0-D5Qy z^WZfie=ml|G}XsOwG}3%UoQlu>LWHA7q2h_>i#uf525y~(;lm;8m`fNKc=}3U1Rl4 zweC073##p)clD^ocVNs!TT+`|&b73UuRc#9VnzF`gZ|UMu)4hvpYsa+5 zRKuCXAUgQEFvq-In7%|BiR?V)=E8Jll8NJfq9=0X;9Hj1Gam6bgy#HSYH& zksX;X&M*-1QfThXF$RfBrIE+oW6mTevX?XBBhn~i|MVdcXC{^DO{^PxTkp=qJCBXE z*E`e5-c(2|!p3x_c$`Uxj zI0*rKmLO2V=0`*e_&kY_!{e}{Ie~~14t9({$QMXL`3b`45O&zdZiz4?g3V)v2qYne zl20MRIN_1PXnw?h$`BpQfjJ|hLn1gl2`i2j5-pS@@MB=*Xa^h>C$Xd8T-_xeRGhu4 zoqtX&3C58iad)tDCCV9Y9ujvq3D?bDf_I}x9B|GgiK8oYI#MNAH&+^tLUnX=&HX!p zpG0yEC=hloeZc!l_;XO-%~8XjU{sO9Qkipt}Llprffy2{+YHC8%qHTcZ-@ zLJ~CEyAm)|1PpJ@FrpEbXofXls%DrTPK~!;ziEAk_+eqBE}S3&-XONIY5U;l`wok? zw{Kcht-(|)flp`AuV_uw$ffY@{%Kq_9ipHx2o;k+hkcijMJ7gH5$|Uy);BG|f7atBP$KO7m9Ik#u| zpF5Wa^(<$@TV~Jl&i+mHOSg14A-;y>(YHQlHsvjahzY%DX~(9_rCSzv54I{M!z-l^ z1A3M#V|tdiY+^$MYv~r`DDXcHq>sj-G9(dF8x4RyCGs1Xhb@@4H^-4mP|eFz%-uRYOANs)~Vzcu*XJ zs!E22WK?-mB}}}&50p1ofJRtgY2%jfz|w{-r@+$YEw=DKW@_qaE>@y`c!g}R6l|mi zwMV)Eg}C~gi-$hAq=-xYw@cDoT=c#!#=oT5LYsE?V&GQx@;@Vxkm~c z+Rz6S=9`;P@OB3@A?*A*m7U4==v^DeKPmo@41!!>>B2cu3CZPGZ^%Z3Xn^k%@&^dFjZ zQei~=Qsi9ne?P21Gs4cwkJy z_5bHs%AwNNtomV z)a09AQEGn3Amd6Nf7_#f6}xI}+Ul{&&PCnr8*XgB@-E=`YyS`JHb`;+ literal 0 HcmV?d00001 diff --git a/res/aticatac.tap b/res/aticatac.tap new file mode 100644 index 0000000000000000000000000000000000000000..f94e2dd5348b8b7e073be65ba039d60d11306901 GIT binary patch literal 37370 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 literal 0 HcmV?d00001 diff --git a/res/aticatac.tzx b/res/aticatac.tzx new file mode 100644 index 0000000000000000000000000000000000000000..7f88d9ecaea2f1f38295c1eaae7e6ac3e4002fb3 GIT binary patch literal 37726 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 literal 0 HcmV?d00001 diff --git a/res/ay2.z80 b/res/ay2.z80 new file mode 100644 index 0000000000000000000000000000000000000000..fab6c9ec74a6230ea7d67910431c22a693ca3930 GIT binary patch literal 41936 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 a%u$+V8Fb-U^AjMP!D5JEn0uD_j literal 0 HcmV?d00001 diff --git a/res/bitbang.tap b/res/bitbang.tap new file mode 100644 index 0000000000000000000000000000000000000000..0a7ea71776493c348c874a499abefd6ae099bf21 GIT binary patch literal 568 zcmWe;U|>kfEJ;etOIJ`(kZ0mzkY|clWcts*CCczIAT!S?u{c%1R>8>Fl#hYoH3I`L zgNO!L!Zj~3DJK;w4O6JCU~FV=W&u>n(7>R;%OJ-EWltQC|(^3&6E zGJydI@{od(HPAe3B_Ms%1gL}ssN@b*ll37(6JwxQKgje7ISf~TLKDgUCU%BTP~Y{C pZ8gl9$W~9oWi?bWFT(~ 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 literal 0 HcmV?d00001 diff --git a/res/dandare1.tap b/res/dandare1.tap new file mode 100644 index 0000000000000000000000000000000000000000..b85a184b375177b50ed5b51625ae0148053654cd GIT binary patch literal 47114 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

NR2^s*Xz<+EI&UQXE)S6WewH-T~_rv(@w%kIW|` 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 M5CQN5|9}8M)0Y>MF8}}l literal 0 HcmV?d00001 diff --git a/res/foo.tap b/res/foo.tap new file mode 100644 index 0000000000000000000000000000000000000000..e3df29eb63a73afe3d02fa46547066f130002a90 GIT binary patch literal 45846 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 literal 0 HcmV?d00001 diff --git a/res/g+g.sna b/res/g+g.sna new file mode 100644 index 0000000000000000000000000000000000000000..580af8e72d63a0ef378afbcefe9ed39b2532876d GIT binary patch literal 49179 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

wUQa$*qd1Vh_?^K|)eP7> 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

y(8K` zvx~nTZSd1?KpoAsNuRG1F5nyKkF|~ek+#wP*?#&SOLIhgrsBJ+=?|UXfTtM!bW>jb zO?WoG&IW83--OYB6*Jqhz_=)O;(bL38~7>-X>f E19*&0o&W#< literal 0 HcmV?d00001 diff --git a/res/g+g.tap b/res/g+g.tap new file mode 100644 index 0000000000000000000000000000000000000000..7b2b0269459e7f5c7b7de9358aa7f10f36868491 GIT binary patch literal 49574 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 literal 0 HcmV?d00001 diff --git a/res/g+g.tzx b/res/g+g.tzx new file mode 100644 index 0000000000000000000000000000000000000000..a95125721778b4ac70966aa74751dd5ce5f4c09a GIT binary patch literal 49599 zcma&NXIN7~+bv8DU6Ce5dXwHu=qMc&ks_go-V{R3PDmh>9YU2(P_Q9gPy{3(z1syv zQA8{h6$Dg}P|uFfbIzal{jTe~F*AG5nsTqX*A$|9xVs>ugCvFN8M&7kdFklrtZf{f zU7RH#3#4PG3#1c+%7f_+>DbxmX7rBG(Xr6canbQ{(d}#MsH>|(1$9WMK#ZU6kEWKc z4jd|o@S;vo7eeg>C z5A^f`_{r!fM+dtrD9b9rrOv;J``Hk+@gbg0@aW@XclrTwM_xge4|uve{U4-7SYfTI z^iXdvOdfYW=7-&#%sxoE6N0%fF`RTjPwV%EiGZ=hel63uNiyUUUHziO$=`sQBOTo$ z%NFa@f!>7V+XMO)I{eRuL}?E6J&CQ@MCYie0()mHIpV&vy>s?EGe^h7?1t~z4g8Z9 zH#06gy)g8meAD%1`M9fjv1?`T%|1l&ZNLTP((aJuRky$&!sS}wl-<|dV7t zS&1JSJX>!gyII1g+7~FTw-R49u!JX)E|BisY!Xawg!;r3BYBX0O~UAtuG1_}UkGJ| zPrKAX{awhuycIzk1ylU6Eb9VSvw!hx$ioIkhpbe@RK|kC9j}{!OXb82j^9;s{{UxX zRMBkZ`1s@Fj|~mS=nq+0jUZx(2kA1aDl?sGF5|V6-_nzOHyiAwCemSsKP;4J@GX z{hTydPyoOJXT}rACj}1=vlvPbW@cUQMV(0? zqRB~rcC80HdbgxBpY`_0B4MMWRJbGy02qo(8`AnW6j9PLg>p;i&Ct>uzcXgY48Om^ zeJ(s)n!NVISwcaLu+{@h67K`8Kz1s79_5ClnysFXZHB8&>iBmVJc3sWF3utBraJb4LNU}0`dcqJ=1>43{=zi=9T0zzief^cP^S<8TbK?< zEyOzNfQlvqK#$?Ppq_+57Zt4xhZ_HXTMk71r}$!lu63Ch88}R-4AZ@$bP=HdVD6z< zDM+8h5aHgR3|qNk?WLz~0NbIJ$8;;wE+Tai{sU}m@<_2Jm>_&MU6nXmG)f$Uk|vlk zPPCAkYLgfGzry0FY{>1>x)p z&~9~rI7^u5lcK7*vZT_*A>Hh1FRRb2!JgK>64tuUeyd%SK^7Vlnr1fIv3Ct$~Hd+ z+b;ZMZfk|~0YPZ||IN(tUwuebj{ z{+n~J>W>^Pv6O0~D1@L1LFI{6>NEQ8*l4I8ew2U)CSU-KKbZtcn*#9!HJ`Wn=bFojXM2a8to6SEShFC-aEW$R&4{m+7`#CG27o;W#UE%ecV$IG@i(_X zhCz<}?*@sJ3;@{Qd+L!%Cs9-He>lxXT%<|g7*9d9?}gCC$LpT-kKg#mAEfyI>UQ|1 zR`v4h=p}34!nv&5-ng zV5pLisyzr^5R|6;SNgero52&cgD)>uB9O)`!r5ow8WoscLc$mjoRAJe>pwJC%oboE z01V}ok}Ox!E}Fwj{Rwc`7*Y&?;$L-tRM~whNAV$`d@}t6?1|#z!!J4gOiWD9$Y2np zIrzVt6%~?S?7l`*(4Ri7x8akIm7co_vR<6CAfYxP{*d&co8#wu6sVRQIdtPjyrfEv zS>R|EWdZ@%BJ@9`r$D+$fI0z!7oh}BsDe^71>*lb5}I9MDF=N>|GX3+A+L%E;Z?yP znG66K0z_7_7?`zK`w9-J6OumZB{L-b?UiI9NcuNu(%-tW4FVJ-03cH{^QwAOd=)8j zM8fEwb17TB=@348aOH6mC!B)m=*>j=rIIc2l+pS)wqIZhAOZqG<|Otq6w*gnH8gR0@A7{<2Y!B_#dJH0k%* zP^lOI2LN&J$_sn?OZiO!4NQSqek;d0eC9-En=Xd zrVh|1Qsa>WX2r;u%LNCAEXU9}2!;RhSA~va+K?SpUj*vow?{CB4gh(w+R;TV0AdCtmXKtE9`lUd1;LP;s z25SRzjCH!Q3bf_Q$D+}ovO>K18NApp7^0jcF# zRg`*)+nv3XkDWo73lOiyN4xV-k3?>5G8E)X6+RV?rr$~`S zcZxK!8@jADNN*bS2{0b&Kk0X5ZBWC>_-(czHZ=I09teIPtV)j^WrLH@Jr-bU|Jdb% zV5&KQA)g{Z8x{k!Nlu`MfKSvxMwi*t)YKyZFg1Y>nlWO$C5;6@lfEUsDZc4#1LDEd z^hXffE2UUM+bbFpFl038V{4W(R~{0k1}1B|tw18Ikpcm-5dYFOXnqxHZ)V`}q*)ed z??4kFm4bFaYTDQsfX*GSJs_TUpNL1N6FEcBY(OcIiu|FhAP->fTUq76@^;8s4_3o- zVBs)yj>6U=5!fXPfR`^{{&~n;aDnsAo3*^S&k%oAAvz3f`ajaIDX+2hQAncn93s&y zWAp&tGjxm@b7ir!qO;N)2J0Us%9?LL#bgvc#9s*V?+3;TbLryj158R0jeluMBA+>s z%$WFJ{*d&o60Dy$@l?G`oSGH~LFeht5Qrc8C6oYYn)H)&6&=)~C8r2Jqus$^6>Oe{ zutWSiIHA*)X>*c@0#J973jiS5k_DQBOb|vn&l0JcdmtB+aehL>63`$r0$3r?nUVp( z$givbOc$dZb9&(cIj{0lXc5Q-qSJPEKq9aQ@lS)LeDC`qWPId{?z24niZrlmOt#ke?_M zA`8;v;w3XP)sh4iBZVdPh)Ho_|l}G84LoBL|A%RfELLMOv2pN=Bujc{qD}7T_A~gQRmH;ju`yc*zN)|OQ>vrO-qOsHN z)bK;-$PMnINk85RN&qaSn?kA0P%~B)0gGlrz-|Cf0iQzv0)p}O8~_ll-JZ%q3M8Ik zehn3O0I8*A1HnWr3B#3`Htl?WO5q;y&H)eq>YDkJ=$Fv_i2s;ES>uO3>G+8Q3FOfF@ zF>p~o{0D}pf=Bsj*O39F6)s7lhF zFiA-I(P!7+NhO!2iXHS?_1?@8=v5YCc%|H99y?%_8w$4MG_X+tADZ-O*ShqMhhVjk zxjv}{85|XJ(g0$X^`1(TzB!~@zzPJbK;=*Pfs?^H$nw zKxW)RA&F{H^;7_p{$uez6nQe#7zl*Mh}qYthrafRpL!^V!C)N0SNlLh5RlNFNkmr` zp+l8o7!;u6BO^2eUx6u*X_6i$29sf6IiKu~FqnaNioG>X0+L}U?C@?EHsfwiFf1hl z?{3hqWO`Mc5%k0KS-p`0B62gBUty3K43olWhJxEJd>7MQm|`oS0ILefcJD1E0MjoQ zl4P^wB;7%)%WNA|Y|KCDLtkU0_EI6uD$-Xx1rjYWGCL4G(pM;I@mMQKh(Q7oDj0f{ zO*GpD!Pf&~f?D{pLv@ zGz)>m649qfB+^k|83iOGfusW=BBmw-O+Akz_CoyWnW{o>Aj$WYwV1$Qk{p=1IKSty zO~C(+zKkRRCUUmj^7A*4_~{&L5F{YEp+>BUNHcNQ_e;lOT*p zI@O|O{>h{~g_Jx20|TqVsOggEjyJw^y;X~n?nD?EW<^C${gXb#zp85kivPD`9A*F@|0Wf)1*VG%p`3&Sn@7|2 zp&0|X`6v+eAJ7vn5xW1N!JIH?t3Xqltt|8?P3KS61DFbMMD{e5gbNa2oT36mR19>4 zVw6|jQXVSXr@rgb=@X!6M%7vco@r_Fo+an z`V;_ntqg!RaPJnN(4uSvZDj)$!;`UO8vM^>Za|?YN7F->u{3r__`rXp-=sE41`_Uq z;E!X0t{C}nP+17Lgt5V3_AtuIhCm$WxxCJ<@S{vsWd;$}Q{;p@6*+PM28*%scgPW< z0zL5#1_PxyR!jZ;5TM_RxB`2?P|~7^38YCM4#ayi0K3V?fI9*Xz^a0>ZKz>Md1lN+ zMGP<}i2#6B81(f-qDco8T9E&wpK(zty&1e<5a*?qKnCIzbeSRNlg%;zm43$JAS}_^ zvB{$%&J>Pvkfia4r0+lm^nDA-_?nHR(*Tb}x(e{@J%k`o@!y~f?g}MI;gIMtWPfOr zw90u22|on5Aff#Y*`zdg0|>qg65;s)&A<&DI5h##Bg2q>k{8;vzaRrSV@f-#derDr zvVI_gn-mg)-V>Kh03;HQ)Gn4lA_+G9a+K=OV8P>IZie-+6rRz)OugApk$7s-3HndS zOROca=1W6iXcF|h4;WO10I+X+33J(y18?ExNI$v19RPaTZk~$9)A)C75D}ljpJ308 z5GIvS0y0wQfc6Uu1_ezIKB*T3E$5VBunf9W?kTsN5x7VN1cU;9h`4WnLsTamv-L1m z1rT_a_8Q=TF7iMyiIxI*X@EL_KMA>jCS78qZ6UT43Z<*7<=m@qjI!4B2yjXP9Q@i7 z=}bj5<<-gp6kvza*=eMvhQ~za>~(ZjC006QrVbQ2E&0j zvx5&9%l480YqkxI($&(xbTEsbln##vL_-WFqO#Hn9tZ6_hzk&Z^HMSpsc%9F4*W); zkQEIu80|os^jF#epasAaT_EY#S!F!~wWxh@lI}1}B>eCQL;y{N5)7RO2s3{_2pdiB zj|yX`w*jzmkisB?zBPfQEpQ!T*8Ob+u-i(f2iTw$4%xq59}NdE-m!hT(5XisU<=Jh zAp(MsLQfpdun8)QEV2OHR9M>L0=Y>f5d+?v1P%cn02>si?08IwGsi8x>Kem7Iz2bD z`e%-6LU7;W8l)k#kkH(UsF0vgn5eNnQ>X_cT49NKT(q=Eb>TQjoTB1Md@JrkLhO%F zh%h5oXt=u&|Ma{*IQC616<;(1?Jrcakg$vaP5RN7s1OMZ@lkpWSTvkj0S*gc{wTp0 zg*dm9&7rpc3?qsLLBH?HR^Z%78j6&G2Czxc0)$MT0@1`CNu>h#a2?84repiytN@8yT4Mu(H}zrB5vBUF88u#pi2J5~ht zn&2%+czx5c!D+k$7LUgU=HZj^Mn(pIj1I>D8Hfg6@14HBzTQ|C9*;-i&@@vDi%c>y z;!PXd?JPJ<7Bn1N4b<1i?&Fg&2eb;8CPGgb8A(%fQ%QthRZuIIk7GgZNqGD|4oAab z-?fd5%+LXZJiHH1{|Md;wP{NniAq9UL(}j}dHP01@Es!~uXE6lkkJ$JlmCt_C?+LY zpwR$Ymzo5Pu5dposR55qUV$p`6UO?+E*hY_aY;Nwv_M+f3B~8kbxm zh{xiBL`B5J#Kja9#i8Cg(lYwK%6DQClA+1NnV7iZlaM%XF>!JBEP2!9q|+)r89AVa z4K#Q1hqZ4Yg_w$oiHVAfi_s}XB`0TG^uoYH%MGn1(AK8aHiZ7-UidQ~8qQ9FLI-d` z^R=O`56uYf6mhg~f8bRWq@+19krPr_$ibxeumil2J{qlq!(uH`2t5Poy~e#pCZ9x( z?9kw(WEvTC5*CZQatwDWJ%x~NvRk2sPr{4rV(}1n8V=Cv-~tFZY+Opl8xu&hkN{{? zken33fyM6QyrI!?#$+ruIq7m0J_$Mz;E*Ezr(sP^P4UTwx+c2+6Qh+FLbxgZ{|Wkk zKK%c{lWB4JABM;4)8a|9|Kd<+|L;2#JsSSs3jYZ$1k-Q~29Jl>{)6?QXPWeB#3AYb ztAN2kZqwS*48kF!O%@hE1i>0`-}po?C=3t=*Ei9RhT389Xlw))`>(l?5&B;XDEZeu z36DQzf+uufmqh;ILxTjI6&~*-jmMA6rv?5FRG03MKPzn_r!I#vLLs3aN#_+QC{{Fs zddTxjN;=Mil}g8Az5AXZ51XXCynJ+z`*4XK;}o$U3s2NG)q{I`df3_7o^|9LN;WXn zQTOuk@o{u?v}R=;!vc!>+Pa>eK6bW_))0<@{+dqqKloXD2-iu})-mveaHzo<2v2ix zQafp?tmq>ou4PD6b5+9`m>6nnnfT}!$SJ4joL7UZ!%Y=EV-h8mlu=mS)T6xdhVrJG zM#_dMXU=OIXez6n)Y2u|^P>_Xap8Et1S^)9NKi4jfGt#Ya!cJsYC2y^pIeJp11@#Con=CSf=?RXyND)4U7lovGC&B2OR z4{vYp8v{e)Oi~$bpJhTTWZj+A+n&g@-IF2O-Xn52DHC}S*%0E6Q%}aVJ(D@jIBMw* zNVNTxxgbPKC{W^#kVYqkv@QQ@73UPwVnf8(X4x~2BJ11%4M)I(nJ9@Mcst^u2Pp)h zizGuEN>8TR*_oQ0?2b_nZwsM9BR)hEiN*A8*Mbqzt$=yX_3NF;%eqY0$bInwr%|#* zgn23Pv^n;g+;w*VKIT+z>WWK3dKRWyM4AtT(`qg+$VqK{^ZQLD~6F6tZWN9iY~ z5wheB+mO?&t`|Q}Qoj7+*JIeRvtW9y;{*H(X_L~V^2;23BrA0W@+8#}Y};xHVoqH7 zplH5+^r-5S`wh$spNsJ`d)hPV#q|$ANJY&%i5new0iDNu>@NT|4rF#?6E$W}$2(SN zJ0Ax(pciX?Xf2?A0WkNB4|6Ei);MwE3;%u;0x)-qM{*SFfvf^XXGZ~FIUvDG+u6VP zFIb$obkkkyq2$Bl&p7lP}>oDG-URAKOk0a_Lv<_sxSLdjX~E@oYKFZFJgR4Xh`Izn*hpGvT`*;$dE&IH2`PSd>0r zFyEEQXnc1Y?aArRGbi)TEN=R<(#h7=WY!}#SsNuG6w2MRm+Tu3E7bMTlB#s^Ll0ar zx1*!VJ^cULqh1TCnP0x7bMZb#{c7f~9k#FO=RIpinOqR*PZ7BCa?87re9~Ld$DOW1 z=^J-wFQ^$Uzx$uO8hxHwt!a=6sAoo_)Z4C)03XKA%8ogPtbF?Mws488YGZrPE9UOo z2j8tG81uj1e~6Fz8T(?+_*vCXGPtHtsNc%ebXod+@|m^7zBtN2`bbB!dE`;TXhojrnOB^mM49o-}`AK&M#HppPdwJwXW z2Y1equQ;`waZj=P&Jh%BhWYj|kKeniB0A_!MZx8rvu)KMRUgE?_PBht(d~KGN(1D@MF-kpLzES(S2d6=4opiv0ji5$WmHnk{o6QqD$L3k{ z`9I}EEH`jupLw?2$)@cTaEZUy(fC~Q^4By!9dFksOxGM6$2FUq3ugJ+Z{+VD>;xSBKtzw)8xkVW&M0)0w~t7IzkLM4n9;aR~_jD<&4#hN&mhhy#8S!3@NQ20V(8D_ui8m zkDaO$((b?U`7RAZIr7@VeQtcXqa1B1def^N#>&v0+iq#Uz*oTEs&M*nQ(|@Qhohbl%;`%@5$panSJLT1|WLN08>n~W zR@KK>3$5LcfpBUju=>2-qF1m(;>6!tP41!TcFNL`Hsbh|t@B!?+WBDwItt&pyj$EM zN}|cfj;LI7@L+cDnW|FrRd{8Q#dSG3dlfxxoENoh^~djh_-#9}Tmf}?VS>%nmHSPT z-Kl?87PLl6&zIL|HjF(u8*79U+`J1Pu2kTEwD|1Ah;SZ!@$tdxm`GU=flT*}$shwx zFKeLi4RtdjAUO}Cv=nOvbHKGr$sn^s6Pdfbm^}O&EO?p|ujE|et-VN@ujDT7z5CSA z?Z!j>B`>ej?B&h$BP|aE-OZnF13c1&Gg*Dv1}vPU~&papG@=^k|yYrzj3B1{Ti&&yG18y z1eOsLXA6Jn`juvCdC9grz@8WAde;8N1?F+JDlx?3-aV=PrcPAn{S(%_r?;EY@s@J! zg`L^BP4OR|ch5LT;lt?w0Q`?wP=9pB=Mez>zPXO--O%Pb1G z*!Y;b#~t5T@PWfn52=XILP!;AoT{rm5n?ITF#40g!ug&GN1y<5Dq7^9?0gw$dO^j+gP8BnA(1&hfjEY`M=j(Y&7-en^yz#hE-M zeew#xv#Kjyyt(iz`=+MW2L=b+7p@mt8_U059MR1IcJ?k@{>RG+MhGkj}`j# zU>jqQ&Rf@&W-_S|M0B0s`&RGBTV~hMPGJ{|{p(EDU@tXo%ATWK=2A^9aCQd=ZNvo# z1?VIs5cH=-<|6KQ-v#WeeK$W+)iC&a6OIbvI8xvU(}+`klstJyYC!+~Gi6Ix5Hr!H zd-X@oyiv2w?cbL^wcHgQ*q&(TK&9F3wst-|kkzUiF-k4HoupHT^1&_HEp)2D*%#%H zV9J*a6_~)gb*ouUrOH;987IyMDYNQ3Y8c>7#5^yt2E zu4*%n8w+!*VM*re`7D;(u^@1i^Qpvh)vudBW%$&lHtrBn<#Osl# zS^IYKcX;A9$|5&Q=ttaa>rPcO;>{w@03SB53b~H%XZWz+(GKXVah?w>NH=?=Dgg0)|3R?T3kkMHDMzY@)lc*(QAZr0l0FI7CZea`20 zyN%2({c9O3+$CPJ+_m-N8kn(!2u}`2hL3fpJD5WpsK*~T{SHDg8Z@O`_zYi&jhOUP ze|O67O22ol;dhTka-HB)?)K9uvVu~RD=R%YL_uPV=&tN=C)$C|QT5xllDe=dxKMc~ z<&E1~I$c`yZ^sIHg$uWM_kGfxO7Xjz?8X{v;%fnCR70PC#RkwhC?45Cgem`Ro-0G_ z)$Uj@(sl0W z51Pleo`~sikoII?&bHV|4P!QiJWl0k8Ur}wWq z$g)nYTDCpX7U%e_H;UaKNsV%P=ql|&r|v%ehos~%Dr`&`#hYlM}N&bL-y-uKyU zaeds%oqYB5$1f^f%bVd(D^!F}_ii}{+i{CZ*SfJYzId@w zE~^LvO6+GOXTd^&N!Pg(!h%s^;&%;9`Z*lT7SRWnYdY`!4t%A5miW1GX3sH7qWOo; zD_adW#V`cdG2btGtqZ8S$a!u)PCV^v>PRTxcJu?r5QL_T_AO4n@>Lg{iVRh#*a zZh-EU=R{2Pk*|0^{s*e9wwf^kuFuZLzr?+H$+`3CEO$RAkafcc*P?tHKWD4KJa+Bq zNs&EU&phroJTkljQT$hjpFCY%`s}tUmTJ_J&*N=0DP15vx{_OCPGOOG>|wonN zHWJI@PI{L@C~-U=DE%?@a`~@S4I)cv+Wn)aHuIUu>_*9-b~BYMv-N`x7Ugr|mvZX+ zVM$`B!i$3h|UISmxh(Z)$~^{-Qt*=*&JM*te`$e z{Jj4IEZjZ_cYMXjS>7(kSIn)GIG9*xG=lJvkhyhdRI2$dH*ZkLne$yP(XvQ!zLVYh zmPU`1{R?;yGyCbKPgce%_2wH8iYA=D^}S(JbXrC=#535sFM@$5Ek*w1J}zkc)>|yG z+`koxsTR>3Wf60Wd+_dNNkV#=Vv7^L<3&Yd@hyvV_QU1&`@>gCY!nm1y5;#xLh$4* zf8oDM`631mx9-n8_0Z`mVUtXakhC#bE6Q9mvoyO#cS*2K;{}P~G&`D1J&O70*ziQ* zSX@{3n=9JFWA(migV{%e_14ZRV{^=l^z$_rYb`T6zplAOXr)C@?HMO}UNk1rhfW}q zUlsny;Su=xq>1>>;M&hHKHZbprx*lcw#h`QhOGkNQV-mNCwI}0Wmmv7~EyHu#3{bVu=+~k26^E;yAW23b(v&)|vCSz0hxz!(5u!&=lTLG1}18!elf^CoI z$nP421G}8lDh{j$OAe2{F$xuwm%AXQ)|6;?f-zT1N=TC~O$6qB?Dx*Irk@XOhU~9z zUC>*)x;%?C6wxUv=Dr#xKa8VZR@(r4yz>J0?)iiYzEto}SNNfO_FW6^t@9jKu0`ds z{cQ$O!Eo_DPLs69_fxV-yF(L&C8A@E*>b=;)gK@COn#2AUz0t>#^IiH(lbP;vPihR z<+EyZAdj}`pR@$l8+qgB8die}IeGOX>uwwTsL4NOUF;Hm^8%ON7x`lhUzCc6TwmLE zoZGsf%IDzIe%JSrumB=DzSpupN?iBYB*SYjzCkYkKCkm_Z%fTg!U|vIuCEK`b7)67 zur_>V)b)%#AYx`Yc@`2{;V<+)Nq56z zL~YoVB|ST%+lP!J2KlNbtX~35Q8mSN%sS`A(<6?RS-h%_?h(GkZqtZEib}ty5y1=>j?_P!Rd%c{#4!7Oy^wI}dp!S9!bR2W9?L(-Yb#!IQ#kQnt(7gT zTL78aO`b2;Yhm!^x7`Y+sl05RrKH*lbD{|tgB%U%)-w4uY~sYVn>6d(y?0`&dv-5W z`^j%p!-|{?!#ywF-D%5>Gw}_=x#Pd3Wh#YS6ptJi!K?{rdRnLY^oIY9eKW?8H@o;S zi0hJf(&HQQ^i{JA_|MV@h?V~@W|a7<}mAFbjvP7OHM@kw+xllN)&Usl}~5u4yp zsmSi!lGa=c`T*|M^b#9wOuq%l8Pl-iw?E%u6XQ)d72~O-x~W9Y6_?f&Og8O(Qvc&- zt!q{pP!qco;HAY;9ZMGo`_!|Jsg&r<3DL6VF>QS2{BBkKkT_E!r{*U6XEH><>CETY zYp$s3Ge%&^y-d%i=kHeN4t_%h=ECFk?z^=I3ns>nL;}9X>rKSdb6iT z_M-N{V7!h;>+9Z4@A`#c@eMN5*Yu*?RG923u>axaZ8`U_Kf|wAPDIT(Et!Y7xci#y zIh|4VoG*P{K`*j@I^@E+Qen}tPNIg5oJC8JHamYWTI3qQ2Hu&KZohnVGBMiTn~M*LJa* z%e|U2hU2_Bp<+x>bs-8Vo;J{S8PUmzrmwmFU?b0fLp!VdEHf{9AnA09V)3Bp-A9!O zy<>aBBRp%tp&~5PZxW99Meek!V)cxexF0&?de~54$rB*V-c}Z%Xj{i=P>QB z^^I(SIEeyjeZ72>^5>FqE+Tr1Mmg^6PWL#17H9Ju-pnVzr~^!!*KzAV4GyQBV~!ZV zKsEa$vE+BYaLwHZn$N`TUB^)rm5DUzM0|3oo4T@ zQ@=i<&n{P1zKdUPx|d&eSGF!;O7hD0=ZPav_GT0crV$Ef-9Rz6F#7r9o%3H#nlH|| z_0Ju>{sOvA8SYgFQ&f+;Urat;S=TDnH`Q~IX-~-SD1YFs>$xvT?A*KB=dXP^Ov>#- zkyDRGG8(J-iFfst#gJBBWC{Ckc4aAU2AQa7EQ$dW%+I?SZxuiIb-~)DMAA-hgpR@H zp2DzCziYlrS~_8UmA|qAt(SURzAZDxKrrOpozL3rO{Y5sC15_q3P%GPI4tW4dplRBY(_A*SFr^6S8wCn+bfwFH#PB<+-*Wyt!+fbuqR& zOF6)z@kzk2eUnxo$I98L-3 zRlf5)+z!2cA&`}qNzs#ccCg{k@dWfq>M@w^wU3uONq7HRJu|tX@C=U%7K%?e0&7dO zvz>c6E*vIfYV~dRmF7HA$z+utkv)4mt9-?Q&TU2y%=6FGpaG5HHey=)VlS@ zb*(5v0{vhqTjb#(&l!W5>8R@>a^f43hnJYEd^_z~7Kb(#M?Fsn#4uVJTlyLhVbL+p z-1l<7pC@UKl(xUPvG}IWx9<3{xgW*ig3`srw;lD9xxHM;59Rp+-+0nYftHT^p@S#ttcm~lYvodD)b}B+SaL$nEbjulIv`_+zWp&e4)e>enRTD;wbc9Y7vLE+(_5AmXwWZIGDm;JK%h-zyN~MTy@&rgX)tS2Z zEch1eexC_XkAoY@k1zdAn?e`8a5;Din&2j5TDTaVPS0m|=KNkz;#4*rJ3;|fTPs#? z3xttGw?2^~vCGfn&}~DdlIP8R_?<|(=giBVF&0RZc*hhrK-T;lHQZH*~>92Ewy^9B2C^;&ND zb=%9fHq71rMeXQ)1~ebBJZmgtzhPq*-TG^vD`s3-I`HvIbfN%fpZ(X1(l`@OM=&S2 z7Wv-399?9r7Lt)Z91vtkY7Qo>=E5PjCZe-Fv4fx zXo~Q5V&)|NYy1l12Krah*)sjan(uG!uiWgZ9q)U8RXUb#5B1FdcXKoK;NVA1^FT1a zfyt>M|0s)q)G5K1jNM`Kq0A`jQ8~DshGt2rv8QEeScJ5buaQ#ap(ee>=93|Tq}$hj zoQt{G+P5Atp)vq|YuY@wPZmQB-Q=)dyF9y6#bZ@{M3XbaKv=>$`f%WQDE<$2>hvpY zK;YgJMwzR>zj<00gAE7z|q zaf|uR6Pe-N-E>FSV5W;}M)8*v&-$F<{%LoDXBWRmF&7S{UX*XxGqx*Ke{KKHNz^vg z&4bNEagxK~<5zaq4-}H}>8#vDo0oawHu_A0W`kB7YdpHkYvvDNADmxZ(QNnH)6{Oa z9Vxr}`>QwhUa;X&nMe+=C13Ip`TOJ-^bB8p>=}6nm?HF_{B<}!?i~L7k=7(fW8~!3 z-;=G)QAdwcMGJy^Z&P))|CBwscWUr+&766`2B53zB%$%(youyI-4O2h+p&Stu1oaO zc30CZe;Dx&P`!^)4?f)A+GkdI#B6|hWazCDtMkhHljvs&Rkh=vF2$_3N~w=#0^uIo z10=8Co6Yo}&+-*k3JLs;ntyYlyrusy^jSV^ z=|3xn_0gcW=>t>B4mUB~gYCjcum84I6IYnan|lenM1T3&B1vh&K0KRG-(20`4LJ^Mcq+#EwX?_=+02_odhwk!0p#Uk@2s^51$(x#^+ly zr#9+xFCEjV6%$!no~}C_o=@wxPRirLcb1RurUjoxq=499h8ylxwsOIua)Uv??Y7}^Vtr|E%e7-sb`lyl zC~P^nr>^?o7YnrA>7?^7MI7k|Y{UU-;`C^;!`D9v>behfbu|y~+Pc?w^CQ?A(<(fF z4!Ib8m&NoLcxV@ubn~BP3p{G(7!>2_%-GiSvb4l1+*CGU`5u+pTf7CBl(uv`m1;ZG z6o^Deqn2+P;i_}$c)koxe-bUc`Z>UobFXlOGwp^Nj`7Ln^GB_JS{~$oysX_qpPScW zh12T6#JWgt?(phR_jzTu*LbUUe{=uomuA#FaAO8AWRZZH&Y}JhOgruIqN!(BWef{H5ilOQO^pmzKQaS|W2_`&?%uytpez3_dhV zJ1-LSQK%^0Ec%e!54R{v*5Nu&P%Mo}v$&@M{re+0`cdS5_-Mk%vs9b_jN&GcNT zdA4#ys>Gmh;gRq&_iiS|t!p3IZ`=Ci&+nXSp)+ZYGLs)~@%6aqPPaJiF3w}=F5bL) z+PnOk;-tG@I9EBVl(@0eB-@<`ecnihq{^NY{=uG>)vWgG(C6dokE9^Giz6>mxLm(` zPu+S}`by+j&Ee9!i!3M6&+Tx}ukS{V_laC#3B9pv9O19|%T>HAx7|0ctXD^r$pIne z`-WZdtNlvaUsWfK(lCX)-!WC+Jfm8FVMw>bj*K~6v21UZru+M>9v-9>NGdONFdoFL zWwWP?*mo{n9r!6*!K|gxTRh#|UG+^ONV(kQif>$A3Wc1KqN1g!C}qsznDXN7*-tNT z+}N`ERj2P0ucChGmjYjwCEl#;ui-PpAopapn`GY`rcad$@pg3>Gww>8;oZ}$%s+9= z<3g#AR^K;1`7U5I&LjLzOs7UtW=iV2sqFK~oDNH(zr~d+K@C7M*lF+b?M!V3jKBOR zqi~hW>#O^pUGya1!X@ei7E7MSS_d~&OJTjs>E-n8Zd}d`i8a<=ZzOmH{=pxRlb7#& z4%b2XBpXK$P^!2uWV%ztmx)&SPyZ|geA)|X?fx5}`C6rIlKqE(>Jc%8ZyF!uSv|Zdu`23?rjBg_uJI;iW4%O`hLkq7%R)-rgN74ilacUotRPFy~ADc%P(ha zZ_c@KDM=!5CF-NY^Crsdn+9zhmsh_52 zmWM@0wujhXZ)^!Gt}ZacORf(Hk=v%}`RmUfl5}mp_Qe;~K1+z=QjTLnuYMOF`2ObI zz0Sg!xsGgC%FUiXJc@jng=cL6w#!WF=FRJ+aHR6CUqC9dZ}G#3<aC|{g!*kbC(%h{s3o8O7o?^u26Z?n2)WI#|ET<4urgEyPa3VmmE zw(^R|*SNbXpT~2{Sq{a1-sIA1>!%I$o9~}rKV&|CE{NcyMb}1kD`&lrjFq!V%7t;;1d_Nv@bL#y1MDc3iq2FHvF8lH|flfg7h5)@S zwKuDJ`0aj6Bz9;q{mNoD;m$6z4_8lz)cD_iTf_=xrd3Qh*m}1`|Ii^BtGxwZF_P+d z_VwC;r0?VJg_^(PZq}26ck`yKoqybNg?+*M{!zqU--2)L*%1&*v7sp=5-m>7@>VPL&uqkeM5^X1$URYdFV_9ut) z;ogS_Pvxj@(e8I@e!pw8wjMlpv++vEr0k_~I={&w=6CU@p#LD`Xf~@jte*14!hied zaKW43Ob>1cZZ$RxJg6$2&po^VZhe0OLsk~~oZ$U1WBzd~OMG+I*o`qM@=>YWOVI{d z!PRlu9|7AQLyxjd$uY!7b`yb?`3kHJ=a;v}650MHHw;`pun#NmEU1i{jR-Hw3p|BG z9T)vj%Of4Q7p0w2l)dY!y*?BU-vXYY$XA+z)ClnV=j0i6>pY`_(+mtT%RA?sg~j$e z{a)Q&X?lPXlKah5v8eLYSWL9CPSDeN*O#ng%Qx43BSD2pwdDtQ!s-a?t4BVw*hl;0 z1Vz}bbm0?&KZQNSmC}DLk#1{^VMjwF>Ywfw92fr7P%~#DyjK0j`tX*ct(t$JUwoM9 znFn`1=YD>oxt9Am=KAxN9<`H#GtTwGYCUm!8t)5z-aCJ|Az2!PhIHIud!R><|$8gtr3;m^?5|lro@5v`g z<;UC2r@VS)(lx(`YWn9?=xQ2&`8E3Z;U9$94*lap!mBr86PT|1$FEY%0)>?fZ7zE= z?oEm5w{dN?onl&z(KYjN3CN?mU#Xqp)o7Pv{67G1K#;$V!XWq~oeq+0KtO3`Rdz7B zC0E$5WCaefT@mqQmWvG!izUBRLs3rR>Lap$yGlp`gaAr^W(mFnhS+WazAU6;ibAsD$$BEW(?)P<}AVnSTFy8@SW9PQpNfq zUtseo%nDm;IwkJ+0*$Q`_d)J01Om~`lC>EEMW4W6{s&b|-7s0#{^)2euL&C>6IyA! zomw?+GTer;6wMcG6O9Zety@lw26#NA{$75zXQQjFG_rJI7L4@IrD@R_SgkdqAC zcV^tm8~npwg{E^7N4xEOMav8k8~W;XAkP@C+`aARdw36^HCbq?J$3GfHR77cP{3f3 zSK7HBUhQHCl>)B&{yB2WvukXf!hnxz3fx+y5aqyxi(TFO4O)SGaIg6T;p?{WEJlzV z!LgfQG=js0^v-MJZB;jdeXY%2W=SF_H(`1u4jovG{ewZOrIXjHwQDExa7e7?Ac4UD0FS% zZlY_)!M(jF1mV!KWj0tL80h9OE7LDG)%KLNn-6_$us$?ziwSn>!KFZ6qIL}TB(j&O zX0O)*gW8Mzp@YZ08USn7KB$sI0rp#D#JR_LhL#4>W~x#qN7bI<_};YPVzJI1oitwS zw9?-@nSYCdDBhAWs}ccBXeE8a6U6E~BRv`=1BE3>UoMB}#ULbl$NB;K%#PmwCjMAJ=!Ur+Sr-V#%Xt4lS zU5Kb50a7ZaAhmOfCYm01U`Axy3!}<$n4JWkok{+Z;G1Le{#&FTtlO9o#2}Uge!bc= zf9}UppXdq2LnlsZ=VPN4IsOL3Mh>34jOhkc#eDyprt^SLxkf5)2<(x+xRGiC1xG-5 z9CUAt4?8xojFv@x*JTuUMmC~$QyD*K(N45u+3jrWq#+Dj!E$Ihvo=at$nzA(T~G)A z$q(jZA}`!{D<;t|kHr1O9&|7S3Qktq#$Qp+g30q19-EjKTq}GJuGWbvv(^cvh>w0$ zU-BPZ^{-N$FzKrQMOeXH^rTTdJ9OIp9>qw6wNb~R#_3S{YmY=R%}hkQt7dnC0gPvC zpBjgERhX=9KCXay9b_;aoxG3RM$rromSWPq-t#E0T(v)6ki@7D^7L`=BMzdb|7s*d zY&IOm8rQ-@m9t`aVtZ>uUK6?D@yXI~RqWwC{UYh58f}Nb#3y z#5zl8Z-c(sgWNEF7w*NwaaX9!AZCeOvYZlYgO@FY>fFOD_nVUOi+6MI#}+`={94{H zxq_lM)nT zh+haf7iTNMPkaeB4@idg&ETK+=J|Z=2t4o*C!ZxA!`$S-qr|bTs~evsb=qj{hg!WMu=l^AI?;)Q2j(AR``T^qW4n@9*Sz zU?y9vLDll@Y6t^phduk{*C&CLLbD1= z%iw4^^tSuryi?|+EPnC82^HV95@_*S+^oySogGPFQ#F6e#e!acA4d5ggny-i3(bk( z&P}5Na7Y_WnX6jp<*H&)sRtWCwjPIzT4;y@xs=|?lg@qL73z)`|KsI0%?Mr?l0al+ zfkv(qX|q78Y|Cm`8y>DtI(bOx&KFPBR^n;pcyN`xzFFo|IDb5X%$AiT1C*OYBZw3xsH#uVS+aZ({!i;UL}DDiXr^eBWZ+I zSCax3d@n*rER**BU3H@o9uKiqqLvJ=GYE6=n*mGYO#2iKdjXoz1CBu~2!7)OAm8A% zDuJ?bh7aR$(x~hUwb{Cg=s2Rr8z2WF&>6m{O9@`SvBe(7tZ`&adma5`bbbDeUcuUd zfk+pn9c76!Yj2!$ZZP{YG9j`hz&K^oFgsJjKw2jduCTM}rjcilD3FY{Js_80D$MPu z*RaGK1@f=$c>;le(FN|><-MZQ0E=kA?>G~Lg@43-&rfCN+Nh`lS>Y=95&V@lwp~L& z1k@Jm@3M~s{QN=zg01^KyBiZ=00C0JlA2P*wn^zL@7?`QuF*ELJDJ)nKzm4HB+1=2 z?cFobA-jl5GmWP&s?cbY@fVEGgXS!7x1O0dM--?vrEZt`qi(Tr9ba>WrTA|%v^2Am zjKynKGrS^tw47n;0Bi2|;zjBOy%=AW-Xh=Nig^@>+v+{Ch#Fn|NSCZH%`_=wZleN2 zcwyWtnDK^CLH`M-;75llW0jJ_Ce|O_nkxq8NvB01Hx@s)PFkfgyfLS}3cezMSM80}7 zI1b~og0Ui0vvJmppKLyGFa{;gF$d9&)bdc-*r$xwq{{eZIOwq#9uKFCv48X!EY3=a zv<#4(zd6d!E)xzOY&-{q090-f6KsZqGFHtG0)h3)|4+gFXn+SKp;QKlIj%9Xj7)N4Y!lN?*(}thmhmz? zru6|&)_O_nsQL%|;SZbN2-;potpvXcW!#)>4h9^R@m6=4@lY6^c`?4c;>_Qe0n{7i z0-nu}ye`VT=1i7e=a{IPQC+Sf_!42gJKAezo6nw<>;BAT=@a%d4*UU=S%9=<5qk!`+OL)G_Ts^AK^t)57Ufd-1PWD*B0=nszoX_6LPthsmIjfj5FRT!+=U%44xxMf%HEeO%wMr$vt zhI;Ip&nB!;u(N>KsP5UV#Zt|>dxMCN-Q|Rz8%ggykawj}Al5@ZK#QeS_FP|4gPA&S zinii;fzgSDCKTq!FC{jhW@JDNmJeq#deS4&t|dQlkQC?qUh~M?zqm!ay&gEtkM5qn z|FjAWd3`@zOtKt{CZF5ihvD&9q3}1PPDxwl|6%<3z>qZ5f%THDPe#AD?=e=XeGiG< ztDtrM_s~4mt#2(AG3PEfdcj_`p2uZAk;{4u7GSShKN1j;G-S%U>Bqhq#Fc3F!z2w!HofmD7bRp^26XI4-p4`rf5ug_=a!rvT@ z1Zkf`aeZxU9=h>yomN#(#lXkde@w0I6?RiFoN|l!cTc)_w}*ijCnp8MP-(vyRyzG6fUkU%`LdzyxjABFJ&o-#46~s+ zkF~I;VZg9zcTgw|ILb6Q8Xy-jVVcHHBOt!@x<$P@m4sWY`+jb%YDs$MOXqb%3aO|* zM@+WR-&N=kHghY1;Qc~kQu2d-54U;)OdX5+!2aJ%I3KGM^l7NQwa|ciRTW=i?e5R{ zNRZNmrJMT2xa(Y5^ZH))w2zLXVX8!Naj6&Du_hwgTND9Xh5@_I+NS<2VosRyaU+c| z`!sN3UoN@97xVa(>_da&3Jz5W-g+|VhxNOeBT*G*@qPvyP(L6dMeO?6Rc}y{@Ixz9 z55FikPk*cBx|k;1V7(L0^}ouL2iO(9e{q9Vxb+p%kc8`n+=al%^-Sg^fSbS&=5@SU z?jUGp*tFzBNViGW{EsENqkNl`y4Ae^WL)t_ady-1RMq2g*x-kzs|u4rKpOO?x?vmorlq(LcJZpAR+};fGQ1x7tByLS1d|>ZNeoQ(t7>datUy^tBsG#MG@b9xznG|c?tKCC z0MPYkv;{U1tuLJl>bmaX`{DHfb{FI_nq3)Os}JyWc7rsG_Gyy8>oMCMc_)k|)2q}_*b`ss%2rCjcNP|l6h^gUw8hjdN(g;QvHj@uXy_O}=& zPWOfTDFx-mr}>Vkn@Mp`&ozL2+?A8BGx4(d6$|dxozmT~bF$YhSOe`7p`Ea`8l$=@ zjQ+Ma@h)Ljs)KoPk1@kP?lZeQ$`Zq zP-mqEBzV1S{a#q7USz*ON);SL@A2mX1s5|JsyjXb>MAmOH>A|`^1}{altR zT9+5y&Se|m5x}n0YYB=_aC&{=(z!fck|dcHRr&!1kWBt3^rr@o^1E&9%rP*0US z^L+fqZ=^|J!{b!T^Y#oO*?N_bs!PeDb0)F_APA~4epMgtFiuJC=!H?}x}tZ%ofxFq zcm}ZU>Eg->s$u!4b7llEXbAxrD+5#ipo{Y+5i~3o;IgghqEP70cH8O&mkmI9>*ClS zIa*=~){eldiqrOG0QLtvQpoNyWNap;$A1AWUK;5&>pEvr+;tz1q{*;Ted zj#4U+xkXWW1eO5;aG6SYw4a)$)Jm`0w6fQdO_&vMsd@S<$#a6x`i2NQ;qxTW6DlP? zXkNhVx_CQ+`eVATJY;#3pEAV)2d0y~mAcRRLAR7>8e#@%#B+CNa3>%Ewi0j;L|=)< zpS7rEs^wZUa7)F=1WI~S=Dop$C9a_tBz9|C=hHxz!-_0fv;#C2B)K3>oXbuP6_aCFL)vE398uh_8OWq}W|xk~ z%Ca^Xu7nqZgMZdZ!)`8e~s zk5mkSaXiiyhGgde6xpy#Zrj;Irkhe2j?5@?*}~0?ijlyzC_t*aRB;hWbJZR#n%SMuok!4kcJW5KDRo31c2Yy<{Gs!$wA~q$*yy?sl#7Cg z%K9i1Kz;bnrqW1rJUh9~-ep~*{cC6icc^xDZf;$Cd?T1?*nbY+MfK?}7kxP}=gU_M5qDUk4wa-J-X=31^Sq6_DmVdvqch^7E9xl_!b%QKd zSx~MbJEu7(dtevh*csl5$bhQyWYd)he6@s=T`uvv`jFkJmp=@v+=3cyvj8g?3;hob z^d|OwbYOW}ov7~LAIOg2_=`*VIRoqXji5N!o4wRU&xO#$WdO!5#@Eua45Ybc-ivOT zv%ImE7om>NYf^=ht5`F&!-|+6R597_ogPvPc&(8w8ECwnhl7o3p+0ptOZq%Gcx2GD zSgTSjF%w)Ye!@M>I*k|T@v^#LdLbZe7XaVUKFgjoK(^l|`_@c=UHd^$>%E6frl(9m zi-vq374acM0M2Emn2AkFP-A%f9GD2IWeHN%6B@ScA7CZK^`{gU*xT+ERwID4(-4z4 zqWM1oi0q;rw(OK#kOPj?crdnlDI$PNg0LEy0Wwi8F?J7G+V zRfT442vk$ij^_C}Ih$yRPhUUKIP3c{ZoB>C!Vj+)9~x z=D#amfPtUQY;ht>p)9y8a~KHJMN$Ib!WvVL9*tg8rSkhr{>oz zYp(kWT02Bn!s5sW@ysJuy)zP_9@wZZF*_{z8SbGL1(^{KEl@xP8B-NtfO1m~ll~Hj z&yStWrUQ3EZ!(u>N{xz*f(V@bhe?(GW$ss?WBa)l$F=q7!9UD9Q$rjuEAsFs*tLH~ z1$86&C75-MgPM2!FFdTIo=yifR!;Z7#4W|z(Wo^13>{=vJ=es$6Hs`PA5JdxH(di! zvi-1~=eQxxz55Bdb%+Uk2P5)TOrL3}v+p|^dlv1QpV`~>Qq#Y_GK?|lr;O0owlzFV zpKcvu`;&eW5!~K~p-)Yi*!sKCq{rCz4Hjgk&tbZOP*e@eJLB1Vh8N8`Iq_{Di3*+e zww(=@J8#j8IC5!|#>k$^*_7ZD%ai>-mnvcjiUqo7nYvRLa$Yb~{#~tnedZ1B$!pvQ zhONfQ4FbPR5uaS?ScJyuzG9F`3yD)#`eCxp9(~ZsJSOV=nAy+1nI~F&NJ0S!L(t)R zu&4(edaogM`9Qu&w-c|MUp@Vmni7vm_TE!jNlk^(gur2{_*%R=qCk|>!+k6a`WS9@ zoa4gvM+{p-w-_Ve=M_uzptl8bx_$}3`iB$t*OY@(p=QYeVES`^{Y$h9i~k-`3A>Eq z(n;TyATG^UoXmS+)-?yJzBdxHr5sbI-3uw9l@?i^NZ*0cS)>NSoY$S#DNBl2mwu{e zKDK70U0%29=JaMAHw>C=zojFo4gXtV;JU#y0+#WhM9UpTfs1Y+$@FO1)#s+svQmzL z9Y|Jb49~M-N5p|7NAoeV)5$c2@j@4MY~mLNY2I0o7mXCAyd=aJ7}8txrbcZi+%4@sf#K_K=}^la<+XSSy>OV&64$Xi+@BqT=~{(e~Y#{aX%yI|S0n z;4JtGI9*ZeY3kiaK&ZGROYONOXE>nY+Gs zqvwTEMmij(1Q5Ss2wXF&Z8XwRN-Ot7;+Bnu)g0y6SRfSiK$ ztG~{}Jb2CaCDV6$;3kS>rQqZPF+IH(g{YDpC0;5As+f(z@*<2mMG_cvcoh zPiZtZVGLk7#Lm!H0tdA=L6xby)x5zxp7v#(c$}6p??9C z>0x;&w^lIw{Q5Z2dExr{kdO2Pm3mT2=>rV`(V)5b!3(Jr7Cph+ z=D#pT$&hvlpp68%9w>u%?sI5&Z=W64>gElV3=0q?LFA!F!i&)0W0AMrGRLpF&@bN? zh}ZNX0XM0`{JXcK?oUcf$k%M2$sqH0_ATv8DWnPJGmJ9J)W_@o%6T)U^IJD=GZJfJ z@n&~Cdyt&i%paBfyy2hw*qNuS>(v4ucYH&e^C~AC0MaXD_U(BIcRYmGj+PX6@vm?> zaYB!MF*PE;kAOk?Z7_XICfp%YSbXcm-I?CpxH(86YN>HR-Hfo>s6CBTsM)Y@j)f^Z zOR#Q^zh7-li%d|OZaz#%b4MU$8)$>$$%nEk(fPX+T=?OYa08euAoe+URmWv879cE) z3>6Kv(>;mMc6X8=M7dA*Nj1dCTmtfs5`WST(kPqAXaC)DRxr_@#of-TZ2Pl-ZrlS& zb37?=vXMb34WNG3i&w)oj~CSfSZ^7@!7OEs-j23>UCRrC5#a{-vt4sl{OUY7wKO9< zpd^)8NJ+(8>Gy{EF~7xiyvBhgz|sUo6C`1XBZwoqL_&^&LR5*CGU=#&zcyRH3Vy+U zC^2flRa1Se!&1#!3O&icb8lbrI}1NRAY0H`SOUs*U5{e6ZU!%#Lhoy%iMb8|Eh)wY z1&)m=@T<}zfj5qH*AmJK@#NOAJbelX4KJM53zU<=Bl{I|F( zlIJ{$?Dh2G9e8RXN^U@!VgG@t%;%RAD6TTLg)^V6;z@i^o|&>aodvBPz%!#$xteNI zCq3;cYKuQfD*l)@oyHt=OA5~G3(v*Kf_;R(Tv=2HPo{@z`Np76T%+y(HA3f}gR6#g z7kPRO$y%g9dI-Oh_LEiCyjPlgGi|WV{w4#ZDMXAyRww$fk`XN(Z^S{;WY~!~DQcMU zAqG+5lpQ{66|?^T-z!P{Np&Db!1icX4U8C_HMF%<@IXN`Iol8aqhYYP)FW9Z*OXTtw z#t4*X9XE$2JCZ6TZ=?chW1Q7_Te(7t-mcgkpdB77w|#@VcpTI#$(m|Kt_}-ZO#ul9 zRg7L%*tN>2UKo-!L1ONO0Be7g?q1v+RWyE1ve;4>vs*p1C^_PmM7RGOHjp263P@|2 zN3O0XJ}y4i;t&^_Ey#cqJ6H#-#_5aw&oLyhgCZ@;e}-oiOn4X{)~pZmNJrST!oq4U z&Y`B9hTfd(Ke48qzZ_k}*(F2guk-f_Ux+30%8B};Bg!9mZ9m3TCE`m^d8(*pzApc6 z3~HlvOL!AN0#9;eO~|~d|6N*h(461X8w|1}qqJ0KWy}l)hOzVssFpuurfE?yavU5G zc@SW3ev){?+b0-e+FesB?3!Bx#aWlqv3=~zE(MrCtH&PK6GvAJ;j>3j2br>qgYOnU zR^|EjdUT}Eo2#JCl(N8{oWlz*$)%d~Nu7U^Bl%F(dzk3yl9JV7VmFr3LU=dpb)m-2 zj$&r7pe3xHfuNZY$Z{++Ap0QPJ{rko2%j7D53Pw3Do7i|B~SRQm(5vBbb;^w5c=r1 zwM)j7;CgHDz?~mhsGB=9{VH$Yh``vHLG5`RXu_&cs9dz2?c1KD-<>)tb=m78yMdRe z*{0urs1My%+1V1~7|mQzhK2HTMsrIf|BvLgz=)agxpv#8;r;Bg%X{o>EZ#S_GU!QAJe0@04hn?b#xTXxCl=RKGfc*YDgyRdz!>uJxB5UFp1BCCEPq2?T{# z(z+aT=MU{WK7x!ppe*b9%tBh+i$-H!pS60U`=vb+8?BlOgutSsd&&tumWx%@79$r? zz*_Ny2EM-MTrPsF95fr>(KXI&T<7kiYx|XGo-h@ls8dS_{s64X9}C&29$KV4xCm+% z(8A0KHpT_G0oCi|rd6s?*ukdv|NmN_Qx>_A#)*_@8Fp~^>i|Isgj*;+&7aN$-G(vJ zN7nJL zqe?pPVgw~BB6Gd~xUnFwrx&VN!&N!S^Tjxk=Niz4+r*r!gMspR6Vi~11e$cf7t6p( z@&;e3ph`F3%syI`?ZtY0Xk_c<@%1V1%oG#al{#A>lp5Zxt=az29QbefK&Vc}>Ihk7 zprgKAKM^@fYnn7`ar}}WqNv|Y#)s}knSwgf{gey{w9t|dTMr;w-P^h=`bWrT6!&S= ziUOaJ>IVI)wL5-M71o5e}u8{4*IT4g~8$K@(HNDnplfQ>ms z!58QV$g`d2(Npywx$FG1#6PjtkQcGj!F#cdlyWUhL=6Gx)Dr1D;0upQhJFP*Djj1z z6SYIx6D0@qauu`TBl)v`c7RLvJP7kSf|h}G6X$LaA3l|}z55;ymJZziOH!+AmNr=f zsSGn#_l%x=Xuw$*BO(s=ehgYii%~caQ(-gMEK1(d9%z|PBq9h-Txw8u6jD$^F!NAHTmlhdm2Ln6 z08#(~08#(~^&c}*=Vi;pFc*ep>&#pIA}nh zR9|E}I8FqP@z$>?a503Gn#r-yg_vL_qXzHzZrz(#flWeGeo(;pa^F<&+-CH4M8AYg7^OQ71dzJ5L zTm1(CGnG4OTUa}6Rw+&;Ye{=4Ye~Z?`yBeU^uXbjWKh0+z;yQu^%8=Zx7dmDrPfMM zrX1_HuZ)Yhqk!Vz{?v+`evG!s7uDhRqkPUh!8@+3e3v46JJ}x`RJww7P8_)7DS0+W z$PTp~;wmJ>AB)=X>lo6o)-1Z%OL##Ko`<@!1b?6QTgs+#yteb?zPO^~_V7yF?r=(k zk4duMwSj8zdtVsbFWS=MK$3?i{H4+QGCR@)KR8c*J*rVHz&*@ZlV4t{<#B0GUW>pj z7#m3!<9PydF+NQ-J7|HZK6~QRrFUfDq)7Y)#iAYUKe?Pu)J(qJDG;eim(@A3!%eT* zog61LXH|{_TWCILT%6YMnlW&nNi6;BO~VB=x!^`Au!^k`#dr@$6tT6BEhr3ADc)z7iBp~Mn@D2%7ZIk(=few{=PKxV2|2pL`81rtw^Uc z35Q{+fk7t2AGzSh+S{Hh?Mo6OCLH@5&K;{ft$(3!TP!+1J`mZ)Q;QIS1^`ASU2|zX z)bOO*yz7C^fc3>Qpu$SOnU;%(%QN<&S|p)9>%Q;5289VvdiZrqZElhoIyzfl?k^gK zG5)5ZLEc@3$J1&1um_(Vk*#`|Uz}7(u1?vwx|jsOP=m)^XPxy&W!GEFxIS(F?6x_B z84*naAj{m9Q#a_39+kKIE6*d|vq|Dtb}2ZoNt%+yjRQ1!+s z>HoAbKw~kaP?vwceB!nG*N8psa~>r&f2v-^noDU097RAWq^&qX8<9;zAy&P*_xeLO zw5^W#3JkwE4&=D-*Oye5bQuz+!FW|dS<0Q;u=SR=VI22q7(DKKx} z;zx&RA0x6CjvD9b_T`zn?c#@jrYom(wdf>Mp5Nt!02=MnhrRF^A0CfP)iW&b`HcKC z6O|i>;OptG<%?PGiYH4mZq&Neud3GVMYPA>Wvi;|yz;E93@RiBFykawd;(lhgu5c3 zkkRIGXI$L6F3nb$CK~6lkKG&ZDg;s}7R_>b8Z3%_yOqP~x2tx*9-O*)F3nb_&Qq>@ z2j_k>{HW#d+O&NQ$dua$oroMQl%#Iz1hz?^(AL1Cz>2?xyFLhHVCzj-3dh)!z5I2^ z4EJdXHm*q&ASM1pJGGk|IH)Z697=eV{)%~lzIb1C1csQ>2{=I!PUZnj2yM3A^e!Bu z*NCJX711mSf~&UBlbO=VDmg}hRi~@ev|&EPc5)Q>nNk8Z^|{GEHrCQ45%#V7`L|?9 zCM~IQb7pQ~m<}dzN9?vs*B1V=A)uI%`nJPqgfTyMBq;$Z53Ar>W$`$<9~?;&O;X3Q z3^0ChtJuDQE{t>LB<@^z6G*I8MS-A^%y;QL^ zI3j*c3T&u=#HDQ5mMYq^558<)P-Y2!nTxzlK;ZS3`yAIx9 zL;JSAIEBlRmaZaaBc!kv!4(^CzV7+T_+sUn`Dz3pK=_CQA~_blA7n#-&N>`s34b&$ zs}2vRE6o5HbJe2OA3k%|eqdrT64VZrPheJEcDOqE8Vpul2e?GMt>T}~h8V$u07=ug zS7y2(@UM4}a$8AZ4uXuvoU`4x&8ywJ+lv2!8$ij30Y=Bmr->&mH7R7*xB)y?Sfp_a+H=4Ov-uK>9ZP^&-d;S9Icrs>zd~ zqwtfJ&!zv6p48u!Dx9YLM&sN12=UhdQS0N#1frVCh4>xvON-l?zVJej(i{_>;!`$n zPg9eL*1IXG^&%uHv5IxN<$4to!1R^okL+`@k6k07UI*qRO_HCN=;p)%aJR& z%c(2K%l${4r~OBfN3^x=J~9xC&0Um)W?}T+V**l(&3+-vzU5z5SWz9CO}en_6HK%>x>^{N+zv4*n{pUxQ)?5yCKpwF>D2XR5y>E+Abwy{nAcY6!CI)Bv%n;;73CH28r2fDjp^|5QI-p}kA9Yqq^<-lu z6c)5G0M8dFBlyV<4rc@!wkSYu^Sm-t_sib$!=g}r2i?RFcQQPmvOJft3e8po`4INWwnh ztZR`94oI56u&z;`p?rTupCx}u+Knn#&j}d2FeC%8p*ESmUXQrm|G_>sw~s@gfufq* zE;hE^#e3ty5!A8qO&^#S)bY{PQy{ME6xKyaW{`B$|LwD0T{le!T{le!BubvB6nho{ zE0DBv&;e#47n3r?1eqcC{A^{Y2sF9?MiZFY_)3`C_)1X+X!R1LWxJ$It%M2jcMa^v ztzRB`&Q)q(rtEMZYagM#K$NHv#dj@DDHvP|Onp{FTgaUxz)P8{2}_x)3AYghbEo#k z?qPC|{uua(!_D%H(jOdFEJhqwEEfnNysgpi;dzU4RKsJ7F)b@&3QK*ZnL|oe^BMW+ z+OmJ5hfklRJOU-N$dU1Nu|od2U2b-#5}03rc+K_6c3A4T8d!?7%LQ9zh2Le4g&82o zYnHzQ0^^$z&VzvzEhRI$cqH_wENe#tTf%FK0VsG(cq;BTTV0}i4ND!xH1lMyLX#gX zMjSRQMjSSMMp#pZ+9My1oXi#Y2v{3U7j1J*BPfC3b(ISkkkLd*g|5-|q3&z5@}POY zs7M`R3RPu;6^GxZJgMLn$^UYTqCsuoZup3cP0Tp`^8+oKESYPI86=Xz<5c#R3uC1 zBUGsm@0%-}N_CQi#Ky%+keGyr-50H=(WdZL1DGm!PM836#c9Y3zYTzbS1v{nxmQOe znwM!b-j|E3J_(N+Wjy}zOQQ!dHVGO(*YLK7nRZ^^ru*YnUe1#WgV!g@rC+TNAn<;I-myvCg7yk?M>;kQCraz%kilO+^$=^l3wut${F{-J5OXLS!UDsT!Mxz?ue zN;+Le_lx_KAxbDF$roAl_XHn3NNx-9z-4K{8jPEdrf2I9KpS>B8Lp^NTLE!#o3{h| zk-u{U`X1MMLg>`{mPMOYSdjQ%b9b~15Rh$sgaX4@`cz)JB3lkyP|x$_RM=Q|hPnOB zEfkq?$M{^}1X~zovruKcNYiCARCd0>#z@+)hpVB9!+GLUT6(43T3>99wU>D4j-c!1 z78q=eNf*ppEx@&6$ex^9x%U|c(%RAdDoriS$eu6fX~(JfN51TIGQm@ zL?<7uT3Il0)REjHi;Ej$Q8oFb(Npu1(a7E)=}jMuDtv7w+bqVnnO3!&@qA_6CP9L> zcCZbtF+Ry6S}Ik4PNe}s_GQJ5_k}+H!fYqVGZdHXE&3!uk~{6a6y7g%Xy>CeeZD*M^RrUM<-A`o1IpkiNt)}H< zg&jW?KV{1q$7x9smn2l{8ra4n1_Q&8b^{NIhTuVyzl$cB!L8UfeN`;XI+zNvMxn7xshbigV)FpnuF!Zh#eZB3`V{pxW_uN_vUm<8m$bh z35eMgO0;*Dadpc^ajUUFs=VHryLIE!=#h{Ht*!D6b*$^id<(5-#t@VHA-RsBR zslV5pd|2;_uMp%EV@8*_S%o6X{}4WS%jQau`MT2yL% zGG%k!6VL5`Uw>IT0jJnMg=vAGFmAg1VLSG93g)jCFRC#nR%w9`?G6n~VQW~7Ouodb zaQ-$yD=~My0_XVd?a~i+6qsqIgcv~fQRrwl!jal)NqAX5U_WqQ8f^%>zHvA{Nj@oJ z0Z(n81W16c@h!zDs6LN}WJwd#iPnF{E^i4+xx%(XCSwzT_jou)>u@;t)lhLv^q=%&U`%1B{{n$ z_P_1e8AAiFsXn3fPVE`cQKI{T;Y*K{4YVH|9x~8;-H)bpf+S@RMMJsT zDYbWu{87BNlZrK;L9c<9`Pyi{yf&KV>_tL|Bx_W6%HIrsoM|H73((T(;DjAA#=-wU zu0=gB()bD}R?dL7@w>5}*qSz0j-Z!qep;#Mi`B8vG63CTh%HPKy$Wq=vshOqwQMD8 z90pW&AfkMo0F z(eJ&)3dQxag)+Q3k5waSBw?wd8Xw|Js1x6|M=EF@02#&&I)gTM;oY_>^V&;JgS0@P z{OLs=Tk&#_S;Lk75-}>M6EYF_Cwq`j8NB-p{6mHqIqQqDo+1?4ZKf&IoAr<-GmVtw zkml*50EP-~mV8pUT+xr&;KG`c(+UGfp8i1F%Mnp`joz9*B?_7!DGrLwJ(>>tQAct^ zkp^x@k#veFOtGM;KbnIbI)!xqMA`X7(j5tu?sRZL6OddTFIw6o-D>sXAdWkVf zW4vswPYR?vieHAe`6NYCd^=L{0%t#(ltU-~_zSmQ)B{HZPR%VbLY2KxPEAd-^rJY5 zn$Yi|Two`5rjC?!#f_>Ov)HHQ;h&?;>e zB|;jW-F;xZdptmHfS9ufJf9*mwN5bxY;PVo3AdTzj56ncVq4_lnBN?XJa?6Lt;=m; z#XTJsJ$PBUED*|#>!gy@j-k^$Lfb8Z(E|-9thQ(F_s|crfcW(HqWw3}2l6Fd7?RDA zP#Y}*BxTTIZn>f|#3@_Ho(9gw?|HLq^6ihY zluNJLn3*AN053r^U`H$Vt2LpE16X6&C@Nh3K?;XPelF~L`|7(2;|M|ikJxcXl~^Gf ztCG94RieHt2fRIQw@+u~mj7vYaWSZ_Mj)rVgvJKCL@b?ib3IE8Z8{TGFe4ds(^Qpm z+id`R4O=uZtMDysoLrwI*IxcNMr(Ppbj!sLQv^LWnj9QV{$+|3ud%Gi|WH%mG;e6GGh&@6R^6bgmN6)<9=lr>|#ZFdanhx`qP9P z>@o8Q?9KD!3^9cFJJJMOud#ATjZ-DMitoi~ofG9nO)zo^*;54j46*ZQ%#yR)Tdu^2 zTg|f^jApaHJF3Jy8#%K8tZGw3o5sX|S9Pyg?n1Afch1Imm-{b7W5Vc1Z#tlJr~Zsv zfGzlIasmi9-VTaLw?k{xrR(&!t|~Nh+qxK9?Rg5J@hl?TZHbISQy!Ku@dEbAUup#+ z)Vww7+Q)^{(I^T2p1M`D3i?x1N?LV`itvNPwB(dhOSSCd1njCzRD(s>N=RL&6g?)$^WjM}sktG&3wUqCK|=I_yFf&EoQakDzdesK0lZ zsIRwJXveTZQkV^M6@9gHYr8W@o{^==Rb@?81pxtI$34EW;uJ@$B(7GV~`}# z)-K$(ZQJHdYo@Jf+qO|{+qP}nwx?~|?w;;j=e+M1U&Q@;cf=FxiM3bNj?7$HmAPXf zw06`yt!&l2x41vYmhvTbrS=~RZ|+Abna=G!&v!o#%IH{HSzBIcSX-56V(F4!QsHE7 z)c?okgA~lgpeiq$pQQHgnJIb2nhE`uYLM0QZ z_+LM^#2rZaMW1m&20XH-28~j@_+V|8r-3EBx0&=ejF=tc4-fR=t%eLrGU z&7p%&+o>CrN~o_{;H9kQSOB)gEaX_X6jadIRA2>6RkyDPB6^l{W=>!zCT~Fc27PnG z%I)RC=vWvasPJw=x!=0>;^DEy5N@2c0tc0Tq+lTYwaHGYuI~@~s_!Qe@8S^{B~dQ3 z5OAZ`>$5lO^@VQz_I;sg3k#G>t?gU!<%ebl`4I*ohk}ilTdHE$M>&Qk!qR%~yUz+6 zGdJRk==xM8TMn>x5|gH;8a%Y#>_Z1zM!b8;IolJnB&0U?y=|6`MbYrIL1%f_<_ym4 zT=;`>n#WxPqV!ZkS8|qEY~Vkh;-ig0CiYiWFqRio@R>icaYy@e;QFt;EH}K%mB@XI z0&d*)O&^N#<|p~mhvxZs^LjNOJi!ZDL$+?dPPT%qDTXKf$KAuw^giddtB<7HGoJ+h zpP}VJcFE8FBNeG{k*_or>iE)sFeyxq4&Ddwo$%z`-w-nA=tEO})ZDAYp-c(|G4#Vx&mlHwB!S-DJ`?YYqb@#a_FCtgdDt)I2Gs6y@>zV)q5*W;M#DSEy&B zpQ+PqO5u zUWLCT++Cq*W*fn|3DAwBlea4p2lwlWJND~aV!{NWYhM&Xa{5zrQ+!Y4<&6m3t-e8SX_>IjuP^xK3N58XUX>|g~e~J!eJF0L50n;wU z5o-Y&!9Nb;@hx9bAIlW#{4($pIwRM((ubjYPXc{d83q5I)N5X2Ay=2A=Hz3d%sPjb zbKzEzmV^&3gF*nfi$#AGw2qkJEMS$F(=lD~Vge z@UroC=PIC`1UCA$*bf(RezdjLruWe~YD0?ZSN~x_<+%I>c3^#3?Nntxcu-03%X&K6 zq5EBGIC}qDgBKT-?iiMLJq7AUiGM*=c22%+ReTLp$y>a(VYs^TTlJ>2sLRAf$W?P#K3pjlKIpeEH#**s^{N80 zWqS60Q)Bmp9M?h*0s{o>v{6mZ4@SH`mJb!QMvmoxksOdzI1rno6HURFb-&F-ft4-} zw66nj8ch^vn%B1~PT@CtEGE`@tW2sNKFhc9^NtB9G1ec;*RFC>wlO`?t+Ojp<`cX> zPoM-Y5w4C!^nBNzXrY!f#fAQPqe1ms+|@Gr@~J&eEgmdPy;d6D)Ta95%q}adMwZ1Z z_{A;O^h%#iR*O7k=d<+Wkmg_CfW(E)@*wd4F;y|4gF`i_CG;>?XJge3NhAXJ(}|W>Q%~a;Sy( z#oD6xakOvR%aA9oZfqFj{4zeAJ5;~MOWBh_yz0Vs;>1D%KIM_|jLj3Q;155Z&#vF6 z{1@*&Y^`4gd(R2Nv0PBH_*tcqi<|J542i|ld|z1F(uvzSIc@#-9bbtxO6h|+ygxiE ztGR+MKI>?vo4dp9+)o5FE5qeL8#st~g|(@ z0=iYi`KBF9#muK|A?WX#5fpr1L-FM6mGU%p`>+M}qfnZ)>b3-*Kx0hTTeub9P*0h> zPs+*$>rVn3x11JBHy`|H`ts{MwJsr|GC;)6`PeGp6l>pzjraM~mxz@Ky0yJ;q#;Yj zGb^-zfn|`?U|uy(??-!R_v`e;$Hh^E!elP^jq|{5AXXR#BQ?7O`1ku4c)OrQfPG^N za%3@ZlJC~z(((y}_>*p{tPG}D+?3pBM;m^mU>}kCk@{}$9-sNUBWH>y;xP(c-1(qN ztv3f-?)a#Q&2jvi{>g@zaxM#cVv3?Juf``ul)<3wNlwA!vS@M!ut#RzNJLRY#N~DN z!4lZJ^PeYWXpw2YV=o({gkEFS$zMP2gcPVhz#WI0c3?Nq;WlxW}f#<<~pmP6-LRme$G0?~fph@mIKrBZ?ov zqEeRD1w&ghhBgLrh9IUg>B(m!a=1($4=WRhZzJ21*B#!?fP~gt{a6xAT|4jcbFRkcY69Sr zDSr5slNepO&QxJ_vDh!CZ6`u!lT-YWJscqsOQP%aM_0hm?uh{D4J%67n1uVTfw47d zvm1~)dvpuxZ-hZ5K7=SROCM2~B*AbE3`5cH=KkkIC3JrHwio75shv4L`^?MI32{F^ zIO1Pj^ihwkNYuYJ=uay#{xa=x(af%xEdoPoRhei}{|*Jwzqv2Z=*-8TxZy$^_TNS_ zyCp^`yv4IFhtST_+&s3`7F~R-lF*SZsO_PJS5L_cdw6r(cY0+l9UiQ2YFV2dq(%GC zAi!G(i+}(mdn~e*{hSyOjPx@^j{MX}%$EgWV?z^oq&wN{-l0 zDIP`)chRILrX&AaF|b~ZVeJa`#D0pxX*EV8YGSl{lV73h8?SvR%#_R?G$D)=Y;1&PGCcBKSXDog9!T?c zd!tT>Q?GGo+|8kTh3xc-9Kak4uHdmJvzka6`IUj`fit6s9TWO?LJY&N0|0Q0QkuyPPr~=v8^}Y(Oy=@Pb&Tx_Ag2&^W>l3-yW^Df9RKv5sw^K4 zniEuHyfZ8zR2a#v!I#geXD;&U7C#@x+sFL1{B9>9=zFaC+DcYw=!+udr>CYqho_eg zl0M;?LeianC6Oy{S&_SJ@=H&7Rt^42(6U-JhoP2`ghn)G?7`A<&P+{7VDeEcC>ku< zdUM?&2R@b?aM2)gJ=MXkdHCwOtECBqi*3XB*N1>o<%s$FGZDEOAQ|{+Hcb_=8?ngu zS-fLhhM+gL4u)#eV;p!%^A|)^2R~!#KJuAd#NMMrw;)Thaw@9EnyDEccoCSR&(2;} zF1G3~nWWDeePIj1SGj;wZh}iAI|m*#W)&R-WfiTk>GX1wGwDw;{C?1+<%1*JpVtJV z@Ztb(gii=P$UA-vDiEYg+62LA8%rx)skQSP`<+zNDnb;JQa-WWvlVYyQsU@;xnwQ4 zh?q67eN>xwdhr4woO`tk>5sUPFdvS`chuE7xRd4Q0$=#J+!FG$z2(b4yQJQRlNF6^ z@u<9m?G87ei|ZRebNmlNNO?!dFtB54kvS@8e=wNRzajD z6zL8KP7>%+HyW9DX4Y$=@Wab=@@W0Y6ja8$DaWTlZ{Ln*<)C74c1eCnN;qskN2jzg zIm9|Bo5ckC$C_K)88mq8Mp|4}RN<6S5)Z-@*cM&e(bavxS7VjZk!7)Wr3e@Y`O>EM zy5?`re<8vx-`x`d{lVSaZ#6D4@nB4U`q=A`#c4I%ti-S;(=nVQ+cbaV_pTj=*tSnr z;vc0(pWN>gDnk;6ibz0WG4YOT*aRd2B&l>W%P6e0EZO1^)6MZE`PHsg&cYs_d*ydz ztI8N49T4oX@){TSD>@?Z@Pi#2*uZ2nD%K;F6WAZJWbv&o?52N-BC@C|BzS`b~Xmns?-y@QC zOaPYl#<&0auf0uX*c{!p#N^it$IoWQeVk3hZ@v(C_eyG9bAt-+8KCgMYV3y^_=A#o z;yzt}922T|hd^Y%GIP_@;PT$T1pOZU>^BIbC**g1Z(z2m3 z6#}B~RFvKCoqhbBhX;uemu0A0R2K9|s7MR3Hm)(G^$zj%BMBXm8%IBzyPPAqMvVOL zmz;B2j}-hnW~TKE>im}~CWe|<5^nwHZyGv&sgR_3!m8T@=~vXYzI={lmRQ^c%+$1{ z7x#>N2(aBfjv(S!B)ZmBpf z!%)6t-~Sxigdk>qoXRlNF?4*~kx$427kvz?Otd*_iP+1IEfMMujeV<2a6Wp4tF%;@ zW9ommI+U4VC)(Rfm^C-8EuD>Rv)65j&#w~-iAi_h_Z}jk7Tk8dhfOXnI^y>#AmARX zi>k!J0JkE)_^_ssUzU-p5r?jWs!$6p-) zXD~)E!d{o6OlCu*e0mEvChL`Mkv$@PSN+BX5h9oy^W%=K`-9A`P$Y5qe3m?XV7_=T zK)~_ooCy!X@Vk0*$*Tzm?)Ov>j*@d_6)Ju4&B(Yp$-qj;1kw^azDm0WS z*QQLtEf7P?`Dg+6@J@EktcHt<`)ndZPRZdmrL6u6dGdVp%@)6v4jK!8`li0Jj><#V zmLB(}R;?-Oy|ngpk9NN*jWI+K2yWHI)!GY@6f^p!uEi}A7PNe~Uj_sedj|xFU}~Pf zOJy(?3=}BdTE0(oXYU*CpJT#l(n**`WU!@hR&+=Sj%IM~or9xoC$r{^5F)14goB3g zNFZ>VEqcLEC?jnACgJX)=jh^??hJ!SOo@lO9x3S|icR0Jr$%m-TZP|P1sw&w%g>)@ zyH7}dpICoQy1!yo!b@J?dhcGhMBHZsQv`3mdPD%bmpwup)|OeWwJAe_k}EkZVKDjA zF>P`+a3Xstk;6RJ*85XYb|<1Vu~PrBAvgn;OGI zO`}gGlvCoPA73|gBL2ojM5FriCFjd*tU2JaTLC?eH_FzlFRqE(1DN z@8^^Q2jqd0#DLDgqU(-h$^}?O?Y5gENy6?8dh*|oIQU5I{wUbOlV*|BtZ88l^N5AqKG+Rg`;K^Tzc zzX^h*K4cz%y*;j}?g=nJ;+SJ-|0df$_tZCn?H{P_)-s&7^@^Xih3Tb&Qpyqjy?TnS z@2Iv%XYOmovA>DJzlSV?xro82%#&P9b=g<1789{-!d> z<^c8zfrZ*I#AO#<0z<0>CZdTaBrjT6z@$ZDrH}?2`)CsLD_c5KNWMbVh-wf`iRy`j z-#!`-0U(Q|2^tNHH|wi0-jxkE-2{o*58*r*hh1ca*4gRja&BeN2g5$P!ZOO~9GFcvK97Bfv5tw3@1BVX zRmZWOkeHEpnL`G8X9S8Yr&xJ03kR+{Q-FXDmt0e0ZJ}%hmQ1%|7N;e|6X#oJ4D!YD z;KidqpMzLC_S5#*e)_R&ItiPwbw`9dcN>%gRjbse z^NIUS-3fM5>uZY23-6)wAAU$WMGOIYL(C4wqOV^cbwPU>h*@JP10*_}(6pRu(A>O6 z2%OvlM4K%)lP=r2lee>13$Y*JJMNd9Ms&Dz2wX&D$RGIhKGdRi?j%|^#yeB7^;5OI z9rsnyZ6T)y^XB(aGcoI36)PN8CY_{Q1K3Y}l}_{(C_5up*Mat_H=0n$at|tF6J|^+RW+fvk@6JAMs>{g z30->!YS4RPUm?*t6^^y(Yf|Za&h4Hdr2p|<+)q)0fP^hiVR4$t_ndO_)7WkL8|S~n zJJcgF-?1Pu3z^0%I1Xai=!ElDG{|6=sKC#t7oK5GIUD=iIk#TJ@sJwkY_iy@PTSO9 zzMxZs@)(5cif;NhUC%$`)7*o!lx%~7;v7YhVf|l;D)adR(xt|oJvBxdwB|`hx3Doy z&HYdO+Y){Kc7pw3guEAl5IW*Df3|5?*yFe{3^X)!JUUJFq^%xPi{MvtUgy!7B7_~x zX!|4@JC)++NVw&3z#G3Y^j9V=Sy;Xw-Cv^J499dLW2wZyna14y@DGSCS6VE#9;)n^ zXt*3tx~3#mGa$&Z3}B{O_&g7@<%zWQj?=dcm0lyyqqKv8if%ix*N$ zR2G!J6p3)6$s?9+up@m`)8KpB;{Z+BwUNU}gzmj7dQFfQv3_ILqpN_G^iB1u?m60E zSBO_B0|T1QDZKy1*33uFZ7K9;v_VbE6hWF#ycYby_;8?H%wBocHyNTdLelb<`KdOw zUBCAc?JFBT;u@J<>fZK4T>epKCb!(7AiP+oxXnYH2%|cW4ugZ^38BxXz>PJM?_so^ z3d)xR88s8Z25Mk(oc5d~bC-P?!O#6Gi@NnzeHMdR>%D9IpmK+?33cg+C~- zQL)#2Sfr|okEYk`fKp1UXGt$Us`tgO>Nz__}`*F;jDVUEuV~V@uohY{(lu{4!Y9f6md?uU9u& zE+lKGbX*XQ!QKJV>oJ>yYc-@yCzE?4hm~HC6ehfXjvzFm>@YDr@PE%Qy#+24&m!%1 zyP%FXNOklQk5ki~cubc{f1{@B|E2YEapBEnKl%n+uI!f0bnN+~)ZyKi!Mym4 z_l8PSb|BsqxOc{T9czBN*B+itR8nsMtmZFrvL{qIL!XFlbc>|JQ% zA_(wc5N*j)h!TpW^*`){rumq7y%eMsg$AxTq9RxZ_-D|A3O^bQ;K;I|cbLyJrjWtl0%DjyA z11GZFdTVYVzKm5`MD`tK;*;fA>TwPSsQCLktDZ|T)3x3bQ%ES~G?nOGJvwV#zZ4oG zQCln8Gz~v=H3yPw2Y9IeY^Ll`o_jI&qbnPKHA6*hf$bsEr6Mt;|u}k?*A}KP}&0R!~NNa8Pn5nlOyI%n&Q-Ww7^~2Xj;td_cYSg z*`B7?p_a-4H~ZtZVDq`_M;lz~otq-Xl1mi4Tt}3Ym#L^#u9jMKzIRGxO+-ReWNPuc zf<_8$T$EbeII~)2Sajl4idQCYns0qd{(j$6<-9np+lVB^Xs?q&y`I`sFq^(OD+f~< zWW}~r1}AJ^Ira#-8y;lNQ)l5QqmK1o=dhYMjd;?uXZ6!ewmj6K_%TYEx*NCoS*Mu{ zB({<79~pW5eRbq)ZgRz!gU(e48DSalb=K)E<4q)ErpoBU@~}?O`T?jdg?P!YdoFF> zUoQGQ3C(`uYuqnUUR}oRQO?oSEC7l-?UxI&p zcjr1j(Tq#P^XQ%l`NK2N%8x>X(m?m(v~ru#$hV{_ujo8X;JM@g6W2QNp#yApF9{92 z+m4CBo>=b+r3GJC)5l1Z!$iH!Wf2&%+)UZg1@}JlsA91OQfSox5zA%Fiy3$Chvz@Z zkv0CsrgOmb3dEiaPE>vKSHZi@r)dkc8fc5RYVLo&I1ZP>Bw@uR$6?Vg+%-8a?ln0V znW78N>Ehz})b=02@oItDK?eT0C-lC3#*Lv?AJy9P`DS5}X3SV9S#*_4rth4NnFN*0 z^uZ!}X-X{=>ge+jxacfd_1H@O>AVVoUp$=kTot2@8ilQK8il$aZSxP0we_kHtDUM4 zsY`tyKzL&RFH$l5It?%axb_gym;{mO7?K&gqV#C$dabv$?Cs>?C%*dDPUu3k_wQu6 zg+GT^E*U~O52M6EM(Zcn8CIe=jbpI;#7`#I@AjhBZeH1*rTi!6Pckx z?-kbH-%mvLR;V*23@Zrpnb-W}T-@hbGnYWGLUtZit6>7fXcq7XPj{_TH~M0Z@s`PEhfJUgF_hwLV=SxMsHd&8%i# zHa}lLx#4w!Gs~)9QDV@FVQ0KN#EPv}S3s9Db87h9M&Yj3zQjXP?P4lb^6J`e@)-q{ zwn13p8&M_IFVCjI&L7NxlSJjmx|6^|Q!FcsA3j7dZ4#KRyj6jVtFo(WtI~N{VG>%T zWPk7YqYC3CR}ZAeMEwFd^mWpn|BW6*i#0Yt|>i?30-i%mc7O^96<~mdgYP zmMcS6WS+Soi4Ge+d#Le#LXHc86pq_F&{g47aw$jv`v%%p=)%6Cs#dyJ3WzU_!;~;} z(KJz2oW=~i^a4EoX8^pMo$Uz_Ojr$$4N#vk!+cCfRIeOoLQI*e?Uz$bm#-YBLQLiU zntR5~Oa#*<`ON%-wdYMSeLHg;2{D~5|F4aB$b3A2wZU#Y(kDiF!dH+TSP_Q*6L+kR zfu_$ATe?zJX;D=c00=?HJ_&&fTj<^US_pYNjdwOZjT-k~gX{25s(xdIP=5;!4Ce=b z56_Z4-m{@PmU9CLpqc<)U;tF3!~o5SrxHw^C!AZe$!Qcb308!pX&eMcJ$R4S9e9sl zLKS;fLK1~PLU5LTxPN26gfd~XY=9h>P=EmbzkzFDkWWf`R=+BCZl@{XQCeK=>5l|C z5d+@r;c@EWdDcEcZ)(ciIavyzP3FI*CsqyQjD|Yf_h$bLE@*c3gAQbv-E~H0qT#W>8yzs|B*~`*Ekw%o7=$|O9yKxYa zva$AnM=7a`$A1t1?LVU(AX!ek275OC=GOAu;);ulCGTh-DAPxY1<)!jg_M}3qynHe{dp%jP@W`<9qyqs(%_XL4=Bmr;ZjmEkb~eFo9MPAq;=KeGk7O_nj+j|t zlaOopw@ATm$tIuOI%1|l!XMLEk^zIY)fd;1=zG*K^KjfIi)-b7jvnxai;D}8)X z4bG2NK?F>!Pk7|#=L4KPJ?%?tO8~CIu7DbU`(-*+`)0=(Ei4cf`(;b4+h)fpAlI>H zx5RoHcKocwI!L$YHKp1Hx_&6t9;^MbE!Aza(~J@p#DDCu|I0CeT-6Ty9;jFN3{r6i zhBzvs0sxfZ!0VuYyg=gfIt6VC&>Zh`DJnq7_xPd_SAJVs4<9S(Wc6YO^tTA2pimL(#%-=*psh1e4Xh1*TWZ zhLv!y`JVbXa@3$9B^01*0QWU#Rp7vGbe5A76Z1`(M^ZvaDFOf_)&NpK=t>VOgCosJ zNx{HLi(^adx+>#WD_6SdEx}$W8Zn6xwfD{dJ-W$ zV@rD`aJ1l8cy5B<69srCa5ilGm*)g{HgMc;&;j*0Jpq6Q2NVN69r7h zz!5)Tsh%_;!J5zs+p=@;`@(Arpe`~vB3pMTs^H}id7m_;AiAL4)%r@bcK(ZzaChOMue;4rh>{c3YE68?GpJX%_Jxms2W z5?EO=wsCOZ5d7mzV^6q2lcJA)sX)qw<{>kfr>sh)kdo3ywgNIsB|;ti_~p&jAdpXu zC^RlH^D-ZvIEmXa4J%D9nLK~PKt*+_RL5Go$LVHs47l!CVs6*iFXYRy#2v5yN*d?U zvCrMEF&d7w|H=n+u(8-&kGO~0{O^U(F$B4Z}z9+rU>-{zc;3*u!yx!oJ z;|Y<7s{T&8m^QbCN$gp<2w(3l%qtFb&Z&#apP{j;OB&bih%A?7Pd^jC)| zR)}D$Jy8JKM8OotLpIoz!ci#>g9G)D4b1u^ncII%*KdhnYH=TrxAGK)M3H`)ReYH+#l zw^SC!y(nFe@GGDz;7g%flO(+C?GlMbKF2SSIPdl#k*Mfmy!>>kUkpOEEkgeB1$uc# zd6K@vm{qX6v&+Y!7UMHcYa zgA!EIlNsFFCl{jfvo&NQtTU(}+C5M?);@4NS_-t*s|l+7t0|cCn+aOqV-!S<29~@N zHYR^N%A>@hC$;#m`NUQ53Bu`Z?VWC5QFK9I(VKQ4(qV4Q1=U2MbjS-y(q!#7H3TUi zJ4zlu(>|*z;csDkMmf>uAGJOS1|gtaZ*1RFS0_IIs;U=v7wqZ+7Dh0bN-BahqL;vU zCRJz&|I%H}PX^5j-6N{Dx1k_Z2b)x$6Mg{Pz0*~By~X8N5y&CvpLv;BfpBmIg42x2 z!G+|%k~M>biIs+ri8UQK50w9}^Z!|cl`UR1xZ=M^;X=p^#DJznO@?fbOY(I{Ov;>2 zNGiNt8v2?KXoj&%e2qfy+{sgoQ+#C}SN|j6%@wG0k}(V*tYdC{YcF9O)YqO@{dbJR zJ^S4KPsN?q>H1*#tGjfiaybkFp|#SSK$QZXU%Br|WuVI=G2w_OUmp~3pyxq2tbc^% z64885$VNUa3`KrSs~g*L1x5_zE%Gnn?2D_SJK#MgzJwum{bC&4zSYt8j*tAnJ+?$a zlhBIY-4{|de4z9?xq>pxm*skM)I7Le>}Z`+^9qLpt7xle@ep4oL*^Lx9Uzs(ga2mh z+!|^mo?B6<$9|%8z=~V_a4NStbP%`tWOuN12PGoIBLpSk$jnQ+d;p_c&Gn0a|HnOh z(HA+nh|$z(gh&q0&O&=}Mi1Ao^`2VA8~jp2M0yj>=}|BH3o1Vc^@u=_QzFIpl16HO zk?VeZJc{3YcT?#s_Z+pxYswCLD}UN=W|D|dNH#si)HFF^neW=;9GdR3j}9W(t5uWG zKKCy$*ThiFLcmzuB(&${PhxP+8lDH*FcAuRROv|IRN~^`RpG+=7#61`sVg(GN~o{2 zwK$}d`~8gop(=+~da0G9EvvqSM^M}QzkrHYrMS4bkJr^ty`;#5xAz60uIIyC@aL~4 ztrs%?<>U+5QYgyogs$eTb*z{{L+{P} zNjerLcz<)3b5r*Trd@b_<=VZf!x|q-^hkaXEQHvX1?8)S*${kSn`fUfehN`i&;;%- zKPe__i?Tfd>+R*d%SwF_Q8c>kR`bo+5QdaX1E|KWF=O(D+@WbE?sOBtMA@FH7$$vJ zi)$xX*9J_Yy2N(DcvCcTgYP+C&nk3B!k_u}fU+YRPT*`WLFIq-I*I;YtPiJdUUV}y zY(HjcL)*JEqOCDU&POk1cgN1+gNnmy{R#rdmuIT88Bagl&0JGyUI=SMsl>=lE>_q5 zlY8nuCEge{LRy}}N345k)IB;eWNZt4J?RdbE{BgLWyxFto}3IS>ci^tcyxOqqi5{~ zDmFFoY4`$r6R(N#pH{hfCGwxApja;2dsh4ui%H#RzsyKv46FW(KEEdmuLO*SiHq_N z&MBp))jd%6mMmmZDlarp5hE+9YvwGhnFXZr`iLbF(V&$==!1!336uRrqRk*AY2#+2NP;1r!g9HHGpWkweiTe)GLsc^gWB zk~cQUe0}!M*gQuZ@1%&+`gWOi*@1L_`2b?`Hi=0NERa>Lcm5-oES{wUKY_8CM_s=Cja zVRK(z79vJ3; zGK~a@%wXnqkqCuslW)U;(vtIL@*HE8h1^^hL-t|-_{M=qK42}t$}92*O2K49Gzc)?MRTe zqCHLawU$+JwB{xMZ02w%9{u69>*4P0kZI6~-`Z7wX3DVYZh3Rv8%kRpwsctK`j4_P zFpV9C#LBPA3!Hw+nqESm!YR45EY$ur%b)b_Xe&_%?m z^Ws7V6s^o|E%W+P?&|so8zqY>86Nk{$tMMOO@lYzw1Fl=yr=T3Vn zRe-_j1U(UvPriezD<-k9e@W>ZsbyH3`VBF#u!;jG$MQ{ZG!n(Cab%$gAfl}Nu(Bd% zIHp#sscd|s@N^=83f4wZRRdpP(Q?#`Ud9xv#3eh*w@}z(zUHCbCLwP%v$FKJd2F#L zQULJBDoP#MZK(X0_$eHjaQ{CL3}7dZS-UA@M@;Iu!!Bd`*H*8pw91{gxR=_H;%!$0ID!4AX491Ht9!1ewE@+)e_*D(QgRIvZT z6fZ+Sw(mZ1o&FLDMLy#Mnq%3U3eGM`QEm<}*Z>$q(9Fd;YNtSdgk7IvJ(?zAB$N z4NnJq{08T^KPbHoXnc8%>P_Ui{Xk}U{1^Y8vytof*!iH|QcAVyfyii!_A^^iT_a$6 zZ>3=`vHIN02gAMB#gLaz7n(xw*O5LoIN4i=?Jr~!7+SV@%nw&wwd-gshIRjTPosyd zpw^AcqWN?xb8xCnJVHWSM^;=S0ww%B$EYG{#{gXJ%Xp5&_&8|`97zRL0!@>nos}jh zR%SJKJbwMt!tHun0meHjIBIhuVK*XZeyYZRD7Q;Ya@MZG(7X*;uBx2@Q#ee-y2sb@t(cvP-y90P|+DrX2uD>9;D z;^kK=Y)wwMUYEa_uwxI#zX`IkvD{v&o228;!<%$RH4{t@(&OYrCI6X=&j^wz`;cR* zr7iOS&cPwBfF!`mZVZvCCBgCnO~az4g2IhJ2S;r0TyMzg?cqb};!5OQsF)5$GVe@n z*suonM*@a;Fk!sYop4I6rD82rz2p0>T;k!wTql6jY3wqwdR4xihwnpEOdA%0LDbZ* zxJ*C1Xv>xmU*q?L0SLd)VXk1`CnQy|WGEOcuagfQx>{gUGVnScCeSP>s#hDL!c(|; zw@cdQ#`8jHD-O7Dlkq7iWfw~Y#v&t7$t?r$$tHwd{J`6GEi-R>J|xfyF;{vvSg^VN1#l5Wz#SkmPCV)|ZKHW?ZZ=oA&D+D{kMPuK=jcOZfbFgf5@FbDt z(YFHR+=|E}jQ70*ORBRWIUg}awMHZ+S$f8$JHQMBJ-Z()7kXYCQ(ns}ZbG?!?4qMs)CtGvO1Eg2R6l}@)%EB_SQ)}nWY&5K0j`g(Xc`f{14Onh2 zYFXGAy(+$#x|D_UwJ#$AfgnXCK%ew>4oAh&!9j5q^jN@0Bi?bacfv>0xh4YPcU_Aq zYWOvNkL*XS^oqc!yjP;a4{#^@i++bsMdU&5?D)A}@tA$Xa*eP&iWyrrW=%QQJTcw= zM)QVAnNj|_7mU!-TeIx_2b69mb>(%~M8*ysV|`FLAu)~M?=|--p$S!n)^X+KXH@SA zPU8mN*3gNkwJo#0mx-9==x$bURd9(WVPH)BqG5P7!OqFjaMmojRWcmtiX zlvUU1M8#(}PTrID2WcwTk8^ww4_W^(IFs<2s!T)spH?2A%mfB=IH{Rge*k*XWdlYu z+7@*4Hc72Cn^Xry%QsQvvW|F!D@2F`)+vQaaq;VfsD^Yuq48Kcidks(FJ?nl5byjN zRCBd?j+7y=m=^~Kpo9dRSXU4lKkGZ&KcE^{-?|N9qmCJIQBgMHiZV`;rlM0(Mkwwy z#q_dv2Df|aA50>hqt<)sot_3G+6>Wg7}n&jph;B)IOvbdCy($d=Kt$I)jK^$ZAPff zqDEJ$nl`DB5*?Sc0@f4iKmgB=bslQyKicRCH_hTw? z2*|fyCb$_1gs?GD+#Zp>Em2p!ew|K`gw+OD#a?nXb}C?aVJGQMScQw9p&Z4}|72tF zX;#objzysAb7QAO^k%d7xzhnD7?d~<;@5A^EDWl_j#x?eD~c=+eLXPTtI91k{R>Ak qknp!*p^2<7JI^YyU=0lm6qad0-3Awr!&^~IiKEjYsH_(N_J06;+%s?h literal 0 HcmV?d00001 diff --git a/res/knightlore.sna b/res/knightlore.sna new file mode 100644 index 0000000000000000000000000000000000000000..6e1914b9bf005331c4d74495e9c2c8fac5bc0f1a GIT binary patch literal 49179 zcmeFaeSA|z_6K}#ZknWRnxrqlZJRa;g;E5e7KB(!(?tdL_j5&E*JTYtRJP(;1YcV; zB*B-8yS}i#qM~#a#noM@idfzQEup1`7hSs`27*FyRUm>wfb`DuJ#$kiu8R2VexB!# z-;Hx-?!2BkbLPxB=T6aO`Q0n41i^Cg4WEy?e!I4O;*K>!*(~9YZc#YXqYrxHKUBw7 zDMf8U-1|f2l_(T;{ZJ|8pAahWOFnzv_nHnULev>ok~t{sQQlqeRD7~r7WOFhai=lv zIaP?%$@0^3PrdwT=V~=`U{vf>gVR&ym14|1J63sm?x{B~3dMtgQyJe)iAMuHP?*{w zmxi7bJT*0*WW}F=zuMj@O!ZXP)L3g~oHoQyXfN0ViT%J6#PZpM{ODj zsO<-uGb)t!1$(skP7;MZ=0BrD_JrmMo>29aWWAq&zuJC+Fk?#Zn$VEysi%$b6WR-| zfc}T@kBSMQQqTua>$As;6cxOfg87Hq$503o^l0&&Bno@XzphgbDKh4NSND$k3HYn+ zWueY%stZ-$Rdd=1KcT(g|1ta(Y$0>tv_5;hh%uo8Ec_$%I~c$g=+WXkNfh>&f6X3w zQ;6p8JL~nAg_S@6#~W8nir4wRF)Emx z?<;X<^ephd6zAuGnkmoKbO^ed>M763W&9xd$>U!tloi)ZsUhk6v)Vfax#Kxc&E3{A zk08tFMSXtq`0Ioja*e0#Ik~38>JiFnI-cW7#7}N-#QAxk+A}YN`GYJ_DrC8;j#l6&&%Z*by6mos&|PTrv)Vr=$eptHt|`@3-k&x9gis^j<*kx2fohLX z73!4lI;;76aDH~&qLV{=fWJ;B%R#P4{N(wYgy1HhF686${~7HSLEa-z@p;Dtf8P4* zglgG0C5ZX&8RHRxp*^zitmeNC=VynfUJkVgp^&y-me+9=V9p>M%$s?=_wa9g6JO8^wo{(JAD)?$H4asun+0MVSqb9`V z-_L3<3v#P`ca3L9=iNVx|LZaTn!7t?Okl_LLT62@TvK!XS?pg0=jVZ#S9b0ctWKv_ z_Ii(CDS!I@PYB-XPOq~QZGKjJui)K@z3Zv=hJM!h=M@s(&Jgf+?ySZM>f9-JI+e4) z-zhj1Bg}4-F@$C(pP$VCCtIAJIIVq!0Q6+RD46h5pVP`uZ_3|LekT3uO#0cG^g9|t z5Nd=e&sl4p6H2WIJU#IE3HYh)d$j49Jgxm`!5OM?!eS^BN|TjPPAcRNK2-Q4#R7

X&(Cb% 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 literal 0 HcmV?d00001 diff --git a/res/shock.tap b/res/shock.tap new file mode 100644 index 0000000000000000000000000000000000000000..a30667f436018d9cedaa7ab5e256d9fbb2a3c07f GIT binary patch literal 99937 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 literal 0 HcmV?d00001 diff --git a/res/steg.tap b/res/steg.tap new file mode 100644 index 0000000000000000000000000000000000000000..a509ffa55f39229b9735841e7d2f27e7873736ba GIT binary patch literal 51271 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